I've lost count of how many times I've seen engineers stare at a blank terminal after running curl https://api.example.com, wondering why they're getting a 301 or a blank page. The answer is almost always in the headers. curl is the Swiss Army knife for HTTP debugging, but most people only use the tip — the -v flag. In this post, I'll walk through the full toolkit: from basic verbosity to packet-level traces, and share a few incidents where headers told the real story.
Start with -v, But Know Its Limits
The first flag everyone learns is -v (verbose). It prints the request line, response headers, and some connection details. But -v has a quirk: when you use it with -o to save the body, it still shows headers on stderr and only the body goes to the file. That's fine, but many people forget that -v includes SSL/TLS negotiation details — you can see the certificate chain, cipher, and whether the connection reused a session.
curl -v https://example.com 2>&1 | head -20One common pitfall: -v does not show the request body by default. If you're debugging a POST request, use --trace or -v combined with --data-binary to see exactly what's sent.
Using -i and -I for Quick Header Inspection
When you just want the response headers without the connection noise, use -i (include) to print headers before the body, or -I (head) to send a HEAD request. I use -I constantly to check Cache-Control, Content-Type, and redirect targets without downloading the body.
curl -I https://example.com/api/statusWhen a server returns a 302 redirect, -I will show you the Location header. But note: some servers don't support HEAD and return 405. In that case, use -i with a GET and pipe to head.
Crafting Custom Headers with -H
The -H flag lets you set any header. But there are subtleties: if you set the same header twice, curl sends both values (unless the header is known to be single-valued). Also, some headers like Host are automatically set from the URL; if you override them with -H, curl will send both unless you use --header "Host:" to empty it first.
curl -H "Authorization: Bearer token123" -H "X-Custom: value" https://api.example.comI once spent an hour debugging a CORS issue only to realize I'd misspelled 'Origin' as 'Orgin'. curl happily sent the typo'd header, and the server ignored it. Always double-check header names.
The War Story: Headers That Lied
The Case of the Missing Caching Header
- 14:00User reports that new blog posts aren't showing up after deployment.
- 14:05I run curl -I https://blog.example.com/new-post — see Status: 200 OK, but no Cache-Control header.
- 14:10I check the Nginx config: it has add_header Cache-Control "no-cache"; but only for certain locations.
- 14:15I discover the CDN (CloudFront) is adding its own Cache-Control: max-age=86400 because the origin didn't send one.
- 14:20Fix: add Cache-Control at the application level, and verify with curl -I showing the new header.
Lesson
When an intermediary (CDN, proxy, load balancer) can add or override headers, always inspect the full header chain. Use curl's -H 'Via:' or check the X-Cache header to see if the response came from cache.
Debugging Proxies and Redirects
Proxies add their own headers: Via, X-Forwarded-For, X-Real-IP. If you're behind a corporate proxy, use -x to set the proxy and -U for authentication. Curl's -v will show you the CONNECT request for HTTPS tunnels. And always check if the server returns a 407 Proxy Authentication Required.
curl -x http://proxy.company.com:8080 -U user:pass -v https://external-api.comGoing Deep with --trace and --trace-ascii
When -v isn't enough, --trace is your next step. It outputs every byte sent and received in hex and ASCII. This is critical for debugging encoding issues (like a BOM in a JSON response), HTTP/2 frames, or extra whitespace in headers.
curl --trace-ascii trace.txt https://example.com/apiI once found a bug where a server was returning a Content-Length header with a trailing space, causing curl to hang waiting for more data. The hex dump showed 0x20 after the number. Without --trace, I would have blamed the network.
of HTTP API issues I've debugged were visible in headers before the first byte of the body
Putting It All Together: A Debugging Checklist
- 1Start with curl -I to see status and headers without body.
- 2If redirect, follow with -L (but beware: -L may change headers).
- 3Add -v to see SSL handshake and connection details.
- 4For request body issues, use --trace-ascii and compare sent vs received.
- 5Check intermediate proxies with Via and X-Cache headers.
- 6Verify certificate chain with --cacert or --cert-status.
The most expensive bug is the one you can't reproduce. curl headers give you the reproduction recipe in plain text.
A Note on Security: Never Use -k in Production Scripts
The -k (or --insecure) flag disables SSL certificate verification. It's tempting when dealing with internal self-signed certs, but it opens you to MITM attacks. Instead, use --cacert to point to a custom CA bundle, or --pinnedpubkey to pin the server's public key.
curl --cacert /path/to/internal-ca.crt https://internal-service.localI've seen teams ship scripts with -k and later wonder why they got compromised. Don't be that team.
Frequently asked questions
How do I see only the response headers with curl?
Use curl -I (or --head) to send a HEAD request and print only the response headers. For a GET request, use curl -s -D - -o /dev/null [URL] to dump headers to stdout and discard the body.
Why does curl -v show extra headers that I didn't send?
Curl automatically adds headers like Host, User-Agent, Accept, and Connection based on the URL and your system's libcurl defaults. You can override them with -H, but some (like Host) are mandatory for HTTP/1.1.
How can I debug HTTPS/TLS handshake issues with curl?
Use curl -v or --trace to see the TLS handshake. For deeper inspection, add --tlsv1.2 or --ciphers to restrict the protocol. You can also use --cert-status to request OCSP stapling.
What does curl --trace do that -v doesn't?
--trace (and --trace-ascii) shows every byte sent and received in hex and ASCII, including SSL/TLS records and HTTP/2 frames. It's invaluable for spotting unexpected bytes, BOMs, or line-ending issues.