GET Requests
| Command | What it does |
|---|---|
| GET and print the body | |
| GET on a non-default port | |
| Follow redirects | |
| Silent: no progress meter | |
| Silent, but still print errors | |
| Fail with exit code 22 on HTTP 4xx and 5xx instead of printing the error page | |
| The combination scripts want | |
| Ask for JSON | |
| Set the User-Agent | |
| Set the Referer | |
| Send a cookie | |
| Save cookies from the response to a file | |
| Send cookies from that file on the next call | |
| Ask for gzip and decode it | |
| Pretty-print JSON with jq | |
| Several URLs in one command |
POST and JSON
implies POST and sets , so JSON needs the header.
curl -X POST https://api.example.com/items \
-H "Content-Type: application/json" \
-d '{"name":"widget","qty":1}'| Command | What it does |
|---|---|
| Form POST (what an HTML form sends) | |
| JSON body from a file | |
| curl 7.82 and newer: sets Content-Type and Accept for you | |
| Same, from a file | |
| PUT | |
| PATCH | |
| DELETE | |
| POST an XML body from a file | |
| POST with an empty body | |
| Send a value starting with without reading a file | |
| Send bytes exactly as they are ( strips newlines) |
Headers
| Command | What it does |
|---|---|
| Add a header | |
| Several headers: repeat | |
| Test a virtual host before DNS points at it | |
| Pin a hostname to an IP; keeps TLS and SNI correct, unlike the Host trick | |
| Remove a default header (name with an empty value) | |
| Send a header with an empty value (trailing semicolon) | |
| Headers from a file, one per line (curl 7.55 and newer) | |
| Fetch compressed without decoding |
Authentication
| Command | What it does |
|---|---|
| Bearer token | |
| Same, shorter | |
| Basic auth | |
| Basic auth, prompt for the password so it stays out of history | |
| API key in a header | |
| API key as a query parameter (quote the URL) | |
| Digest auth | |
| NTLM, for IIS and Windows proxies | |
| Credentials from () | |
| Client certificate (mutual TLS) |
Downloads
| Command | What it does |
|---|---|
| Save with the file name from the URL | |
| Save under a name you choose | |
| Follow redirects, then save (GitHub release assets) | |
| Use the name from the header | |
| Resume a partial download | |
| Progress bar instead of the meter | |
| Several files | |
| Numeric range | |
| Create the target directories | |
| Throttle to 1 MB/s | |
| Only download if newer than the local copy | |
| Discard the body (timing, status checks) | |
| The install-script pattern; read the script first |
Uploads
| Command | What it does |
|---|---|
| multipart/form-data upload, what a browser file field sends | |
| Set the MIME type and add a text field | |
| Upload under a different name | |
| PUT the file as the raw request body | |
| Trailing slash: PUT to the URL plus the file name | |
| Several files | |
| FTP upload | |
| SFTP upload (curl built with libssh2; lists sftp if so) | |
| Raw bytes in a POST |
Response Headers and Status Codes
| Command | What it does |
|---|---|
| HEAD request: headers only | |
| Headers followed by the body | |
| Headers of a real GET, for servers that treat HEAD differently | |
| Save the headers to a file | |
| Just the status code | |
| Final code and URL after redirects | |
| Total time in seconds | |
| Which address it actually connected to | |
| Bytes received | |
| Read the format from a file |
Where the time goes:
curl -s -o /dev/null -w "dns %{time_namelookup}s connect %{time_connect}s tls %{time_appconnect}s first byte %{time_starttransfer}s total %{time_total}s code %{http_code}\n" https://example.comVerbose and Debugging
| Command | What it does |
|---|---|
| Show the request (), response headers (), and connection and TLS details () | |
| Verbose without the body in the way | |
| Only the headers from the verbose output | |
| Everything on the wire, including bodies | |
| Same, to a file with timestamps | |
| Exit 22 on HTTP errors but still print the body (curl 7.76 and newer) | |
| Force HTTP/1.1 (, for the others) | |
| Force IPv4 ( for IPv6) | |
| Version, TLS backend, and supported protocols | |
| Print the exit code and error text (curl 7.75 and newer) |
Copy as cURL
Browsers and API clients export a request as a curl command, which is the quickest way to reproduce a call that works in one place and fails in another.
| Source | How to get the curl command |
|---|---|
| Chrome, Edge | Network tab, right-click the request, Copy, then Copy as cURL |
| Firefox | Network tab, right-click, Copy Value, Copy as cURL |
| Safari | Network tab, right-click the request, Copy as cURL |
| Postman | The code snippet button, then cURL |
| Insomnia, Bruno | Right-click the request, Copy as cURL |
| Swagger UI | Try it out, Execute, and the command appears above the response |
| Windows | Copy as cURL (cmd), because the bash version's single quotes break in cmd.exe |
An exported command carries your cookies and header, so strip those lines before pasting it into a ticket or an online converter. goes the other way and writes the equivalent libcurl C source.
Certificates and -k
| Command | What it does |
|---|---|
| Skip certificate verification (); for testing, not for scripts | |
| Trust a private CA and keep verification on | |
| Use a directory of CA certificates | |
| Certificate chain, issuer, and dates in the lines | |
| 0 means the chain verified | |
| Require a stapled OCSP response | |
| Minimum TLS 1.2 ( caps it, for testing old servers) | |
| Windows only: skip the revocation check that fails behind some proxies | |
| Mutual TLS with a private CA |
Exit code 60 means the chain did not verify. The SSL checker shows what the server is sending, including a missing intermediate, and the certificate decoder reads a you already have.
Proxies
| Command | What it does |
|---|---|
| HTTP proxy | |
| Proxy with credentials ( also works) | |
| SOCKS5 proxy that also resolves DNS; pairs with from the SSH cheat sheet | |
| SOCKS5 with local DNS | |
| Ignore the proxy environment variables | |
| Tunnel through the proxy with CONNECT () | |
| Environment variables curl honours (, too) |
prints when one of those variables is set, which explains many mystery failures.
Query Parameters
| Command | What it does |
|---|---|
| Quote URLs with , or the shell backgrounds the command | |
| Encode a value and send it as a query string | |
| Build a query string from pairs instead of a POST body | |
| Same as , curl 7.87 and newer | |
| URL-encode a POST field | |
| URL-encode a file's contents into a field | |
| Brace expansion fetches three URLs | |
| Turn globbing off so and are sent literally |
Timeouts and Retries
| Command | What it does |
|---|---|
| Give up on connecting after 5 seconds | |
| Cap the whole transfer at 10 seconds (exit code 28) | |
| Retry transient failures (timeouts, 5xx) up to 3 times | |
| Wait 2 seconds between retries instead of backing off | |
| Retry on every error, including connection refused | |
| Treat connection refused as transient | |
| Abort if slower than 1000 bytes/s for 30 seconds | |
| TCP keepalive probe interval ( turns it off) | |
| No output buffering; use for server-sent events and streaming logs |
Connectivity Checks
| Command | What it does |
|---|---|
| Is the site answering | |
| Is a TCP port open: look for (Ctrl+C to leave) | |
| Quick port probe over HTTP; an SSH banner or means the port is open | |
| Which IP the name resolved to | |
| Test a new server before changing DNS | |
| Send from a specific local address or interface | |
| Your public IPv4 address | |
| Headers and status without the body |
For a port you cannot reach from your machine, the SSH port checker tests it from outside your network.
Exit Codes
| Code | Meaning | Usual cause |
|---|---|---|
| Success | The transfer finished; HTTP 500 still counts unless you used | |
| Unsupported protocol | Typo like , or a build without that protocol | |
| Malformed URL | Unquoted spaces or brackets | |
| Could not resolve proxy | Bad or proxy environment variable | |
| Could not resolve host | DNS is down, the name is wrong, or there is no network | |
| Failed to connect | Port closed, service down, or a firewall refused it | |
| Partial file | The connection dropped mid-download; retry with | |
| HTTP error | A 4xx or 5xx with | |
| Write error | Disk full, or no permission on the path | |
| Read error | The , , or file is missing or unreadable | |
| Timeout | or hit, often a silent firewall | |
| TLS handshake failed | HTTPS to an HTTP port, protocol or cipher mismatch, Windows revocation check | |
| Too many redirects | A redirect loop with ( caps it) | |
| Empty reply | The server closed the connection without responding | |
| Send failure | Connection dropped while uploading | |
| Receive failure | Connection reset by the server, proxy, or load balancer | |
| Certificate not verified | Self-signed, expired, private CA, missing intermediate, wrong clock | |
| CA bundle problem | Bad path or a missing system CA store |
shows the code right after curl exits. Codes are documented in under EXIT CODES.
Windows and PowerShell
| Situation | Do this |
|---|---|
| Windows PowerShell 5.1 | is an alias for ; type to get real curl |
| PowerShell 7 | The alias is gone; runs curl.exe |
| cmd.exe | is real curl on Windows 10 1803 and later |
| JSON in cmd.exe | No single quotes: |
| JSON in PowerShell 7.3 and newer | works as in bash |
| JSON in older PowerShell | Inner quotes get stripped; write |
| Line continuation | in cmd.exe, a backtick in PowerShell, not |
| Certificate errors (exit 35 or 60) | Windows curl uses Schannel and the Windows certificate store; if the revocation check fails behind a proxy |
| Git Bash | Ships its own curl with OpenSSL and its own CA bundle, so results can differ from cmd.exe |
Install curl
usually means a minimal image rather than a broken machine. Alpine and the Debian slim images ship without curl, which is why it fails inside containers and CI jobs that work fine on your laptop.
| Command | What it does |
|---|---|
| Debian, Ubuntu | |
| Fedora, RHEL, Rocky, Alma | |
| Alpine, the usual Docker case | |
| Arch | |
| macOS; Homebrew keeps it keg-only, so still wins on | |
| Windows, if the bundled is missing | |
| Check whether it is installed and which copy you are running |
Gotchas
- already means POST. Adding is harmless alone, but with forces POST on the redirect target too, where curl would normally switch to GET.
- sends HEAD, and some servers answer HEAD with 405 or different headers. shows the headers of a real GET.
- Quote every URL that contains , , , , or . Unquoted, the shell backgrounds the command at and curl globs the brackets. In zsh the URL never even reaches curl: you get instead.
- A curl that hangs and prints nothing is usually waiting on the connection, not on the server. turns the wait into exit code 28, and shows whether it is stuck at DNS, TCP, or TLS. On a streaming endpoint the opposite is true: the data is arriving but buffered, and releases it.
- strips newlines from the file. sends it byte for byte.
- makes the error go away, not the problem. Find the cause with the SSL checker and fix the chain or add .
- is visible in shell history and . Use to be prompted, or with a .
- overwrites an existing file without asking, and lets the server choose the file name.
- A that works in the terminal but not in cron or CI is usually missing a proxy variable, a , or the CA bundle the shell had.