Guides

curl command examples: GET, POST, headers, auth and redirects

curl -X POST -d for form data, curl -H for headers, curl -u for basic auth, curl -L for redirects, curl -o to save a file — with the actual output of each one.

Published ~6 min read

Short answer: curl URL fetches a URL and prints the body. -I shows only the headers, -L follows redirects, -o FILE saves the response to a file, and -X/-d send a POST with data. Every example below was run against example.com and httpbin.org with curl 7.88.1 (Debian 12); the flags themselves are the same across every curl version in active use.

Debian · Ubuntu
curl https://example.com                    # print the response body
curl -I https://example.com                  # headers only, no body
curl -L https://example.com                  # follow redirects

curl examples, by task

TaskCommandFlag
GET a URL, print the bodycurl https://example.com
Headers only, no bodycurl -I https://example.com-I
Headers and body togethercurl -i https://example.com-i
Follow redirectscurl -L https://example.com-L
Save to a file, keep the original namecurl -O https://example.com/file.zip-O
Save to a file, choose the namecurl -o out.html https://example.com-o
Send a POST with form datacurl -X POST -d "name=vmheaven" https://httpbin.org/post-X, -d
Send JSONcurl -X POST -H "Content-Type: application/json" -d '{"name":"vmheaven"}' https://httpbin.org/post-H, -d
Custom headercurl -H "X-Test: vmheaven" https://httpbin.org/headers-H
Basic authcurl -u user:pass https://httpbin.org/basic-auth/user/pass-u
Upload a file as form datacurl -F "file=@report.pdf" https://httpbin.org/post-F
Fail on HTTP errors instead of printing the error pagecurl -f https://example.com/missing-f
Silent, but still fail loudly on an errorcurl -sS https://example.com -o /dev/null-s -S
Only the HTTP status codecurl -s -o /dev/null -w "%{http_code}\n" https://example.com-w
Time the requestcurl -s -o /dev/null -w "%{time_total}s\n" https://example.com-w
Resume a partial downloadcurl -C - -O https://example.com/big.iso-C -

POST data: -d vs -F vs JSON

-d sends application/x-www-form-urlencoded data by default — the same encoding an HTML form submits. We ran this against httpbin.org, which echoes back whatever it received:

request
curl -X POST https://httpbin.org/post -d "name=vmheaven"
response (trimmed)
{
  "form": {
    "name": "vmheaven"
  },
  "headers": {
    "Content-Type": "application/x-www-form-urlencoded",
    "User-Agent": "curl/7.88.1"
  }
}

-X POST is actually redundant here — passing -d switches curl to POST on its own, so curl -d "name=vmheaven" https://httpbin.org/post does the same thing. Writing -X POST explicitly is still common for clarity, and becomes necessary the moment you want a method -d does not imply by itself, such as PUT or DELETE with a body.

For JSON, set the content type yourself — curl does not infer it from the data:

request
curl -X POST https://httpbin.org/post \
  -H "Content-Type: application/json" \
  -d '{"name":"vmheaven"}'

For a file upload as actual multipart form data (what a file-picker on a web form sends), use -F instead of -d — mixing the two sends the wrong encoding for the kind of data each is meant for:

Debian · Ubuntu
curl -F "file=@/path/to/report.pdf" https://httpbin.org/post

The leading @ is what tells curl to read a file rather than send the literal string /path/to/report.pdf.

Headers and authentication

request
curl -H "X-Test: vmheaven" https://httpbin.org/headers
response (trimmed)
{
  "headers": {
    "User-Agent": "curl/7.88.1",
    "X-Test": "vmheaven"
  }
}

-H is repeatable — pass it once per header. -u user:pass sends HTTP Basic authentication; curl adds the Authorization: Basic ... header itself, base64-encoded, so there is no need to build that header by hand with -H. A failed basic-auth request against httpbin.org’s own test endpoint, which only accepts the exact user/pass pair in its own URL, returns a plain 401:

request, with the wrong password on purpose
curl -u user:wrongpass https://httpbin.org/basic-auth/user/pass -w "\nHTTP:%{http_code}\n"
response
HTTP:401

Redirects, status codes and -w

By default curl stops at the first redirect response and prints that response’s own body — often a short “moved” page — rather than the page the redirect points to. -L tells it to keep following until it reaches a non-redirect response:

without -L — stops at the redirect itself
curl -s -o /dev/null -w "%{http_code}\n" http://httpbin.org/redirect/2
# 302 — the redirect response itself, nothing followed
with -L — follows every hop
curl -s -L -o /dev/null -w "%{http_code}\n" http://httpbin.org/redirect/2
# 200 — curl followed both hops to the final page

-w (--write-out) prints whatever variables you ask for after the transfer finishes, which is the clean way to script against curl’s result instead of parsing output meant for a human. The most useful ones:

VariableMeaning
%{http_code}The final HTTP status code
%{url_effective}The URL actually reached, after redirects
%{time_total}Total time for the request, in seconds
%{time_starttransfer}Time to first byte of the response
%{size_download}Bytes downloaded

For checking a full redirect chain, status codes and headers without building the curl command yourself, the HTTP header and redirect checker runs the same kind of request from our server in Amsterdam, as a browser, Googlebot or Bingbot.

Debugging a request that fails

FlagWhat it shows
-vThe full request and response, including the TLS handshake and every header sent and received
-sSSilent output, but still print an error message if the request fails — the combination to use in scripts
-fMake curl itself return a non-zero exit code on an HTTP 4xx/5xx response, instead of printing the server's error page and exiting 0
--resolve HOST:PORT:IPForce a hostname to a specific IP, bypassing DNS — the standard way to test a server directly before its DNS points there
-kSkip TLS certificate verification — for testing an internal or self-signed origin only, never for a production check

curl: command not found

If none of this works because there is no curl at all, that is a separate, simpler problem — see curl: command not found for the one-line install on every major distribution.

  • Need the request from a real browser engine, not curl? CORS errors only happen in a browser — curl ignores CORS entirely, which is a common source of “it works in curl but not in the browser” confusion.
  • Downloading a whole site, not one file? wget is built for recursive downloads in a way curl is not — see wget: command not found if that is missing instead.

Frequently asked

How do I send a POST request with curl?

curl -X POST -d "name=value" URL sends form-encoded data (-d alone already switches curl to POST, so -X POST is optional there but common for clarity). For JSON, add -H "Content-Type: application/json" and pass the JSON string to -d.

How do I make curl follow a redirect?

curl -L URL. Without it, curl stops at the first redirect response and prints that response's own body rather than the page it points to.

How do I see only the HTTP status code with curl?

curl -s -o /dev/null -w "%{http_code}\n" URL — -s silences the normal output, -o /dev/null discards the body, and -w prints just the status code variable afterward.

Why does curl return success for a 404 page?

By default curl's own exit code only reports whether it got a complete HTTP response, not whether that response was an error page. Add -f (--fail) to make curl itself return a non-zero exit code on a 4xx or 5xx response, which is what a script checking $? usually actually wants.

Related articles

All guides