The Flag Forest
curl has roughly 237 different flags. You’ll use about 6 of them 95% of the time. But knowing the next handful? That’s where you stop cargo-culting Stack Overflow answers and start solving actual problems.
Let’s talk about the flags that matter—the ones that’ll keep you from SSH-ing into a server at 2 AM to debug a flaky webhook.
The Core Six You Already Know
These are your bread and butter. If you don’t know these, go practice for five minutes:
curl -X POST https://api.example.com/endpoint # HTTP methodcurl -H "Authorization: Bearer token123" https://... # Custom headercurl -d '{"key":"value"}' https://... # POST datacurl -u username:password https://... # Basic authcurl -L https://... # Follow redirectscurl -o filename.json https://... # Save to fileStandard stuff. You’ve used all of these. But here’s where it gets interesting.
The Ones That Save You Hours
-w — Output Formatting (The Detective Flag)
Want to know what actually happened during that request? Not just the body, but headers, timing, response code, all of it?
curl -w "Status: %{http_code}\nTime: %{time_total}s\nRedirects: %{num_redirects}\n" \ https://api.example.com/endpointThat %{...} syntax is your friend. Some useful ones:
curl -w "DNS lookup: %{time_namelookup}sConnection: %{time_connect}sTTFB: %{time_starttransfer}sTotal: %{time_total}sSize: %{size_download} bytesStatus: %{http_code}" https://api.example.comThis tells you exactly where requests are getting slow. Is it DNS? The connection? The server thinking? Now you know.
-v and -vv — Verbose Mode
curl -v https://api.example.com/endpointShows you request headers, response headers, SSL/TLS details. The arrow notation:
> GET /endpoint HTTP/1.1> Host: api.example.com> User-Agent: curl/8.1.2
< HTTP/1.1 200 OK< Content-Type: application/jsonUse -vv for even more detail (including HTTP/2 frame information if you’re deep troubleshooting).
-b and -c — Cookie Handling
Save cookies from one request, reuse them in another:
curl -c cookies.txt https://api.example.com/login \ -d '{"user":"you","pass":"secret"}'
# Use those cookies on the next request:curl -b cookies.txt https://api.example.com/protectedLifesaver when you’re testing authenticated flows.
--data-raw — Raw POST Data (No Escaping)
Tired of fighting with quote escaping in shell scripts?
curl --data-raw '{"message":"Don'\''t escape me"}' https://api.example.com--data-raw treats the string literally. No shell interpolation weirdness.
-H @headers.txt — Load Headers from File
When you’re building complex requests, write headers to a file:
# headers.txt:Authorization: Bearer eyJhbGc...X-Custom-Header: value123Content-Type: application/jsonThen:
curl -H @headers.txt -d '{"key":"value"}' https://api.example.comCleaner than stacking -H flags ten deep.
--compressed — Automatic Decompression
Most APIs return gzip-compressed responses. curl can auto-decompress:
curl --compressed https://api.example.com/endpointSaves bandwidth on large payloads.
--max-time — Request Timeout
Prevent hanging indefinitely on dead servers:
curl --max-time 10 https://api.example.com # 10 second timeoutPair it with -m (same thing, shorter flag). Essential for monitoring scripts.
The One That Stops Redirects Cold
curl -L --max-redirs 3 https://example.com # Follow up to 3 redirectsSome APIs redirect you to hell and back. Cap it. Prevents infinite loops and makes debugging obvious.
Real-World Example: Debugging an API
# Full diagnostics:curl -v -w "\n\nStatus: %{http_code}\nTotal time: %{time_total}s\n" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ --compressed \ --max-time 5 \ -d '{"action":"test"}' \ https://api.example.com/webhookThat one command tells you:
- Full HTTP conversation (request + response headers)
- Response status code
- Total request time
- Compressed if possible
- Won’t hang beyond 5 seconds
Worth memorizing? Absolutely.
Advanced: Conditional Requests
Sometimes you only want to download if the file changed:
# Download only if server has a newer version:curl -z last-modified.txt -o file.txt https://example.com/file.txt
# The -z flag checks the local file's modification time# If remote is older, nothing downloads (saves bandwidth)Or use ETags for precise cache validation:
# Save the ETag from first request:curl -i https://api.example.com/data | grep -i etag
# Later, check if it's the same:curl -H 'If-None-Match: "abc123"' https://api.example.com/data
# If unchanged, server returns 304 Not Modified (no body download)This is crucial for monitoring scripts that poll APIs—you stop downloading the same gigabyte of data over and over.
Building Scripts That Don’t Break
When curl appears in a bash script, make it defensive:
# Fail immediately if curl fails:set -e
# Set timeout to prevent hanging:curl --max-time 10 \ --retry 3 \ --retry-delay 5 \ https://api.example.com/endpoint || exit 1
echo "Request succeeded"The --retry flags are criminally underused. Three retries with five-second delays handles temporary network blips without making your script fragile.
For loops over many URLs:
urls=("https://api.example.com/1" "https://api.example.com/2")
for url in "${urls[@]}"; do curl --silent --show-error \ --max-time 10 \ --retry 2 \ "$url" || echo "Failed: $url"done--silent kills progress meters (noisy in scripts), --show-error still shows real errors. Clean output.
The Trick Nobody Remembers
Want to see what curl is actually sending?
curl --trace - https://api.example.com/endpoint 2>&1 | head -20That --trace - writes trace output to stdout (normally goes to stderr). You see everything: SSL negotiation, every byte sent and received, all headers.
=> Send header=> GET / HTTP/1.1=> Host: api.example.com=> User-Agent: curl/7.68.0...<= Recv header<= HTTP/1.1 200 OK<= Content-Type: application/jsonWhen something weird is happening and -v isn’t verbose enough, --trace shows the truth.
The trick isn’t knowing all 237 flags. It’s knowing these dozen ones so well you don’t need to think about them. That’s when you’re actually productive instead of fighting the tool.