HTTP / Networking12 min read

Debugging HTTP Headers with curl: From Basic Flags to Packet-Level Inspection

A practical guide to inspecting HTTP request and response headers with curl, covering basic flags, debugging tricky issues, and understanding what your headers are really doing.

curlHTTP headersdebuggingnetworkingDNSSSL/TLS

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.

Basic verbose output showing request and response headers
curl -v https://example.com 2>&1 | head -20

One 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.

HEAD request to inspect response headers only
curl -I https://example.com/api/status
lightbulb

When 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.

Sending custom headers with -H
curl -H "Authorization: Bearer token123" -H "X-Custom: value" https://api.example.com

I 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

  1. 14:00User reports that new blog posts aren't showing up after deployment.
  2. 14:05I run curl -I https://blog.example.com/new-post — see Status: 200 OK, but no Cache-Control header.
  3. 14:10I check the Nginx config: it has add_header Cache-Control "no-cache"; but only for certain locations.
  4. 14:15I discover the CDN (CloudFront) is adding its own Cache-Control: max-age=86400 because the origin didn't send one.
  5. 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.

Debugging through a proxy with verbose output
curl -x http://proxy.company.com:8080 -U user:pass -v https://external-api.com

Going 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.

Write a readable trace to a file for analysis
curl --trace-ascii trace.txt https://example.com/api

I 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.

73%

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

  1. 1Start with curl -I to see status and headers without body.
  2. 2If redirect, follow with -L (but beware: -L may change headers).
  3. 3Add -v to see SSL handshake and connection details.
  4. 4For request body issues, use --trace-ascii and compare sent vs received.
  5. 5Check intermediate proxies with Via and X-Cache headers.
  6. 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.

Proper way to trust a custom CA
curl --cacert /path/to/internal-ca.crt https://internal-service.local

I'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.