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.

PrefixMeans
*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.

FlagOutput
--trace <f>Hex plus ASCII dump of everything, - for stdout
--trace-ascii <f>Same without the hex column, far more readable
--trace-timeTimestamp every trace line
--trace-idsPrefix 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 Authorization header, 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.

ExitLayerFirst thing to check
3URL parsingUnencoded space or brace, try -g
6DNSdig +short host, or --resolve to bypass
7TCPWrong port, firewall, or the proxy is unreachable
22HTTP statusOnly with -f, use --fail-with-body to see why
28Timeout--connect-timeout vs --max-time, which one fired
35TLS handshakeVersion or cipher mismatch, try --tlsv1.2
47Redirect loop--max-redirs, inspect with -IL
52Empty replyServer closed early, often a protocol mismatch
56Receive failureConnection reset mid-transfer
60Certificate--cacert, clock skew, or wrong hostname
92HTTP/2 stream errorRetry 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/