curl Cheatsheet
Debugging
Use this curl reference while you build software engineering projects, review code, or refresh the syntax you reach for most.
-v, the first move
-v / --verbose prints the protocol exchange to stderr, so it does not
pollute a piped body. Read the prefixes and most problems are obvious.
| Prefix | Means |
|---|---|
* | curl's own commentary: DNS, connect, TLS, connection reuse |
> | A request line or header curl sent |
< | A response line or header curl received |
{ } | Data payload sizes on an HTTP/2 or HTTP/3 stream |
curl -v https://example.com 2>&1 | head -30 curl -v https://example.com 2>trace.txt -o body.html # split the streams curl -v https://example.com 2>&1 | grep '^[<>]' # headers only curl -sv https://example.com 2>&1 >/dev/null | grep -i '^< server'
-v shows headers but never bodies. -s and -v compose fine, -s only silences
the progress meter.
Full traces
When the bug is in the bytes, -v is not enough. The trace options dump payloads
too, including the multipart boundary and the exact JSON you sent.
| Flag | Output |
|---|---|
--trace <f> | Hex plus ASCII dump of everything, - for stdout |
--trace-ascii <f> | Same without the hex column, far more readable |
--trace-time | Timestamp every trace line |
--trace-ids | Prefix each line with transfer and connection ids |
--trace-config <list> | Enable named trace components (curl 8.3.0) |
--libcurl <f> | Emit equivalent libcurl C source |
curl --trace-ascii - -d '{"a":1}' https://ex.com/v1 | head -40 curl --trace-ascii trace.txt --trace-time https://example.com curl --trace-config ids,time,http/2 https://example.com curl --libcurl repro.c -d 'a=1' https://ex.com/v1
--trace-config is mutually exclusive with -v and --trace, so pick one.
--libcurl is the fastest way to turn a working command line into code.
Traces contain your
Authorizationheader, your cookies, and your request body verbatim. Scrub them before pasting into an issue or a ticket.
Reading the failure
Exit code plus the last * line usually identifies the layer that broke.
| Exit | Layer | First thing to check |
|---|---|---|
| 3 | URL parsing | Unencoded space or brace, try -g |
| 6 | DNS | dig +short host, or --resolve to bypass |
| 7 | TCP | Wrong port, firewall, or the proxy is unreachable |
| 22 | HTTP status | Only with -f, use --fail-with-body to see why |
| 28 | Timeout | --connect-timeout vs --max-time, which one fired |
| 35 | TLS handshake | Version or cipher mismatch, try --tlsv1.2 |
| 47 | Redirect loop | --max-redirs, inspect with -IL |
| 52 | Empty reply | Server closed early, often a protocol mismatch |
| 56 | Receive failure | Connection reset mid-transfer |
| 60 | Certificate | --cacert, clock skew, or wrong hostname |
| 92 | HTTP/2 stream error | Retry with --http1.1 to isolate |
curl -sS https://ex.com/ ; echo "exit=$?" curl -so /dev/null -w '%{onerror}%{exitcode}: %{errormsg}\n' https://ex.com/
Isolating the layer
Change one variable at a time. This sequence narrows almost any curl failure.
# 1. does the name resolve, and to what dig +short api.example.com # 2. is the port open, independent of TLS curl -v telnet://api.example.com:443 --connect-timeout 3 # 3. is it TLS or HTTP curl -vk https://api.example.com/ 2>&1 | grep -E 'SSL|ALPN|subject' # 4. is it the protocol version curl -sI --http1.1 https://api.example.com/ | head -1 # 5. is it DNS or the server, point at the IP but keep the name curl --resolve api.example.com:443:203.0.113.10 https://api.example.com/ # 6. is it something in your config curl -q -v https://api.example.com/
-q as the first argument skips .curlrc, which is the check to run whenever
a command behaves differently on your machine than on someone else's.
Common surprises
- Empty output. Almost always a redirect you did not follow. Add -L.
- curl: (3) URL rejected. A space or [/{ in the URL. Quote it and add
-g to turn globbing off.
- A 415 or a mangled body on a JSON POST. -d sets a form content type. Use
--json or an explicit -H 'Content-Type: application/json'.
- Corrupted binary upload. -d @file strips newlines. Use --data-binary @file.
- Exit 0 on a 500. curl only fails on status with -f or --fail-with-body.
- Redirected POST arrives empty. curl downgrades to GET on 301, 302, and 303.
Use --post301 and friends, not -X POST.
- Cookies vanish. The engine is off unless -b names a file or -c is given.
- The command works in one shell and not another. Single versus double quoting
of the JSON body, or a stale .curlrc. Reproduce with -q.
- Progress meter corrupts a pipe. Use -s for scripts and -sS when you still
want error text.
Comparing against a known good server
httpbin style echo endpoints show you what actually arrived, which settles
arguments about headers and encoding faster than reading traces.
curl -s -F 'f=@a.png' -F 'n=ada' https://httpbin.org/post | jq '.files,.form' curl -s -H 'X-Debug: 1' https://httpbin.org/headers | jq . curl -s --json '{"a":1}' https://httpbin.org/post | jq '.json,.headers' curl -s https://httpbin.org/status/503 -w '%{http_code}\n' -o /dev/null
For a local target, nc -l 8080 prints the raw request bytes with no server
involved, which is the ground truth when you suspect curl and the server disagree
about what was sent.
nc -l 8080 & curl -s --json '{"a":1}' http://localhost:8080/