curl is in every developer's daily toolkit — for testing APIs, debugging HTTP, downloading files, and automating requests. This reference covers the flags you actually use.
Quick reference
| Flag | What it does |
|---|---|
-X METHOD |
Set HTTP method (GET/POST/PUT/DELETE/PATCH) |
-H "Header: value" |
Add a request header |
-d "data" |
Send request body |
--data-raw "data" |
Send raw body (no escaping) |
-u user:pass |
Basic auth |
-b "name=val" |
Send cookie |
-c cookies.txt |
Save cookies to file |
-o file |
Save response to file |
-O |
Save with server filename |
-L |
Follow redirects |
-I |
HEAD request (headers only) |
-i |
Show response headers + body |
-v |
Verbose — show full request/response |
-s |
Silent (no progress meter) |
-w "%{http_code}" |
Print specific info after request |
--max-time 10 |
Timeout in seconds |
-k |
Skip SSL certificate verification |
--compressed |
Request gzip + decompress response |
--http2 |
Force HTTP/2 |
-x http://proxy:8080 |
Use proxy |
GET requests
# Basic GET
curl https://api.example.com/users
# Pretty-print JSON (pipe to jq)
curl -s https://api.example.com/users | jq .
# Show only HTTP status code
curl -s -o /dev/null -w "%{http_code}" https://example.com
# Check if site is up (200 OK)
curl -sI https://example.com | head -1
# Follow redirects
curl -L https://bit.ly/some-short-link
# GET with query parameters
curl "https://api.example.com/search?q=hello+world&page=1"
POST requests
# POST JSON body
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-d '{"name": "Alice", "email": "alice@example.com"}'
# POST form data (application/x-www-form-urlencoded)
curl -X POST https://example.com/login \
-d "username=alice&password=secret"
# POST form data — shorthand (curl infers POST + form content-type)
curl -F "username=alice" -F "password=secret" https://example.com/login
# POST with a JSON file
curl -X POST https://api.example.com/import \
-H "Content-Type: application/json" \
-d @payload.json
PUT / PATCH / DELETE
# PUT — replace resource
curl -X PUT https://api.example.com/users/42 \
-H "Content-Type: application/json" \
-d '{"name": "Alice", "email": "alice@example.com"}'
# PATCH — partial update
curl -X PATCH https://api.example.com/users/42 \
-H "Content-Type: application/json" \
-d '{"email": "newemail@example.com"}'
# DELETE
curl -X DELETE https://api.example.com/users/42
Headers
# Add a custom header
curl -H "X-Custom-Header: myvalue" https://api.example.com
# Multiple headers
curl \
-H "Accept: application/json" \
-H "X-Request-ID: abc123" \
https://api.example.com/data
# Remove a header curl sends by default (e.g., User-Agent)
curl -H "User-Agent:" https://api.example.com
# Set custom User-Agent
curl -A "MyApp/1.0" https://api.example.com
Authentication
# Bearer token (JWT / OAuth)
curl -H "Authorization: Bearer eyJhbGci..." https://api.example.com/me
# Basic auth
curl -u alice:secretpassword https://api.example.com/admin
# Basic auth — prompt for password (don't put it in shell history)
curl -u alice https://api.example.com/admin
# API key in header
curl -H "X-API-Key: your-api-key" https://api.example.com/data
# API key in query string
curl "https://api.example.com/data?api_key=your-api-key"
# Digest auth
curl --digest -u alice:password https://api.example.com/secure
File upload
# Upload a file as multipart/form-data
curl -X POST https://api.example.com/upload \
-F "file=@/path/to/file.jpg"
# Upload with custom field name and MIME type
curl -X POST https://api.example.com/upload \
-F "photo=@avatar.png;type=image/png"
# Upload multiple files
curl -X POST https://api.example.com/upload \
-F "files=@photo1.jpg" \
-F "files=@photo2.jpg"
# PUT a raw file (binary upload)
curl -X PUT https://api.example.com/image \
-H "Content-Type: image/jpeg" \
--data-binary @avatar.jpg
Download files
# Download and save with given filename
curl -o downloaded.zip https://example.com/archive.zip
# Download and keep the server filename
curl -O https://example.com/archive.zip
# Download multiple files
curl -O https://example.com/file1.txt -O https://example.com/file2.txt
# Download with progress bar (default, just remove -s)
curl -O https://example.com/large-file.iso
# Resume interrupted download
curl -C - -O https://example.com/large-file.iso
# Download only if modified since date
curl -z "Mon, 01 Jan 2025 00:00:00 GMT" -O https://example.com/data.csv
Cookies
# Send a cookie
curl -b "session=abc123; theme=dark" https://example.com
# Save cookies from response to file
curl -c cookies.txt https://example.com/login -d "user=alice&pass=secret"
# Send cookies from file (for subsequent requests)
curl -b cookies.txt https://example.com/dashboard
# Session: save + send cookies (login then use session)
curl -c cookies.txt -b cookies.txt \
-X POST https://example.com/login \
-d "user=alice&pass=secret"
curl -c cookies.txt -b cookies.txt https://example.com/dashboard
Output and debugging
# Verbose — see full headers, TLS handshake, redirects
curl -v https://api.example.com/data
# Only response headers (HEAD request)
curl -I https://example.com
# Show response headers then body
curl -i https://api.example.com/data
# Silent mode (no progress, no errors)
curl -s https://api.example.com/data
# Silent with errors still shown
curl -sS https://api.example.com/data
# Write-out: print timing/status info
curl -s -o /dev/null -w "Status: %{http_code}\nTime: %{time_total}s\nSize: %{size_download} bytes\n" https://example.com
# Trace — full byte-level dump (extreme debug)
curl --trace-ascii - https://example.com
Useful -w variables
| Variable | What it prints |
|---|---|
%{http_code} |
HTTP status code (200, 404…) |
%{time_total} |
Total request time (seconds) |
%{time_connect} |
Time to establish TCP connection |
%{time_starttransfer} |
Time to first byte (TTFB) |
%{size_download} |
Downloaded bytes |
%{speed_download} |
Average download speed |
%{url_effective} |
Final URL after redirects |
Timeouts and retries
# Max total time for entire request (seconds)
curl --max-time 10 https://api.example.com/slow
# Connection timeout only
curl --connect-timeout 5 https://api.example.com
# Retry on transient errors (500, network failure)
curl --retry 3 https://api.example.com/data
# Retry with delay between attempts (seconds)
curl --retry 3 --retry-delay 2 https://api.example.com/data
# Retry on any HTTP error (4xx/5xx)
curl --retry 3 --retry-all-errors https://api.example.com/data
SSL / TLS
# Skip SSL certificate verification (dev/self-signed only — never in production)
curl -k https://localhost:8443/api
# Use specific CA bundle
curl --cacert /path/to/ca-bundle.crt https://api.example.com
# Client certificate authentication (mTLS)
curl --cert client.crt --key client.key https://api.example.com/secure
# Show certificate info
curl -v --head https://example.com 2>&1 | grep -A5 "Server certificate"
# Force TLS version
curl --tls-max 1.2 https://legacy.example.com
Proxy
# HTTP proxy
curl -x http://proxy.company.com:8080 https://api.example.com
# SOCKS5 proxy (e.g., SSH tunnel)
curl --socks5 127.0.0.1:1080 https://api.example.com
# Proxy with auth
curl -x http://user:pass@proxy.company.com:8080 https://api.example.com
# Bypass proxy for specific hosts
curl --noproxy "internal.company.com" -x http://proxy:8080 https://external.com
Parallel / multiple requests
# Run multiple URLs sequentially
curl https://api.example.com/a https://api.example.com/b
# Parallel (--parallel flag, curl 7.66+)
curl --parallel \
https://api.example.com/a \
https://api.example.com/b \
https://api.example.com/c
# URL globbing — request /item/1 through /item/10
curl https://api.example.com/item/[1-10]
# URL list from a file
xargs -a urls.txt curl -s -o /dev/null -w "%{http_code} %{url_effective}\n"
Config files
For flags you use on every request, put them in ~/.curlrc:
# ~/.curlrc
silent
show-error
location # follow redirects
max-time = 30
compressed # request gzip
Or use a per-project config:
curl --config project.curlrc https://api.example.com/data
Common patterns
Health check script
#!/bin/bash
STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://api.example.com/health)
if [ "$STATUS" != "200" ]; then
echo "Health check failed: $STATUS"
exit 1
fi
echo "OK: $STATUS"
API request with JSON and auth
# Store base URL and token in variables
BASE="https://api.example.com"
TOKEN="eyJhbGci..."
# GET
curl -s -H "Authorization: Bearer $TOKEN" "$BASE/users" | jq .
# POST
curl -s -X POST "$BASE/users" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Bob"}' | jq .
Measure response time
curl -s -o /dev/null -w "
DNS: %{time_namelookup}s
Connect: %{time_connect}s
TLS: %{time_appconnect}s
TTFB: %{time_starttransfer}s
Total: %{time_total}s
Size: %{size_download} bytes
" https://example.com
Loop over IDs
for ID in 1 2 3 4 5; do
curl -s "https://api.example.com/users/$ID" | jq '.name'
done
6 common mistakes
| Mistake | Problem | Fix |
|---|---|---|
Forgetting quotes around URL with ? |
Shell expands ? as glob |
Always quote URLs: curl "https://api.com?q=hello" |
-d with GET request |
curl changes method to POST automatically | Add -G flag for GET with body params, or use --data-urlencode |
-k in scripts/CI |
Skips SSL validation — security risk | Use a proper CA bundle instead |
--max-time vs --connect-timeout |
Max-time covers full request; connect-timeout only TCP | Set both for production scripts |
Not using -s in scripts |
Progress meter corrupts piped output | Always add -s (silent) when piping to jq etc. |
| Credentials in command line | Shell history saves them | Use env vars: -H "Authorization: Bearer $TOKEN" |
FAQ
How do I send JSON with curl?
curl -X POST https://api.example.com/data \
-H "Content-Type: application/json" \
-d '{"key": "value"}'
Both -H "Content-Type: application/json" and -d are required. curl doesn't automatically set Content-Type when using -d.
How do I see what curl actually sent?
Use -v (verbose). It prints request headers prefixed with > and response headers with <. Use --trace-ascii - for the full byte dump.
What's the difference between -o and -O?
-o filename saves to the filename you specify. -O saves using the filename from the URL path. Use -O when the URL ends with the filename you want; use -o when you want to name it yourself.
How do I make curl follow redirects?
Add -L. By default curl stops at the first redirect. -L follows up to 30 redirects (configurable with --max-redirs).
How do I use curl with a proxy?
curl -x http://proxy:8080 https://target.com
# Or set environment variable
export https_proxy=http://proxy:8080
curl https://target.com
How do I test a local API running on localhost?
curl http://localhost:3000/api/users
# For HTTPS with self-signed cert
curl -k https://localhost:8443/api/users