$linuxjunkies
>

Use curl Like a Pro

Master curl beyond basic GET requests: inspect and send headers, authenticate with tokens, POST JSON, upload files, manage cookies, and debug with -v.

IntermediateUbuntuDebianFedoraArch9 min readUpdated June 7, 2026

Before you start

  • curl installed (version 7.76 or later recommended)
  • Basic familiarity with a terminal and shell scripting
  • Network access to a test endpoint such as httpbin.org

curl ships on virtually every Linux system and does far more than fetch a URL. Once you move past simple GET requests, you unlock a precise HTTP client you can script, debug, and integrate into pipelines. This guide covers the options you'll reach for daily: inspecting headers, authenticating, posting JSON, uploading files, managing cookies, and tracing exactly what's happening on the wire.

Prerequisites and Version Check

Most distros ship a recent curl. Confirm yours:

curl --version

Output will start with something like curl 8.5.0 (x86_64-pc-linux-gnu) and list supported protocols and features. You need at least 7.76 for --fail-with-body; 7.55+ for --data-raw. If your distro lags, install from the official repo or use a container.

Working with Headers

Viewing Response Headers

Use -I (HEAD request) to fetch only headers, or -i to include headers above the body in a normal GET:

curl -I https://example.com
curl -i https://example.com

To dump headers to a file while still printing the body normally, use -D:

curl -D headers.txt https://example.com -o body.html

Sending Custom Request Headers

Pass each header with -H. Stack multiple -H flags as needed:

curl -H "Accept: application/json" \
     -H "X-Request-ID: abc123" \
     https://api.example.com/status

To remove a default header curl adds (like Host or User-Agent), pass it with an empty value:

curl -H "User-Agent:" https://example.com

Authentication

HTTP Basic Auth

Use -u user:password. Avoid putting passwords in shell history by omitting the password — curl will prompt:

curl -u alice https://api.example.com/private

For scripting where the password must be non-interactive, store it in a .netrc file (chmod 600 ~/.netrc) and use --netrc:

curl --netrc https://api.example.com/private

The ~/.netrc format is: machine api.example.com login alice password s3cr3t

Bearer Token Auth

Pass the Authorization header directly:

TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
curl -H "Authorization: Bearer $TOKEN" https://api.example.com/me

Store tokens in environment variables or a secrets manager; never hardcode them in scripts committed to version control.

Posting JSON

Set the content type and provide the payload with -d (or --data-raw to prevent @ being treated as a filename). Always pair with -X POST or just rely on curl inferring POST when you provide -d:

curl -X POST https://api.example.com/users \
     -H "Content-Type: application/json" \
     -H "Authorization: Bearer $TOKEN" \
     -d '{"name": "Alice", "role": "admin"}'

For longer payloads, store JSON in a file and reference it with @:

curl -X POST https://api.example.com/users \
     -H "Content-Type: application/json" \
     -d @payload.json

To avoid quoting headaches with shell variables inside JSON, use --data-raw or build the JSON with jq first:

PAYLOAD=$(jq -n --arg name "Alice" '{name: $name, role: "admin"}')
curl -X POST https://api.example.com/users \
     -H "Content-Type: application/json" \
     -d "$PAYLOAD"

File Uploads

Multipart Form Upload

Use -F to send a multipart/form-data request, the same format a browser uses for file input fields:

curl -X POST https://upload.example.com/files \
     -F "file=@/path/to/report.pdf" \
     -F "description=Monthly report"

Override the detected MIME type or set a remote filename:

curl -X POST https://upload.example.com/files \
     -F "[email protected];type=application/pdf;filename=march-report.pdf"

Raw Binary Upload (PUT)

Some APIs expect the file body directly without multipart wrapping:

curl -X PUT https://storage.example.com/objects/image.png \
     -H "Content-Type: image/png" \
     --data-binary @photo.png

Managing Cookies

Cookies matter for session-based APIs and web scraping. Use -c to save cookies to a file and -b to send them back:

# Log in and save the session cookie
curl -c cookies.txt -X POST https://app.example.com/login \
     -d "username=alice&password=hunter2"

# Make an authenticated request using the saved cookie
curl -b cookies.txt https://app.example.com/dashboard

Use both flags together to keep updating the cookie jar across redirects in one command:

curl -b cookies.txt -c cookies.txt -L https://app.example.com/profile

Send a specific cookie inline without a file:

curl -b "session=abc123; theme=dark" https://app.example.com/

Debugging with -v and --trace

Verbose Mode

-v prints the full request/response exchange to stderr — request headers, response headers, TLS handshake details, and redirect chain. Lines starting with > are sent; < are received; * are curl status messages:

curl -v https://api.example.com/status

Redirect verbose output to a file while still seeing the body on your terminal:

curl -v https://api.example.com/status 2>debug.log

Full Hex Trace

For low-level inspection of every byte (useful for binary protocols or TLS debugging):

curl --trace trace.bin https://api.example.com/status
curl --trace-ascii trace.txt https://api.example.com/status

--trace-ascii is human-readable and usually enough. Combine with --trace-time to add microsecond timestamps to each line.

Timing Breakdown

Profile where time is spent in a request using -w (write-out):

curl -o /dev/null -s -w \
  "DNS: %{time_namelookup}s\nConnect: %{time_connect}s\nTLS: %{time_appconnect}s\nTTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n" \
  https://api.example.com/status

This outputs nothing about the body, only timing — useful for latency benchmarking in scripts.

Useful Flags to Know

  • -L — follow redirects (301, 302, 307, 308)
  • -s — silent mode, suppresses progress meter and error messages; pair with -S to still show errors
  • -o file — write body to file instead of stdout
  • -O — write body to a file named after the remote filename
  • --fail — exit non-zero on HTTP 4xx/5xx (useful in scripts); use --fail-with-body (curl 7.76+) to also print the error response
  • --max-time 10 — abort if the whole transfer takes over 10 seconds
  • --retry 3 --retry-delay 2 — retry on transient failures
  • -k / --insecure — skip TLS certificate verification. Never use in production; only for local dev with self-signed certs.

Verification

Test your understanding with a real public echo API. httpbin.org reflects back exactly what you send:

curl -s -X POST https://httpbin.org/post \
     -H "Content-Type: application/json" \
     -H "X-Custom: test" \
     -d '{"hello": "world"}' | python3 -m json.tool

The JSON response will include your headers, body, and origin IP. Confirm json.hello equals world and your X-Custom header appears under headers.

Troubleshooting

  • Certificate errors: Run curl -v and look for TLS handshake failures. Update your CA bundle (sudo update-ca-certificates on Debian/Ubuntu; sudo update-ca-trust on Fedora/RHEL) before resorting to -k.
  • Empty responses or 000 status: Usually a DNS or connection failure. Add -v to see where it dies. Check --resolve to force a specific IP.
  • Unexpected 400 on JSON POST: Confirm the Content-Type header is present and the JSON is valid (echo '...' | jq .). Some APIs also require Accept: application/json.
  • Shell quoting issues: Single quotes work best for JSON in bash. If you need variable interpolation inside JSON, build with jq instead of string concatenation.
  • Proxy environments: curl respects http_proxy, https_proxy, and no_proxy environment variables. Export them or use -x proxy:port explicitly.
tested on:Ubuntu 24.04Fedora 40Debian 12Arch rolling

Frequently asked questions

How do I suppress the progress bar but still see errors?
Use -s (silent) combined with -S (show errors). The combination -sS suppresses the meter but prints any curl-level errors to stderr, which is ideal for scripts.
What's the difference between -d and --data-raw?
With -d, a value starting with @ is treated as a filename to read from. --data-raw sends the string literally, so @ has no special meaning. Use --data-raw when your data might start with @ or when you want to be explicit.
How do I make curl exit with a non-zero code on HTTP errors?
Use --fail for silent failure, or --fail-with-body (curl 7.76+) to also print the server's error response body before exiting non-zero. This is essential for reliable use in shell scripts and CI pipelines.
Can curl follow redirects automatically?
Yes, add the -L flag. By default curl stops at the first 3xx response. Combine with --max-redirs 5 to cap the redirect chain and prevent infinite redirect loops.
How do I test an API running locally with a self-signed certificate?
Use -k (--insecure) to skip certificate validation. This is acceptable only for local development; never use it against production endpoints because it disables all TLS protection.

Related guides