curl

curl sends one request and shows you exactly what came back: the body, the headers, the status code, the redirects, and the timing. Reach for it the moment the browser hides too much, whether you are reproducing a finding as a clean repeatable request, replaying an API call, or answering “is that endpoint actually returning 200?”

Mental model: curl [options] URL. Everything else is one flag away.

New to HTTP? Core concepts
  • URL: scheme, host, optional port, path, query, for example http://TARGET_IP:8080/api/users?id=1
  • Method (verb): GET reads, POST sends, plus PUT, DELETE, HEAD, OPTIONS via -X
  • Request headers (-H) are what you send; response headers (-i/-I) are what the server returns
  • Status code: 2xx success, 3xx redirect, 4xx your error, 5xx server error
  • Body: the payload returned, or the data you send with -d
  • Cookies and sessions: -b sends, -c saves to a jar
  • TLS: HTTPS; curl verifies certificates by default and fails on a bad one
When to reach for curl (and when not)

Reach for it to reproduce a finding as an exact repeatable request, replay an API call, check a status or header after a change, verify a security header (HSTS, CSP) is really sent, or isolate whether a problem is the server, a redirect, TLS, or DNS.

Reach for something else when you need to render JavaScript (use a browser or headless browser), fuzz paths or parameters at scale (ffuf, gobuster), intercept and replay with a UI (Burp, mitmproxy), or just download and mirror files (wget).

Install, update, verify#

sudo apt install -y curl                 # Debian / Ubuntu / Kali
sudo dnf install -y curl                 # Fedora / RHEL
sudo apt install --only-upgrade -y curl  # update

curl --version        # version, TLS backend, protocols (HTTP2/HTTP3)
command -v curl        # confirm it is on PATH

curl ships by default on Windows 10+ and macOS. On Windows, PowerShell’s curl is an alias for Invoke-WebRequest, so call curl.exe to get the real tool.

Flags you’ll actually use#

Skim these first; the cheat sheet below is these flags combined.

FlagDoesFlagDoes
-sSilent (no progress bar)-HAdd a request header
-SShow errors even with -s-dSend a POST body (form-encoded)
-iInclude response headers--data-binarySend a body’s exact bytes (@file reads a file)
-IHEAD, headers only-FMultipart form / file upload
-LFollow redirects-o / -OSave to a name / keep remote name
-XSet the method-C -Resume a partial download
-uBasic auth user:pass-wWrite-out vars (status, timing, sizes)
-b / -cSend / save cookies-kSkip TLS verification (labs)
-A / -eSet User-Agent / Referer-xRoute through a proxy
--resolvePin a hostname to an IP-vVerbose sent/received trace

Cheat sheet#

Every block starts simple and adds one flag at a time, so you can stop at the line that does the job.

# Build up a request: stop at the line you need
curl URL                                  # 1. the body
curl -i URL                               # 2. + response headers
curl -sS URL                              # 3. + quiet, but keep errors
curl -sSiL URL                            # 4. + follow redirects, show each hop

# Status and timing (scriptable)
curl -s -o /dev/null -w '%{http_code}\n' URL                 # just the status code
curl -s -o /dev/null \
  -w 'dns:%{time_namelookup} ttfb:%{time_starttransfer} total:%{time_total}\n' URL

# Send data
curl -d 'id=1&user=admin' URL             # form POST (implies POST)
curl -H 'Content-Type: application/json' -d '{"id":1}' URL   # JSON POST
curl --data-binary @payload.json URL      # send a file's exact bytes
curl -F '[email protected]' URL            # multipart upload
curl -X PUT -d '{"role":"admin"}' URL     # any method

# Headers, auth, identity
curl -H 'Authorization: Bearer TOKEN' URL # bearer token
curl -u USER:PASS URL                     # basic auth
curl -A 'Mozilla/5.0' URL                 # set User-Agent
curl -e 'https://ref.example/' URL        # set Referer
curl -H 'Host: vhost.local' http://TARGET_IP/            # vhost by IP + Host header
curl --resolve vhost.local:443:TARGET_IP https://vhost.local/   # pin DNS, real SNI

# Cookies and sessions
curl -c jar.txt -d 'user=U&pass=P' URL    # save cookies from login
curl -b jar.txt URL                       # replay the saved session

# Files and ranges
curl -O URL                               # download, keep remote name
curl -o out.bin URL                       # download to a chosen name
curl -C - -O URL                          # resume a partial download
curl -r 0-1023 URL                        # first 1024 bytes only (range request)

# TLS, proxy, protocol
curl -k https://TARGET_IP/                # skip cert check (labs only)
curl --cacert ca.pem https://host/        # trust a specific CA
curl -x http://127.0.0.1:8080 URL -k      # route through Burp/mitmproxy
curl --http2 -I https://host/             # force HTTP/2
curl --compressed URL                     # request gzip, decode transparently

# Debug and resilience
curl -v URL                               # full sent (>) and received (<) trace
curl --retry 3 --retry-delay 2 URL        # retry transient failures
curl -s URL | jq .                        # pretty-print / filter JSON

Command breakdowns#

Each block below explains one situation, the exact command, and what to expect back.

Check whether a page is up (status code only)#

curl -s -o /dev/null -w '%{http_code}\n' http://TARGET_IP/

Prints a bare 200, 302, 401, or 404. -o /dev/null throws away the body, -w prints only the code. Ideal inside loops and scripts.

See headers and body together#

curl -i http://TARGET_IP/login

Headers, a blank line, then the body. The fastest single-shot triage view. Use -I for a HEAD request when you want headers only.

Follow redirects to the final page#

curl -sSiL http://TARGET_IP/

-L follows each 3xx; -i shows every hop’s headers, so you see the whole redirect chain instead of just the first Location.

POST a login form#

curl -i -X POST http://TARGET_IP/login -d 'username=admin&password=hunter2'

-d form-encodes the body and implies POST. Watch the response for a Set-Cookie or a 302 to a dashboard.

POST JSON to an API#

curl -i -X POST http://TARGET_IP:8080/api/users \
  -H 'Content-Type: application/json' \
  -d '{"name":"svc","role":"user"}'

A 201 usually means created. Forget the Content-Type and most APIs reject the body. For a large or exact payload use --data-binary @file.json.

Upload a file (multipart)#

curl -F '[email protected]' -F 'note=lab' http://TARGET_IP/upload

-F builds a multipart/form-data body, one -F per field; @ reads a file. This is how you test file-upload endpoints.

Send a bearer token or basic auth#

curl -s http://TARGET_IP/api/me -H 'Authorization: Bearer TOKEN'
curl -s -u USER:PASS http://TARGET_IP/protected/

A 401 back means the credential is missing or wrong; read the WWW-Authenticate header for the scheme the server wants.

Save a session and reuse it#

curl -s -c jar.txt -d 'username=U&password=P' http://TARGET_IP/login
curl -s -b jar.txt http://TARGET_IP/dashboard

-c writes the cookie jar on login, -b sends it back. This is how you stay authenticated across separate curl calls.

Test a virtual host without DNS#

curl -s http://TARGET_IP/ -H 'Host: internal.local'
curl -s --resolve internal.local:443:TARGET_IP https://internal.local/

The Host header hits a name-based vhost by IP. For HTTPS use --resolve instead, so the TLS SNI and certificate match the real hostname.

Resume a large download#

curl -C - -O http://TARGET_IP/files/big.iso

-C - tells curl to figure out the offset and continue where a dropped transfer stopped, instead of starting over.

Ignore a self-signed certificate#

curl -k -I https://TARGET_IP/

-k skips TLS verification. Labs only; never normalize it against real targets. To do it properly, trust the CA with --cacert ca.pem.

Find where the time goes on a slow endpoint#

curl -s -o /dev/null \
  -w 'dns:%{time_namelookup} connect:%{time_connect} ttfb:%{time_starttransfer} total:%{time_total}\n' \
  http://TARGET_IP/

Splits DNS versus connect versus server think-time, so you blame the right layer instead of guessing.

Route through Burp or mitmproxy#

curl -s -x http://127.0.0.1:8080 http://TARGET_IP/ -k

-x sends the request through your proxy, so it lands in the history for inspection and replay.

Debug exactly what is sent and received#

curl -v http://TARGET_IP/api/health

> lines are what you sent, < lines are the response, plus the TLS handshake. The first place to look when a request behaves unexpectedly.

Reading the response#

You seeMeaningDo next
200 / 201 / 204SuccessRead the body, it is the real content
301 / 302 / 307RedirectCheck Location, or add -L to follow
401Needs authAdd -u or an Authorization header
403ForbiddenYou reached it but cannot have it
404MissingWrong path, enumerate with ffuf/gobuster
429Rate limitedSlow down, add delays
5xxServer brokeOften interesting; confirm it is not just an overloaded box

Key headers: Location (redirect target), Set-Cookie (session), WWW-Authenticate (auth challenge), Content-Type (how to parse the body).

Troubleshooting#

ProblemCauseFix
command not foundNot installed / stale PATHReinstall; open a fresh shell
Connection refusedNothing on that portConfirm the port (scan with Nmap first)
Hangs then times outHost down or filteredCheck IP, route, firewall/scope
Could not resolve hostDNS failureUse the IP, or set Host / --resolve
SSL certificate problemSelf-signed / bad cert-k in labs, or --cacert the CA
Empty body on a 3xxRedirect not followedAdd -L (and -i to see the hop)
API rejects your -d bodyMissing Content-TypeAdd -H 'Content-Type: application/json'
PowerShell curl acts weirdAliased to Invoke-WebRequestCall curl.exe

Gotchas#

  • Quote URLs with &, ?, or spaces, or the shell eats part of the address.
  • -d strips newlines and treats @file as “read from file”. Use --data-binary for exact payloads.
  • -o is your filename, -O is the remote filename. Easy to overwrite the wrong file.
  • The status code and headers are the answer as often as the body is, so do not read the body alone.
  • --resolve beats a Host header on HTTPS, because it fixes SNI and cert validation too.

Pairs with#

nmap finds the open web ports first. ffuf and gobuster discover paths and parameters, then curl confirms each hit cleanly. jq slices JSON. Burp and mitmproxy (via -x) give full interception. For plain downloads and mirroring reach for wget; for exploratory API work with colorized output httpie is friendlier, but curl stays the most universal and scriptable.

Find us elsewhere

Merch, stickers, and moreSupport the work at the Solvere Labs shop