curl Cheatsheet - curl HTTP Debugging Command Reference
For developers who need to hit APIs from the shell, reproduce production issues, and see a full HTTP round trip. Browsers cannot express custom headers, POST bodies, or relaxed TLS checks, so curl is the way to take full control of what a request looks like. By the end you can declaratively build requests with bodies/headers/cookies, break down DNS/connect/TTFB timing with -w to find where slowness lives, and use -v to inspect the TLS handshake and redirect chain.
Typical Use Case
The most typical scenario is "see one real request" while troubleshooting. When the frontend returns 400, replay that exact request with curl -v and inspect the outgoing headers against the server response to tell whether Content-Type is missing or an extra cookie is sent. For intermittent timeouts, use curl -w to split time_namelookup/time_connect/time_appconnect/time_starttransfer and isolate DNS, TCP connection, or slow server response. To reproduce a login-gated issue, carry cookies with curl -b cookies.txt and follow redirects with -L to reach the real landing page. Test a path with -I (headers only) before -d posting data to reduce mistakes.
Basic Requests 6
curl https://example.comcurl -X POST https://api.example.comcurl -H "Content-Type: application/json" -H "Authorization: Bearer xxx" https://api.example.comcurl -d '{"k":"v"}' https://api.example.comcurl -d @file.json https://api.example.comcurl -G https://api.example.com --data-urlencode "q=hello world"Debugging & Details 6
curl -v https://example.comcurl -I https://example.comcurl -L https://example.comcurl -w "%{http_code} %{time_total}\n" -o /dev/null -s https://example.comcurl -s https://example.comcurl --resolve example.com:443:1.2.3.4 https://example.comProxy & Timeout 5
curl -x http://proxy:8080 https://example.comcurl -x socks5h://127.0.0.1:1080 https://example.comcurl --connect-timeout 5 https://example.comcurl --max-time 30 https://example.comcurl --retry 3 https://example.comAuth & Upload 5
curl -u user:pass https://example.comcurl -F "file=@photo.jpg" https://example.com/uploadcurl -F "file=@photo.jpg" -F "name=tom" https://example.com/uploadcurl -o photo.jpg https://example.com/photo.jpgcurl --cookie "session=abc" https://example.comParameter matrix
| Parameter | Effect | Example |
|---|---|---|
-X <METHOD> | Force the request method, overriding the default curl infers from -d | curl -X POST https://api.example.com |
-H <header> | Append one header; repeat it to set Content-Type and Authorization among others | curl -H "Content-Type: application/json" -H "Authorization: Bearer ***" https://api.example.com |
-d <data> | Send a request body and infer POST; the payload defaults to form encoding | curl -d '{"k":"v"}' https://api.example.com |
-d @<file> | Read the body from a file, keeping a long payload out of the command line | curl -d @file.json https://api.example.com |
-G + --data-urlencode <data> | Move the data into the query string with proper URL encoding, so values with spaces need no manual escaping | curl -G https://api.example.com --data-urlencode "q=hello world" |
-v | Print the request line, request headers, response headers and handshake; the first tool for debugging | curl -v https://example.com |
-I | Fetch headers only, confirming status and cache headers without downloading the body | curl -I https://example.com |
-L | Follow 301 and 302 hops to the final resource; pair with --max-redirs to cap the chain | curl -L https://example.com |
-w <format> | Emit custom metrics from a template such as status and timing; monitoring probes rely on it for output | curl -w "%{http_code} %{time_total}\n" -o /dev/null -s https://example.com |
-s | Silent mode: drop the progress meter and error chatter so -o downloads leave the terminal clean | curl -s https://example.com |
-o <file> | Write the response body to a file, replacing terminal output when downloading binary content | curl -o photo.jpg https://example.com/photo.jpg |
--resolve <host:port:ip> | Pin a hostname to a specific IP, bypassing DNS caching when debugging load balancing and certificate coverage | curl --resolve example.com:443:1.2.3.4 https://example.com |
-x <proxy> | Route through the given proxy, for cases where direct access works but proxied access does not | curl -x http://proxy:8080 https://example.com |
socks5h://<proxy> | Use a SOCKS5 proxy and hand name resolution to it too, avoiding local DNS leakage or poisoning | curl -x socks5h://127.0.0.1:1080 https://example.com |
--connect-timeout <sec> | Bound only the connection-establishment phase so a stalled handshake cannot hang forever | curl --connect-timeout 5 https://example.com |
--max-time <sec> | Cap the whole request including connect and transfer, the standard guard against a hung script | curl --max-time 30 https://example.com |
--retry <n> | Retry automatically after failures, absorbing transient network flapping | curl --retry 3 https://example.com |
-u <user:pass> | Send an HTTP Basic auth header instead of writing Authorization by hand | curl -u user:pass https://example.com |
--cookie <header> | Carry cookies to replay an authenticated request, equivalent to reusing a browser session | curl --cookie "session=abc" https://example.com |
-F <field=value> | Build a multipart/form-data request carrying a file plus ordinary fields at once | curl -F "file=@photo.jpg" -F "name=tom" https://example.com/upload |
Common pitfalls
SymptomA curl call inside a script sits there burning CPU and never returns, holding up the monitoring job.
Causecurl has no timeout by default: as long as the peer keeps the connection open without answering, the call waits indefinitely and the caller cannot tell.
FixSet explicit bounds: --connect-timeout 5 for the connection phase and --max-time 30 for the request as a whole.
SymptomAfter -s the script raises nothing, yet the status it records is always the same failing value.
CauseSilent mode only suppresses terminal output; it does not turn HTTP errors into a non-zero exit code, so shell failure detection never fires.
FixJudge from the response itself: use -w "%{http_code} %{time_total}\n" -o /dev/null -s to pull the status out explicitly and decide for yourself.
SymptomI believed I was testing one hostname and the request actually hit a different machine.
Cause--resolve only overrides local resolution; if another resolution path or a proxy is in play the request may not reach the pinned IP, and the conclusion rests on the wrong backend.
FixAdd -v and read the address actually connected to, confirming it matches the IP in --resolve before drawing conclusions.
SymptomAn upload endpoint answers 4xx and the server says it never received the file.
CauseMixing -F with a hand-written Content-Type collides with the multipart boundary curl generates on its own, mangling the request structure.
FixPick one approach: let -F "file=@photo.jpg" -F "name=tom" generate the multipart headers and do not override Content-Type yourself.
SymptomA large upload is rejected with a malformed request body message.
CauseWith -F curl is responsible for chunking the payload; adding a -d segment concatenates two different body formats into one, which the receiver cannot parse.
FixPut the extra fields in the same -F call, as in -F "file=@photo.jpg" -F "name=tom", and never mix it with -d.
SymptomAn authenticated request returns 401 even though the password is definitely right.
CauseWhen credentials are mixed with -H or -d the generated headers may not match what the server expects, and Basic may not even be the scheme this endpoint uses.
FixEither hand authentication to curl with curl -u user:pass, or state the scheme the endpoint actually wants via curl -H "Authorization: Bearer ***".
SymptomSpaces and special characters in a query value arrive garbled or truncated.
CauseA hand-written URL leaves spaces and characters like & or # unencoded, so the server reads them as separators while parsing the query string.
FixUse curl -G https://api.example.com --data-urlencode "q=hello world" and let curl do the encoding.
SymptomThe same endpoint works when typed by hand but returns an empty response inside a cron job.
CauseA hand-run command can depend on the terminal or the current directory, neither of which exists in a scheduled run, and without -o the output is simply discarded or written elsewhere.
FixRewrite it as curl -s https://example.com with an explicit -o destination, validate the status with -w "%{http_code} %{time_total}\n" -o /dev/null -s, and use absolute paths.
SymptomI set -d and assumed the request was a POST, but the method on the wire was something else.
Cause-X overrides the method curl infers from -d, so a hand-written method that disagrees with the body sends the server down a branch that simply errors out.
FixCheck the request line with -v, and unless you need redirect-specific behaviour, let curl infer the method from -d instead of hardcoding -X.
SymptomAfter a redirect the response is still an interstitial page rather than the target resource.
CauseWithout following, curl hands back the 3xx headers and body verbatim, which is usually just HTML pointing at the next hop.
FixFollow the chain with curl -L https://example.com, and add --max-redirs when you need a cap against redirect loops.
Troubleshooting
1Confirm the status and cache headers with a headers-only request first
curl -I https://example.comThe first line is the status code, and no body is downloaded, which makes this the cheap pre-check before writing to an endpoint.
2Turn on verbose output when you need the full request, response headers and handshake
curl -v https://example.comThis shows the method actually sent, the request headers and the status line, which is what tells you what the server really received.
3Save both request context and response body to files and read them later
curl -s https://example.com -o resp.json-s keeps the terminal clean and -o puts the body into resp.json; if the file is empty the URL returned no body.
4Emit only status and total time for a monitoring probe
curl -w "%{http_code} %{time_total}\n" -o /dev/null -s https://example.comThe terminal keeps a single line of numbers; time is in seconds, ready to drive timeout and latency alerts.
5Add the cookie when reproducing an authenticated problem
curl --cookie "session=abc" https://example.comA 200 here means the fault was in authentication; a persistent 401 or 403 points at permissions or the token itself.
6Pin the hostname to a specific IP when DNS or load balancing is in doubt
curl --resolve example.com:443:1.2.3.4 https://example.comThe request goes straight to that IP while keeping the original hostname for TLS and the Host header; a healthy status clears resolution and scheduling as the cause.
7Bound a probe with timeouts so the job cannot hang
curl --connect-timeout 5 --max-time 30 --retry 3 https://example.comFive seconds to connect, thirty seconds overall, three retries: with all three caps in place the job cannot wait forever.
Command Examples
Break down the time each phase takes in a request
curl -o /dev/null -s -w "DNS: %{time_namelookup}s\nCONNECT: %{time_connect}s\nTTFB: %{time_starttransfer}s\nTOTAL: %{time_total}s\n" https://example.com/api-o /dev/null drops the body so the timing lines stay readable, and -w prints the per-phase numbers. A slow time_namelookup points at name resolution, a slow time_connect at connectivity, and a slow time_starttransfer at server-side work or a slow first byte.
Output
DNS: 0.012s CONNECT: 0.047s TTFB: 0.213s TOTAL: 0.220s
POST with a JSON body, custom header, and cookie
curl -X POST 'https://api.example.com/login' -H 'Content-Type: application/json' -H 'X-Api-Key: abc123' -b 'session=xyz' -d '{"username":"test"}'Note that -d does not set the content type on its own, so the header has to be supplied explicitly. -b sends cookies and -H may be repeated to stack several custom headers.
Skip TLS verification for a local self-signed service
curl -k https://localhost:8443/healthKeep -k for local development or an internal self-signed certificate. In production the verification must stay on, because skipping it lets a man-in-the-middle read every byte of the traffic.
Common Pitfalls
- -d sends form data by default; when POSTing JSON you must add -H "Content-Type: application/json" or the server may parse it as a form and return 400 or empty data.
- -k disables TLS verification — only acceptable for local/internal self-signed certs. Used on production it defeats HTTPS entirely.
- When combining -X POST with -d, -X can override the inferred method; confirm the actual method sent with -v.
- In monitoring scripts -w time_total is measured in seconds and curl has no default timeout; always add --max-time so the process cannot hang forever.
- To follow redirects use -L rather than editing the URL by hand, or you end up at the redirect page instead of the final resource.
Tips
- When sending JSON with -d, manually add -H "Content-Type: application/json" or the server may fail to parse.
- For HTTPS certificate issues, use -v to see the handshake; skip verification temporarily with -k (never in production).
- -w time_total is in seconds. Monitoring scripts should pair with --max-time to prevent hanging.
FAQ
How do I send a request with a Cookie or a login session in curl?
To pass fixed cookies in one shot, use curl -b "name=value; token=xxx". To store the cookies returned by the server, use -c cookies.txt, then send them back with -b cookies.txt to reuse the session. For login flows, run the login request with -c first, then use -b on the following requests to stay authenticated.
How do I get a curl command for an authenticated API from the browser?
Open DevTools with F12, go to the Network panel, perform the authenticated request once, then right-click it and choose Copy as cURL. That reproduces all the headers, cookies, and body. Sanitize any exposed Cookie or Token in that command before reuse or sharing.
How do I POST JSON vs form-encoded data with curl?
For JSON, use curl -X POST -H "Content-Type: application/json" -d '{"a":1}'. For form data, -d "key=value&k2=v2" sets Content-Type to application/x-www-form-urlencoded automatically, or use -F "file=@a.png" for multipart uploads. Note -X POST is not needed when -d is present, since curl implies it.
What should I do when curl reports an HTTPS certificate error?
For self-signed or internal certificates, pass -k (--insecure) to skip verification while diagnosing; in production, prefer fixing the CA or adding the proper certificate rather than relying on -k. To trust a specific CA, use --cacert path.pem. Add --noproxy "*" to bypass a proxy if one is interfering.
How do I tell whether slowness is in the network or the server?
Use curl -w "\ntime_namelookup:%{time_namelookup}\ntime_connect:%{time_connect}\ntime_starttransfer:%{time_starttransfer}\ntime_total:%{time_total}\n" -o /dev/null -s URL. time_connect reflects TCP handshake and network latency, time_starttransfer is the time to the first byte (server processing plus TTFB), and time_total is the total. Comparing the phases tells you which segment is slow. A short form is curl -w '%{time_total}\n' -o /dev/null -s URL.
Official References
Each command links to its official documentation below, so you can verify the latest usage and read deeper.
Maintained by LaoHand
Publicly updated on Jul 21, 2026, continuously proofread against official docs.
Contact Us
Wrong command or description? Send us corrections, business inquiries or product feedback by email.
Contact Us