curl
Transfer data to and from a server
curl sends an HTTP (or FTP, or a dozen other protocols') request and prints or saves whatever
comes back. It's the tool behind "let me just check what this API returns," behind download
scripts, behind health checks, and behind debugging why a webhook isn't firing.
The mental model: build a request, read a response
Every curl invocation assembles a request from pieces (method, URL, headers, body), sends
it, and by default prints the response body to stdout. Everything else (status line,
response headers, timing, progress) is opt-in via flags:
curl https://example.com # GET, print the body
curl -I https://example.com # HEAD, print only the response headers
curl -i https://example.com # GET, print headers AND body
curl -v https://example.com # GET, print the whole conversation (connect, TLS, headers, body)
-i/-I/-v are about what curl shows you, not what request it sends. A common
confusion is meaning "make a HEAD request" but reaching for -i.
GET is implicit; other methods usually aren't
Without -X, curl sends GET, unless you give it a request body with -d/--data or
-F/--form, in which case it switches to POST automatically. This is why you'll see
curl -X POST url -d "..." written both with and without the explicit -X POST. The flag is
often redundant but harmless, and worth keeping for readability. -X is not redundant for
PUT or DELETE, which curl never infers.
-d vs -F: two different request bodies
-d/--datasendsapplication/x-www-form-urlencodeddata (or literally whatever string you give it, if you setContent-Typeyourself for a JSON body).-F/--formsendsmultipart/form-data, the format browsers use for<form>uploads, and the one you need for actually uploading a file (-F "file=@report.pdf").
Mixing these up is the single most common reason a "curl works but the server doesn't seem to see the data" bug happens. Check whether the endpoint expects urlencoded or multipart.
Exit codes tell scripts what actually happened
By default, curl exits 0 even for a 404 or 500 response. As far as curl is concerned,
it successfully transferred something. Two flags change that:
-f/--failmakes curl exit non-zero (22) on HTTP error status codes, instead of printing the error page and exiting0.-m/--max-timebounds the whole operation; on timeout curl exits28.
A script that checks curl's exit code without --fail is almost always checking the wrong
thing. See Exit codes and error handling.
Redirects don't follow themselves
By default curl prints whatever the server sends back for a 3xx, including a redirect's
near-empty body, without ever requesting the new location. Add -L / --location to follow
redirects automatically. This trips up more people than it should, because the failure mode
looks like "the page is just... blank" rather than an obvious error.
Getting machine-readable facts out of a response
-w / --write-out prints values from a template string after the transfer completes: status
code, total time, effective URL after redirects, and more, without you having to parse curl's
normal output:
curl -s -o /dev/null -w "%{http_code}\n" https://example.com
-o /dev/null throws away the body, -s silences the progress meter, and -w prints just the
one number you asked for. This pattern, discard the body and print one templated fact, is the
backbone of most health-check and monitoring scripts built on curl.
Saving output
-o file saves to a name you choose; -O saves using the remote URL's own filename (which
means the URL needs an actual filename in its path; -O on https://example.com/ with no
path fails, since there's nothing to name the file). -C - resumes an interrupted download
instead of restarting from zero, when the server supports range requests.
Basic requests
GET is the default. This section covers what curl shows you by default and how to see more.
Fetch a URL and print the body
curl https://example.com
With no flags, curl sends a GET request and prints the response body to stdout.
Show output
<!doctype html><html><head><title>Example Domain</title>...
Fetch only the response headers
curl -I https://example.com
`-I` (`--head`) sends a HEAD request, same as GET but the server doesn't send a body, so you get headers only, fast.
Show output
HTTP/2 200
date: Sun, 05 Jul 2026 15:43:10 GMT
content-type: text/html
server: cloudflare
last-modified: Wed, 01 Jul 2026 17:52:37 GMT
allow: GET, HEAD
Print response headers AND body
curl -i https://httpbin.org/get
`-i` (`--include`) keeps the GET request but includes the response headers above the body in the output.
Show output
HTTP/2 200
content-type: application/json
content-length: 255
{
"args": {},
Silence the progress meter
curl -s https://example.com -o page.html
`-s` (`--silent`) suppresses the progress bar, useful in scripts where you don't want that noise in logs.
Show both a response and any error, quietly
curl -sS https://example.com -o page.html
`-sS` keeps `-s`'s quiet progress but restores error messages `-s` alone would also hide. The combination you want in almost every script.
Saving output
Choosing a filename, or letting curl pick one.
Save the response body to a named file
curl -o page.html https://example.com
`-o` (`--output`) writes the body to the filename you give it, instead of stdout.
Save using the remote file's own name
curl -O https://httpbin.org/robots.txt
`-O` (`--remote-name`) names the local file after the last path segment of the URL: here, `robots.txt`.
Show output
User-agent: *
Disallow: /deny
Resume an interrupted download
curl -C - -o robots.txt https://httpbin.org/robots.txt
`-C -` asks curl to figure out how much of the local file already exists and resume from there, if the server supports range requests. Avoids re-downloading a large file from scratch after a dropped connection.
Status codes and scripting
Making curl's exit code and output actually reflect success or failure.
Print only the HTTP status code
curl -s -o /dev/null -w "%{http_code}\n" https://example.com
Discard the body (`-o /dev/null`), silence the progress meter (`-s`), print just the status code (`-w`): the standard one-liner for a health check.
Show output
200
Get a 404 for a page that doesn't exist
curl -s -o /dev/null -w "%{http_code}\n" https://example.com/nonexistent
Without `--fail`, curl treats a 404 as a successfully completed transfer. It just happens to contain an error page.
Show output
404
Make curl itself fail on an HTTP error status
curl --fail -s https://example.com/nonexistent
echo "exit=$?"
`--fail` (`-f`) turns a 4xx/5xx response into a non-zero exit code (`22`) instead of curl reporting success. This is the flag a script's error handling almost always needs.
Show output
exit=22
Print several facts about a transfer at once
curl -sS -w "time_total=%{time_total}s\n" -o /dev/null https://example.com
`-w` accepts any combination of `%{...}` variables in one template string: status code, timing, sizes, effective URL, and more.
Show output
time_total=0.230857s
Redirects
curl doesn't follow redirects unless you tell it to.
See a redirect without following it
curl -s https://httpbin.org/redirect/1 -o /dev/null -w "%{http_code}\n"
Without `-L`, curl reports the redirect's own status code (`302`) and doesn't request the new location.
Show output
302
Follow redirects to the final destination
curl -Ls https://httpbin.org/redirect/1 -o /dev/null -w "%{http_code} %{url_effective}\n"
`-L` (`--location`) follows the `Location` header automatically; `%{url_effective}` shows where curl actually ended up.
Show output
200 https://httpbin.org/get
Sending data: POST, PUT, DELETE
GET is implicit; a request body usually switches curl to POST automatically.
Send form data with POST
curl -X POST https://httpbin.org/post -d "name=deb1"
`-d` sends `application/x-www-form-urlencoded` data; `-X POST` is technically redundant here since `-d` already implies POST, but it's clearer to read.
Show output
{
"form": {
"name": "deb1"
}
}
POST without spelling out -X
curl https://httpbin.org/post -d "name=deb1"
Confirms the point above: `-d` alone is enough to make curl send POST instead of GET.
Show output
{'name': 'deb1'}
Send a JSON body
curl -X POST -H "Content-Type: application/json" -d '{"a":1}' https://httpbin.org/post
Set `Content-Type` yourself and pass raw JSON to `-d`. curl doesn't validate or reformat it, so make sure it's well-formed.
Show output
{'a': 1}
Send a query string alongside a GET request
curl -G --data-urlencode "q=hello world" https://httpbin.org/get
`-G` forces curl back to GET while still using `--data-urlencode` to build and properly escape the query string. Otherwise `-d`'s data would go in the request body instead of the URL.
Show output
{'q': 'hello world'}
Update a resource with PUT
curl -X PUT https://httpbin.org/put -d "status=updated"
Unlike POST, curl never infers PUT. `-X PUT` is required.
Show output
{'status': 'updated'}
Remove a resource with DELETE
curl -X DELETE https://httpbin.org/delete
Same story as PUT: DELETE has to be spelled out with `-X`.
Upload a file as multipart form data
curl -F "file=@robots.txt" https://httpbin.org/post
`-F` (`--form`) sends `multipart/form-data`, the format needed to actually upload a file. `-d` can't do this. The `@` prefix tells curl to read the named file's contents rather than sending the literal string.
Headers and identity
Adding request headers, and reading back what the server received.
Add a custom request header
curl -H "X-Test: yes" https://httpbin.org/headers
`-H` adds one header per flag; repeat it for multiple headers.
Show output
{
"headers": {
"X-Test": "yes"
}
}
Set a custom User-Agent
curl -A "MyScript/1.0" https://httpbin.org/user-agent
`-A` (`--user-agent`) is shorthand for `-H "User-Agent: ..."`. Some APIs reject requests with curl's default user agent, so this comes up more often than you'd expect.
Show output
{
"user-agent": "MyScript/1.0"
}
Authentication and cookies
Basic auth and session cookies.
Authenticate with HTTP Basic auth
curl -u user:pass https://httpbin.org/basic-auth/user/pass
`-u user:pass` sends the `Authorization: Basic ...` header curl builds from the credentials. Never hardcode real credentials in a script; read them from an environment variable or a credentials file instead.
Show output
{
"authenticated": true,
"user": "user"
}
See what an unauthenticated request gets
curl -s -o /dev/null -w "%{http_code}\n" https://httpbin.org/basic-auth/user/pass
Without `-u`, the same endpoint returns `401 Unauthorized`.
Show output
401
Save cookies from a response
curl -c cookies.txt https://httpbin.org/cookies/set/session/abc123
`-c` (`--cookie-jar`) writes any `Set-Cookie` headers to a file in Netscape cookie-file format.
Show output
# Netscape HTTP Cookie File
httpbin.org FALSE / FALSE 0 session abc123
Send cookies from a saved file
curl -b cookies.txt https://httpbin.org/cookies
`-b` (`--cookie`) reads a cookie jar and sends its cookies with the request. Pair with `-c` to persist a session across multiple curl calls, the way a browser would.
Show output
{
"cookies": {
"session": "abc123"
}
}
Timeouts, retries, and TLS
Making curl behave well against a slow, flaky, or self-signed endpoint.
Give up after a fixed time
curl -m 2 https://httpbin.org/delay/5 -o /dev/null
echo "exit=$?"
`-m` (`--max-time`) bounds the entire request; curl exits `28` on timeout. A script should treat that distinctly from a clean HTTP error.
Show output
exit=28
Retry a failing request automatically
curl --retry 2 --retry-delay 1 -o /dev/null -s -w "%{http_code}\n" https://httpbin.org/status/500
`--retry` retries on transient failures and select 5xx responses, waiting `--retry-delay` seconds between attempts. Useful against flaky upstreams, not a substitute for `--fail` in the exit-code check.
Show output
500
Skip TLS certificate verification
curl -sk https://example.com -o /dev/null -w "%{http_code}\n"
`-k` (`--insecure`) disables certificate checks. Fine for a self-signed cert on a box you control during setup, never for anything handling real traffic or credentials.
Show output
200
Watch the full TLS handshake and request
curl -v https://example.com -o /dev/null
`-v` (`--verbose`) prints the connection, TLS handshake, and every header sent and received. The first thing to reach for when a request behaves unexpectedly.
Show output
* Host example.com:443 was resolved.
* IPv4: 104.20.23.154, 172.66.147.243
* Trying 104.20.23.154:443...
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* CAfile: /etc/ssl/certs/ca-certificates.crt
Practical scripting patterns
curl calls that show up inside larger scripts.
Check whether a site is up before proceeding
if curl --fail -s -o /dev/null https://example.com; then
echo "site is up"
fi
`--fail` plus `-s -o /dev/null` reduces the whole request to a plain yes/no exit code, ready for an `if`.
Show output
site is up
Extract one field from a JSON response
curl -s https://httpbin.org/get | python3 -c "import json,sys; print(json.load(sys.stdin)['url'])"
curl doesn't parse JSON itself. Pipe into `python3 -c` (or `jq` if it's installed) to pull out a specific field.
Show output
https://httpbin.org/get
See the public IP a request would come from
curl -s https://icanhazip.com
A quick way to confirm which outbound IP a script, container, or VPN is actually using. Hostnames/IPs shown here are sanitised.
Show output
203.0.113.42