Developer Cluster
curl and HTTP Debugging: The Complete Guide
When a request does not return what you expected, the question is almost never "is the network up." It is one of: are you hitting the right URL, are the headers correct, did the body get encoded the way the server expected, is there a redirect, is the TLS handshake succeeding, is the server returning a cached response? Every one of those has a curl flag that will answer it in one command, and knowing the flags is the difference between ten seconds and twenty minutes of debugging.
This guide covers the curl options you will use weekly, the HTTP concepts they map to, and the browser tools that turn ad-hoc debugging into a workflow you can share with teammates.
The bare minimum curl
The simplest curl command is curl https://example.com. It sends a GET request and prints the response body. Useful for sanity checks. Useless when you need to understand what is happening on the wire.
The four flags that turn curl into a debugger:
-v(verbose): print the request headers, the response headers, and the TLS handshake details.-i(include): print response headers before the body.-I(head): send a HEAD request — headers only, no body. Perfect when you want to see status and headers without downloading 100 MB.-L(location): follow redirects automatically.
The official reference is curl.se/docs, which covers every flag the binary understands. The most-read command in that documentation is man curl, and with good reason.
Sending and inspecting headers
HTTP headers carry most of the interesting information in a request. Authentication tokens, content types, cache directives, content length, encoding hints, CORS preflights — all of them are headers.
To add a header: -H "Authorization: Bearer abc". To set multiple, repeat -H. To see exactly what the server is sending back: -I for headers only, or -i for headers plus body.
Common mistakes when setting headers from curl:
- Forgetting the colon-space format.
-H "Authorization Bearer abc"is wrong. - Quoting issues on shells. Single quotes on Unix, double quotes on Windows cmd; complex headers often need careful escaping.
- Shadowing curl's default headers. curl sets
User-Agentautomatically; override it with-A "MyAgent/1.0"or pass-H "User-Agent: MyAgent/1.0".
MDN's HTTP headers reference is the quick lookup when you cannot remember whether it is Content-Type or Content-type (headers are case-insensitive per RFC 7230, but consistency matters in logs).
HTTP methods and bodies
POST, PUT, PATCH, DELETE — curl uses -X METHOD to set the verb. -d attaches a request body and implicitly makes the method POST. --data-binary sends the body without any conversion. --data-urlencode URL-encodes the body, which is what forms use.
A common POST:
curl -X POST https://api.example.com/items \
-H "Content-Type: application/json" \
-d '{"name":"example","count":3}'
Three things that trip beginners up:
- Content-Type matters. Sending JSON with
Content-Type: application/x-www-form-urlencodedis a common bug. Servers that accept both silently parse it wrong. - Single vs double quotes. Bash does variable expansion inside double quotes. If your JSON has a
$, it will get replaced. Single quotes keep it literal. - Escaping nested quotes. JSON requires double quotes around keys. Bash requires you to escape them if your outer quotes are double. Write the JSON in a file and use
-d @file.jsonto avoid the mess.
If the body is form data, you almost certainly want --data-urlencode. Spaces become + or %20, special characters get percent-encoded, and the server parses it as a form. If you are encoding query parameters instead of a body, URL Encoder/Decoder will encode a string the same way curl would, which is handy when building URLs by hand.
To stop typing curl commands by hand, curl to Code converts a curl command into the equivalent request in your language of choice — JavaScript fetch, Python requests, Go http, and more. Very useful when you want to port a curl recipe from Stack Overflow into your actual app code.
Redirects and cookies
Redirects are a common source of confusion. curl does not follow them by default; you get the 301 or 302 response and the Location header. With -L, curl follows the chain until it hits a non-redirect response.
Common redirect gotchas:
- Method change on 301/302. Historically, most clients follow a POST redirect as a GET. 307 and 308 preserve the method and body. If your redirect is turning POSTs into GETs, use 307 on the server side.
- Redirect loops. curl has a cap (default 50). If a server redirects to itself, curl will stop and error out.
- Cross-host redirects dropping Authorization. curl drops the Authorization header when following a redirect to a different host, which is correct behavior for safety but sometimes surprising.
Cookies work the same way. Use -b cookies.txt to send and -c cookies.txt to save. Session-based APIs often require cookie handling for multi-request flows.
TLS and the secure handshake
When curl cannot connect to an HTTPS endpoint, the verbose output (-v) shows the TLS handshake. You will see the protocol version, the cipher suite, the certificate chain, and the validation result. If the certificate is invalid, curl refuses the connection.
Flags you will reach for in TLS debugging:
-k(insecure): accept self-signed and invalid certificates. Useful for local testing, dangerous in production scripts.--cacert <file>: trust a specific CA bundle. Useful in corporate networks with internal CAs.--tlsv1.2: force a specific TLS version, to test whether a server supports newer or older versions.--resolve host:port:ip: pin the DNS resolution, useful when debugging load balancer issues without touching the hosts file.
For a quick sanity check that a host is reachable at all before blaming TLS, test with a plain connection first.
Reading status codes
HTTP status codes are three-digit responses that tell you what the server did with your request. They cluster into families: 2xx success, 3xx redirect, 4xx client error, 5xx server error. Every code has a specific meaning, and knowing the meaning is faster than guessing.
Some that consistently confuse teams:
- 401 vs 403. 401 means "you are not authenticated" (you need to log in or refresh a token). 403 means "you are authenticated but not authorized" (no amount of re-auth will help; permissions are the problem).
- 404 vs 410. 404 means "not found." 410 means "gone — do not ask again." Google treats 410 as a stronger delisting signal than 404.
- 502 vs 503 vs 504. 502 is "bad gateway" (upstream returned an error). 503 is "service unavailable" (the server knows it is overloaded). 504 is "gateway timeout" (upstream took too long). A server that knows the difference is operationally much easier to debug.
HTTP Status Codes is a quick reference for every code, its family, and its intended use. Keep it open in a tab whenever you are debugging an unfamiliar API.
Adjacent tools worth bookmarking
Tools worth having at hand when debugging HTTP: JSON Formatter to pretty-print API responses on the fly, Base64 Encoder/Decoder for Basic Auth credentials and binary payloads, JWT Decoder for Bearer tokens, Hash Generator for verifying response body checksums, and API Tester when you want a GUI alternative to curl for shareable request recipes.
Related pillar guide
This cluster is part of the developer track. For the broader view of browser-based developer tools, see The Complete Guide to Free Online Tools in 2026.
FAQ
Can I use curl to test my own API behind localhost?
Yes — curl http://localhost:3000/endpoint works fine. If you are running HTTPS locally with a self-signed cert, add -k to skip verification or --cacert to trust your dev CA.
How do I save the response body to a file?
curl -o file.json https://api.example.com/data writes the body to file.json. Use -O to keep the server's filename.
Why does my POST arrive empty at the server?
Almost always Content-Type or quoting. Check that Content-Type matches the body format and that your JSON did not get mangled by shell interpolation. -d @file.json bypasses shell quoting entirely.
What is the difference between -d and --data-binary?
-d converts newlines in the input. --data-binary sends the body exactly as provided. For JSON and binary uploads, prefer --data-binary.
Can curl test WebSocket connections?
Not really. curl is for request/response HTTP. For WebSockets use a dedicated client or a library. You can test the HTTP upgrade handshake with curl but not subsequent WebSocket frames.
Closing thought
curl is the HTTP debugger that never lies. Every curl command is an honest reproduction of what happened on the wire, which is a quality most other clients cannot claim. Spend an afternoon with the flags in this article and you will solve a class of bugs that used to take a whole day.