curl Troubleshooting Cheatsheet
Examples use example.com and documentation IPs; swap in your target.
| Symptom | Goal | Command | Key details |
|---|---|---|---|
| First look | Headers only | curl -I https://example.com/ |
HEAD request; some apps mishandle HEAD, so use -sS -D - -o /dev/null to test the real GET |
| See the wire | curl -v -o /dev/null https://example.com/ |
* info, > sent, < received; output goes to stderr |
|
| Status only | curl -sS -o /dev/null -w '%{response_code}\n' https://example.com/ |
-sS hides progress but keeps errors |
|
| Readable JSON body | curl -sS https://api.example.com/users/42 | jq . |
-sS keeps the progress meter out of the pipe |
|
| Follow redirects | curl -L http://example.com/ |
Exit 47 = too many redirects |
|
| Connection | Is the port open? | curl -v telnet://db.example.com:5432 |
"Connected to" = TCP is reachable; no nc or telnet needed. Exit 7 = connect failed |
| Which backend answered? | curl -sS -o /dev/null -w '%{remote_ip}:%{remote_port} %{response_code}\n' https://example.com/ |
Shows where a load balancer, DNS round-robin, or VIP sent you | |
| Bound the request | curl --connect-timeout 3 --max-time 10 https://example.com/ |
--connect-timeout limits connection setup; --max-time the whole request. Exit 28 = timeout |
|
| Slow / flapping | Phase timing | curl -sS -o /dev/null -w 'dns=%{time_namelookup} conn=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer} total=%{time_total}\n' https://example.com/ |
Cumulative times: high dns → resolver; conn−dns → network/firewall; tls−conn → handshake; ttfb−tls → server |
| Watch over time | while true; do curl -sS -o /dev/null -w '%{response_code} %{time_total}\n' https://example.com/health; sleep 1; done |
Catches rollouts, intermittent 5xx, and failovers | |
| DNS / routing | Pin host to IP | curl --resolve 'example.com:443:203.0.113.10' https://example.com/health |
Keeps SNI, cert check, and Host; exit 6 = DNS failed |
| Send to another backend | curl --connect-to 'example.com:443:backend.internal:8443' https://example.com/health |
Like --resolve, but can also change the port |
|
| Hit an IP with a Host header | curl -H 'Host: example.com' http://203.0.113.10/ |
Plain-HTTP virtual-host test; for HTTPS use --resolve so SNI matches |
|
| IPv4 vs IPv6 | curl -4 https://example.com/ / curl -6 https://example.com/ |
Isolates a broken address family | |
| TLS | Cert subject / expiry | curl -sv -o /dev/null https://example.com/ 2>&1 | grep -E 'subject:|issuer:|expire date:' |
Exit 60 = cert verification failed; exit 35 = handshake failed |
| Private CA | curl --cacert ./internal-ca.pem https://service.internal/ |
Verifies the server with this PEM CA bundle | |
| Mutual TLS | curl --cert ./client.crt --key ./client.key --cacert ./ca.pem https://mtls.example.com/health |
Sends a client certificate | |
| Exact TLS version | curl --tlsv1.2 --tls-max 1.2 https://example.com/ |
--tlsv1.2 alone means 1.2 or newer |
|
| Confirm a cert issue | curl -k https://dev.example.com/ |
Diagnosis only, never the fix | |
| HTTP version | Compare | curl --http1.1 https://example.com/ / curl --http2 https://example.com/ |
Exit 16 / 92 = HTTP/2 error |
| Proxy | Through a proxy | curl -x http://proxy.example.com:8080 https://example.com/ |
Also reads http_proxy, HTTPS_PROXY, NO_PROXY |
| Bypass all proxies | curl --noproxy '*' https://example.com/ |
Overrides the env vars | |
| SOCKS / SSH tunnel | curl --socks5-hostname 127.0.0.1:1080 https://internal.example.com/ |
The proxy resolves the hostname, e.g. through ssh -D 1080 jumphost |
|
| Local / container | Unix socket | curl --unix-socket /var/run/docker.sock http://localhost/version |
For Docker, containerd, and local daemons |
| API request | Reproduce a browser request | DevTools → Network → right-click → Copy as cURL | Exact headers, cookies, and body; strip secrets before sharing |
| Any method, JSON body | curl -X PATCH --json '{"name":"Alice","active":true}' https://api.example.com/users/42 |
-X sets the method name only (PUT, PATCH, …); without it a body means POST. --json adds the body plus Content-Type/Accept headers (curl 7.82+). Use -I, not -X HEAD, which hangs |
|
| Any method, form body | curl -X PUT --data-urlencode 'name=Alice' --data-urlencode 'note=hello & bye' https://api.example.com/users/42 |
URL-encodes each field; Content-Type: application/x-www-form-urlencoded |
|
| GET with query | curl -G --data-urlencode 'q=distributed systems' --data-urlencode 'page=2' https://api.example.com/search |
-G moves the data into the query string |
|
| Delete | curl -X DELETE https://api.example.com/users/42 |
No body needed | |
| Basic auth | curl -u alice https://api.example.com/private |
Prompts for the password; exit 67 = auth rejected |
|
| Bearer token | curl -H "Authorization: Bearer $API_TOKEN" https://api.example.com/private |
Token comes from an env var, not the command history | |
| CORS preflight | curl -i -X OPTIONS -H 'Origin: https://app.example.com' -H 'Access-Control-Request-Method: POST' https://api.example.com/users |
Check the Access-Control-Allow-* response headers |
|
| Fail on 4xx/5xx | curl --fail-with-body https://example.com/missing |
curl 7.76+. Without it, a 404 or 500 still exits 0; with it, exit 22 |
|
| Feature missing | Check build | curl --version |
Lists protocols, TLS backend, and features |
Read other posts
Comments