curl is the universal HTTP client: every API debug session, every webhook test, every "is the server up?" check eventually runs through it. The flag surface is huge but you only use a dozen of them daily. This page is the reference for those flags, the JSON / form / multipart body forms, the auth modes, and the safety nets (-f, --max-time, --retry) that turn one-off commands into reliable script primitives.
How do I use curl?
curl sends an HTTP, HTTPS, FTP, SFTP, or one of many other protocol requests and prints the response to stdout. For HTTP, the basic invocation is curl URL (a GET request). Change the method with -X POST / -X PUT / -X DELETE. Send a JSON body with -d '{}' -H 'Content-Type: application/json'. Send form data with -d 'key=value'. Add headers with -H 'Name: Value'. Authenticate with -u user:pass (basic), -H 'Authorization: Bearer TOKEN' (bearer), or --digest (digest). Save the response with -o file (specified name) or -O (use URL's filename). Critical safety flags for scripts: -f (fail on HTTP 4xx/5xx), -sS (silent but show errors), -L (follow redirects), --max-time and --connect-timeout (bound the request). Windows 10 1803+ ships real curl in C:\Windows\System32\curl.exe (the PowerShell curl alias to Invoke-WebRequest only intercepts in the absence of explicit curl.exe).
Jump to:
- Basic requests
- Sending data: JSON, form, multipart
- Headers
- Authentication
- Following redirects and saving output
- Failure and silence: -f, -sS
- Timeouts and retries
- Overriding DNS with --resolve
- Proxies and --socks5
- Useful flag matrix
- Common pitfalls
- What to do next
- FAQ
Basic requests
GET (the default):
curl https://api.example.com/usersNote on Windows: PowerShell aliases curl to Invoke-WebRequest for backwards compat. Always call curl.exe explicitly in PowerShell to get the real binary. Inside cmd.exe and on Windows 10 1803+, curl resolves directly to curl.exe.
POST with a JSON body:
curl -X POST -H 'Content-Type: application/json' -d '{"name":"Ishan"}' https://api.example.com/usersPowerShell's parser needs the inner double quotes escaped. The alternative, much cleaner, is Invoke-RestMethod:
# Linux equivalent already shown above with curlPUT and DELETE follow the same pattern with -X PUT and -X DELETE. HEAD has a dedicated flag, -I:
curl -I https://example.comUseful for checking redirects, content types, cache headers, and HSTS without downloading the body.
Sending data: JSON, form, multipart
-d sends the body. By default it sets Content-Type: application/x-www-form-urlencoded and uses POST. For JSON, set the header explicitly:
curl -X POST -H 'Content-Type: application/json' -d '{"event":"login"}' https://api.example.com/eventsRead body from a file (handy for large JSON payloads):
curl -X POST -H 'Content-Type: application/json' -d @body.json https://api.example.com/eventsURL-encoded form (Content-Type: application/x-www-form-urlencoded):
curl -d 'name=Ishan&role=admin' https://api.example.com/users--data-urlencode handles special characters safely (use one per field):
curl --data-urlencode 'q=hello world' --data-urlencode 'lang=en' https://api.example.com/searchMultipart file upload (Content-Type: multipart/form-data):
curl -F 'file=@./avatar.png' -F 'name=avatar' https://api.example.com/upload-F always uses POST and multipart, regardless of -X. The @ prefix tells curl to read the field value from a file. For arbitrary binary uploads (PUT, no multipart wrapper), use --data-binary @file with the right Content-Type.
Headers
-H 'Name: Value' adds or overrides a single header. Repeat for each header.
curl -H 'Accept: application/json' -H 'X-Request-ID: 1234' https://api.example.com/usersOverride the User-Agent (the default is curl/X.Y.Z):
curl -A 'Mozilla/5.0' https://example.comShow response headers along with the body (verbose mode shows request headers too):
curl -i https://api.example.com/usersFull request and response trace:
curl -v https://api.example.com/users-v is the first thing I add when an API call fails for unclear reasons. It prints every header sent and received plus TLS negotiation details.
Authentication
Basic auth (sends Authorization: Basic base64(user:pass)):
curl -u username:password https://api.example.com/protectedPrompt for the password (does not appear in shell history):
curl -u username https://api.example.com/protectedBearer token (the OAuth / JWT pattern):
curl -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIs...' https://api.example.com/meDigest auth (legacy, rare):
curl --digest -u user:pass https://api.example.com/protectedFor client certificates (mTLS):
curl --cert client.pem --key client.key https://api.example.com/mtlsFollowing redirects and saving output
-L follows redirects (otherwise curl prints the 3xx response and stops):
curl -L https://example.com-o filename saves the response body to a specified path:
curl -o output.html https://example.com-O (capital O) saves using the URL's basename as the filename:
curl -O https://example.com/files/report.pdfCombine with -L for downloads that go through a redirect (CDN, github releases):
curl -LO https://github.com/owner/repo/releases/latest/download/asset.tar.gzResume a partial download with -C -:
curl -C - -O https://example.com/large.isoFailure and silence: -f, -sS
-f makes curl return a non-zero exit code on HTTP 4xx/5xx. Without it, curl exits 0 for any HTTP response, which silently breaks scripts that rely on exit status.
curl -f https://api.example.com/users || echo 'request failed'-s is silent (no progress bar, no errors). -S re-enables error messages. The combination -sS is the standard "quiet but visible failures" mode for scripts:
curl -fsSL https://example.com/install.sh | bash-fsSL is the four-flag combo I use for "download this script and run it" patterns: fail on HTTP error, silent progress, show errors, follow redirects. Without -f, a 404 page would be piped to bash. Without -L, a CDN redirect would download an empty body.
Timeouts and retries
--max-time SECONDS caps the total request time. --connect-timeout SECONDS caps just the initial connection. Both essential in scripts to avoid hangs.
curl --connect-timeout 5 --max-time 30 https://api.example.com/healthRetry on transient failures:
curl --retry 3 --retry-delay 2 --retry-max-time 30 https://api.example.com/healthBy default --retry only retries on transient errors (timeouts, connection refused, some 5xx). Add --retry-all-errors (curl 7.71+) to retry on every failure including 4xx, which is rarely what you want but occasionally useful for stubborn flaky endpoints.
For exponential backoff patterns in shell scripts, the Bash while loop retry pattern gives more control than --retry.
Overriding DNS with --resolve
--resolve HOST:PORT:IP substitutes a DNS lookup for a fixed IP. Useful for testing a new server before DNS cutover, or pinning to a specific origin behind a load balancer.
curl --resolve example.com:443:203.0.113.5 https://example.comcurl sees example.com:443 and uses 203.0.113.5 without hitting DNS, but still sends Host: example.com and validates the TLS cert for example.com. This is the right tool for "is my new server actually serving this hostname before I flip DNS?" tests. Pairs with a DNS health check before and after cutover.
Proxies and --socks5
HTTP/HTTPS proxy:
curl -x http://proxy.internal:8080 https://api.example.com/usersSOCKS5 proxy (matches what an SSH -D 1080 tunnel or Tor's local SOCKS port exposes):
curl --socks5 127.0.0.1:1080 https://example.comFor Tor specifically, the local SOCKS5 port is 9050 by default. See Tor country codes for exit-node selection for how to constrain which exit your traffic flows through, and SOCKS5 proxies with curl, Python, and Node for the equivalent patterns in other languages.
--socks5-hostname (note the suffix) does DNS resolution on the SOCKS side, which is what you almost always want with Tor — otherwise the DNS query leaks to your local resolver:
curl --socks5-hostname 127.0.0.1:9050 https://check.torproject.orgUseful flag matrix
| Flag | Long form | Purpose |
|---|---|---|
-X | --request | HTTP method (GET, POST, PUT, DELETE, ...) |
-d | --data | Request body (URL-encoded form by default) |
-H | --header | Add or override a header |
-u | --user | Basic auth (or digest with --digest) |
-A | --user-agent | Override User-Agent |
-e | --referer | Set Referer header |
-b | --cookie | Send a cookie (or read from file with @filename) |
-c | --cookie-jar | Write received cookies to file |
-L | --location | Follow redirects |
-f | --fail | Exit non-zero on HTTP 4xx/5xx |
-s | --silent | No progress meter or errors |
-S | --show-error | Re-enable errors (use with -s) |
-i | --include | Include response headers in output |
-I | --head | HEAD request (headers only) |
-v | --verbose | Trace request and response |
-o | --output | Save body to a named file |
-O | --remote-name | Save body using URL's basename |
-J | --remote-header-name | Use server's Content-Disposition filename (with -O) |
-C | --continue-at | Resume download (-C - for automatic offset) |
-k | --insecure | Skip TLS verification (debugging only) |
-x | --proxy | HTTP proxy |
--socks5 | SOCKS5 proxy | |
--socks5-hostname | SOCKS5 proxy with remote DNS | |
--resolve | DNS override for a single host | |
--max-time | Total request timeout (seconds) | |
--connect-timeout | Initial connection timeout | |
--retry | Number of retries on transient failure |
Common pitfalls
1. curl URL | bash without -f. A 404 page or a captive-portal HTML gets piped into bash. Always use -fsSL. The -f flag returns non-zero on HTTP error so the pipe never reaches bash.
2. PowerShell aliases curl to Invoke-WebRequest. Running curl --help in PowerShell shows the cmdlet's docs, not curl's. Use curl.exe explicitly in PowerShell. On Windows 10 1803+, the real curl is at C:\Windows\System32\curl.exe.
3. Sending JSON without Content-Type: application/json. curl's -d defaults to application/x-www-form-urlencoded. Many APIs accept the body anyway, but some reject it as malformed form data. Always pair -d with the matching -H 'Content-Type: ...'.
4. Forgetting -X POST is implied by -d. curl -d 'a=1' URL is a POST. Adding -X POST is harmless but redundant. The mistake people make: curl -X GET -d 'a=1' URL sends a GET with a body, which many servers ignore or reject.
5. -L plus auth header. When curl follows a redirect to a different host, it strips the Authorization header by default (a security feature). For cross-host auth survival use --location-trusted. Use sparingly; it leaks credentials to whatever the redirect points at.
6. SOCKS5 DNS leak. --socks5 resolves the hostname locally and sends the IP through the proxy. For Tor or any proxy where you want the destination hostname hidden from your local resolver, use --socks5-hostname.
7. curl exit 0 on 404. Without -f, curl considers any HTTP response a success at the curl level (the network call worked). Scripts that rely on $? to detect API errors must use -f or parse the output.
8. -k in production. -k (or --insecure) skips TLS certificate verification. Useful for testing self-signed certs, dangerous in scripts that run unattended. The right fix is usually --cacert pointing at the actual CA bundle.
What to do next
- grep cheat sheet — for piping curl output through grep to extract specific lines.
- find command cheat sheet — when looking for files to upload with
curl -F. - SSH cheat sheet — for SOCKS5 tunneling and proxying curl traffic through SSH.
- DNS health check — before and after a DNS cutover,
curl --resolveis the natural companion. - Bash for loop — looping curl over a list of URLs or endpoints.
- Bash while loop — retry-with-backoff patterns when
--retrydoes not give you enough control. - Tor SOCKS5 with curl, Python, and Node — using curl through the Tor SOCKS5 port.
- Tor country codes for exit-node selection — pairing curl with Tor for geo-pinned requests.
- External: curl manpage, Everything curl (Daniel Stenberg), PowerShell Invoke-RestMethod.




![Bash while loop reference: read files with while IFS= read -r, retry-with-backoff, wait-for-service polling, the subshell-scoping bug fix, the until and select siblings, plus [[ ]] vs [ ] vs (( )) test contexts.](https://techearl.com/cdn-cgi/image/width=1536,format=auto,quality=80/https://images.techearl.com/bash-while-loop/bash-while-loop.jpg?v=2026-04-25T13%3A18%3A00Z)
