Vibe Engines
YouTube

Vibe Engines is free, interactive AI engineering & system design — runs in your browser. Pick a learning path

curl Quick Reference

Talk to any HTTP API from the terminal — headers, bodies, auth, downloads, and the flags that make debugging painless.

curl sends a request and prints the response body to stdout, nothing more unless you ask. Every flag below adjusts that: -i, -v and -w show more of the exchange, -s shows less, -o sends the body elsewhere, and -d and -H shape what goes out. Two defaults catch people: curl does not follow redirects without -L, and it exits 0 on a 404 or 500 unless you add -f, because the transfer itself worked.

Short flags bundle (-fsSL). Run curl --version before trusting a newer flag: --json needs 7.82 or later.

Requests & bodies

curl URL
GET is the default; quote any URL containing & or ? so the shell leaves it alone
-X PUT · -X DELETE
set the method (also PATCH); -d already implies POST, so -X POST is redundant there
-d 'a=1&b=2'
send a body as POST, form-encoded by default — set a JSON Content-Type yourself
--json '{"a":1}'
POST JSON with Content-Type and Accept set (7.82+); older curl: -H plus -d
-d @body.json
body from a file, @- from stdin; strips newlines — --data-binary sends bytes as-is
--data-binary @file
send the file byte-for-byte, newlines kept — for NDJSON bulk APIs and signed payloads
-F file=@photo.png
multipart upload; add text fields with -F "name=value"
-T file.zip URL
upload a file with PUT — e.g. to a presigned S3 URL
-G --data-urlencode
URL-encode "q=a b" into the query string of a GET

Headers & auth

-H "Name: value"
add a header (repeatable); -H "Accept:" removes a default, -H @file reads them from a file
--oauth2-bearer "$TOKEN"
sends Authorization: Bearer … — same as writing the header by hand
-u user:pass
Basic auth; -u user alone prompts for the password, keeping it out of history
-n
take credentials from ~/.netrc so no secret appears on the command line
-b "session=abc"
send cookies; -b jar.txt reads a cookie file
-c jar.txt
save cookies the server sets; -b jar.txt -c jar.txt keeps a session going
-A "my-agent/1.0"
set the User-Agent; some APIs reject requests without a descriptive one
--aws-sigv4
sign AWS-style, e.g. "aws:amz:us-east-1:s3" with -u KEY:SECRET (7.75+)

Inspect & debug

-i
show response headers, then the body
-I
HEAD request, headers only; some servers answer HEAD differently from GET
-v
request and response headers plus the TLS handshake, on stderr
--trace-ascii -
everything on the wire, request body included; add --trace-time for timestamps
-sD - -o /dev/null
print a real GET’s headers and discard the body — for when HEAD lies
-w '%{http_code}\n'
print the status after the transfer; with -s -o /dev/null it is the only output
-w '%{time_total}'
also time_namelookup, time_connect, time_appconnect (TLS), time_starttransfer (TTFB)
-w @format.txt
read the write-out template from a file — a reusable timing report
-w '%{json}'
every write-out variable as one JSON object (7.70+); pipe it to jq
-sS
-s silences progress and errors; adding -S brings error messages back

Downloads & output

-o out.zip · -O
save under a name you choose · under the URL’s own file name
-OJ
use the file name from the server’s Content-Disposition header
-L
follow redirects; POST becomes GET after 301/302/303, and credentials stay with the first host
-C - -O URL
resume an interrupted download from where the partial file stops
-r 0-1023
fetch a byte range; the server answers 206 if it supports ranges
--compressed
ask for gzip or brotli and decompress automatically, as browsers do
--create-dirs -o a/b/f
create missing directories for the output path
-Z URL1 URL2
transfer in parallel (7.66+); cap concurrency with --parallel-max
--limit-rate 1M
throttle bandwidth — simulate slow clients or go easy on a server

Scripts: fail, retry, time out

-f
HTTP 400+ exits with code 22 and no body; --fail-with-body keeps the body (7.76+)
curl -fsSL URL
the install-script idiom: fail on errors, quiet, still show errors, follow redirects
--retry 3
retry timeouts, 408, 429 and 5xx with doubling backoff; --retry-delay fixes the wait
--retry-all-errors
retry on any failure, e.g. connection refused (7.71+); safe only for idempotent calls
-m 30
cap the whole transfer at 30 seconds — without it a stuck request can hang forever
--connect-timeout 5
cap only the connection phase; pair it with -m
--retry-max-time 60
stop retrying once 60 seconds have passed in total

TLS, DNS & proxies

-k
skip certificate verification — for debugging only, never in a production script
--cacert ca.pem
trust a private CA instead of turning verification off
--cert c.pem --key k.pem
present a client certificate for mutual TLS
--resolve h:443:1.2.3.4
pin a host to an IP for one request; SNI and certificate checks still use the name
-x http://proxy:3128
route through a proxy; curl also reads HTTPS_PROXY and NO_PROXY
--http1.1 · --http2
force a protocol version; --http3 needs a curl built with HTTP/3
-4 · -6
force IPv4 or IPv6 — isolates bugs that only appear on one stack
--unix-socket PATH
speak HTTP over a Unix socket, e.g. Docker’s /var/run/docker.sock

Recipes

Call a JSON API with a token and pretty-print the result

TOKEN=$(cat ~/.config/myapi/token)
curl -fsS --json '{"name":"demo"}' -H "Authorization: Bearer $TOKEN" https://api.example.com/v1/items | jq .

--json sets both JSON headers and implies POST (7.82+); -f turns a 4xx or 5xx into a non-zero exit, so a CI step fails instead of piping an error page into jq.

Break down where a slow request spends its time

curl -s -o /dev/null -w 'dns      %{time_namelookup}\nconnect  %{time_connect}\ntls      %{time_appconnect}\nttfb     %{time_starttransfer}\ntotal    %{time_total}\n' https://example.com

Each figure is seconds since the start, so TTFB minus TLS is roughly the server’s think time. Large connect or TLS numbers point at the network, not the app.

Test a new server before DNS points at it

curl -sv -o /dev/null --resolve app.example.com:443:203.0.113.10 https://app.example.com/healthz

--resolve sends the request to the new IP but keeps SNI and certificate checks for the real hostname, which curling the bare IP cannot do.

Download a release reliably in a script

curl -fL --retry 5 --connect-timeout 10 -m 600 -o app.tar.gz https://example.com/app.tar.gz
echo "EXPECTED_SHA256  app.tar.gz" | sha256sum -c -

Retries absorb transient network errors and 5xx responses, and the checksum proves the bytes are right. On macOS use shasum -a 256 -c - instead.

Check a list of URLs and print their status codes

while read -r url; do
  code=$(curl -s -o /dev/null -m 10 -w '%{http_code}' "$url")
  echo "$code $url"
done < urls.txt

A 000 means no HTTP response arrived at all: a DNS, connection or timeout failure.

Questions about curl Cheat Sheet

How do I send a POST request with JSON using curl?

On curl 7.82 or newer, pass the body to --json, for example curl --json '{"name":"demo"}' https://api.example.com/items. It sets Content-Type and Accept to application/json and implies POST. On older versions, combine -H 'Content-Type: application/json' with -d and the body. Keep the JSON in single quotes so the shell leaves it alone, or store it in a file and pass @body.json. Note that -d strips newlines when it reads a file, which is harmless for JSON but breaks NDJSON, so use --data-binary when line breaks matter.

Why does curl exit with code 0 on a 404 or 500?

Because from curl’s point of view the transfer succeeded: it connected, sent the request and received a complete response. HTTP status codes are the server’s business. Add -f (or --fail) and curl exits with code 22 for any status of 400 or above and prints no body, which is what scripts and CI steps need. If you want the error body too, use --fail-with-body on curl 7.76 or newer. To branch on the exact status, capture it with -w '%{http_code}' and compare it in the shell.

How do I see the request and response headers with curl?

Use -v to print the whole exchange on stderr: the connection and TLS handshake, every request header curl sends and every response header. Use -i to put the response headers in the normal output, ahead of the body. -I sends a HEAD request and prints only headers, but some servers answer HEAD differently from GET, so -s -D - -o /dev/null is the reliable way to see a real GET’s headers. When you also need the request body and the exact bytes, --trace-ascii - writes everything on the wire to stdout.

What is the difference between curl -d and -F?

Option -d sends the data as the raw request body, by default labeled application/x-www-form-urlencoded, the format of a plain HTML form; it is also how you send JSON once you set the content type. Option -F builds a multipart/form-data body, the format browsers use for file uploads: each -F adds one part, and name=@file attaches a file with its own filename and type. Use -d for APIs and simple forms, and -F when the server expects an upload field. Both imply POST unless you change the method.