Portal | Level: L1: Foundations | Topics: curl & wget, Linux Fundamentals | Domain: CLI Tools
curl & wget — HTTP Tools Primer¶
Why This Matters¶
curl and wget are how you interact with HTTP from the command line. Health checks, API calls, downloading artifacts, testing load balancers, debugging SSL, sending webhooks — all of it flows through these tools. curl is available on every modern Linux, macOS, and Windows system. If you cannot drive curl fluently, you are opening a browser or writing a script for something that should take five seconds.
Name origin: "curl" stands for "Client URL." Daniel Stenberg started it in 1998 as "httpget," renamed it to "urlget," and finally "curl." The project has been in continuous development for over 25 years and supports over 25 protocols. Stenberg estimates curl is installed on over 20 billion devices worldwide.
curl — The HTTP Swiss Army Knife¶
Basic Requests¶
# GET request (default)
curl https://api.example.com/status
# Verbose output (see headers, TLS handshake, everything)
curl -v https://api.example.com/status
# Silent mode (suppress progress bar)
curl -s https://api.example.com/status
# Silent with errors shown
curl -sS https://api.example.com/status
# Follow redirects
curl -L https://example.com/download
# Show only response headers
curl -I https://example.com
# Show response headers AND body
curl -i https://api.example.com/data
HTTP Methods¶
# POST
curl -X POST https://api.example.com/users
# PUT
curl -X PUT https://api.example.com/users/123
# PATCH
curl -X PATCH https://api.example.com/users/123
# DELETE
curl -X DELETE https://api.example.com/users/123
# HEAD (same as -I)
curl -X HEAD https://api.example.com/status
Note: -X POST is not needed when you use -d (data). curl infers POST from the presence of a body.
Gotcha: Using
-X POSTwith-dis harmless but redundant. However, using-X GETwith-dwill send a GET request with a body, which confuses many servers and proxies. If you see a GET with-Xand-din a script, it is almost always a bug.
Sending JSON¶
# POST JSON payload
curl -s -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-d '{"name": "alice", "role": "admin"}'
# JSON from a file
curl -s -X POST https://api.example.com/config \
-H "Content-Type: application/json" \
-d @payload.json
# PUT JSON update
curl -s -X PUT https://api.example.com/users/123 \
-H "Content-Type: application/json" \
-d '{"role": "viewer"}'
# Pipe JSON from stdin
echo '{"status": "active"}' | curl -s -X POST https://api.example.com/status \
-H "Content-Type: application/json" \
-d @-
Authentication¶
# Basic auth
curl -u username:password https://api.example.com/secure
# Bearer token
curl -H "Authorization: Bearer eyJhbGci..." https://api.example.com/data
# OAuth2 bearer (shorthand, requires curl 7.73+)
curl --oauth2-bearer eyJhbGci... https://api.example.com/data
# API key in header
curl -H "X-API-Key: abc123def456" https://api.example.com/data
# Netrc file (avoid credentials in command history)
curl --netrc-file ~/.netrc https://api.example.com/data
# ~/.netrc format:
# machine api.example.com login user password secret
Downloading Files¶
# Save with remote filename
curl -O https://releases.example.com/app-v2.3.tar.gz
# Save with custom filename
curl -o app.tar.gz https://releases.example.com/app-v2.3.tar.gz
# Resume interrupted download
curl -C - -O https://releases.example.com/large-file.iso
# Download multiple files
curl -O https://example.com/file1.txt -O https://example.com/file2.txt
# Limit download speed
curl --limit-rate 1M -O https://example.com/large.tar.gz
# Download only if newer than local file
curl -z local-file.txt -O https://example.com/file.txt
Cookie Handling¶
# Save cookies to a file
curl -c cookies.txt https://example.com/login \
-d "user=alice&pass=secret"
# Send cookies from a file
curl -b cookies.txt https://example.com/dashboard
# Send a specific cookie
curl -b "session=abc123" https://example.com/api
# Save and send cookies in one flow
curl -c cookies.txt -b cookies.txt https://example.com/auth
Timing and Performance¶
# Measure response time breakdown
curl -o /dev/null -s -w "\
DNS: %{time_namelookup}s\n\
Connect: %{time_connect}s\n\
TLS: %{time_appconnect}s\n\
TTFB: %{time_starttransfer}s\n\
Total: %{time_total}s\n\
Size: %{size_download} bytes\n\
HTTP Code: %{http_code}\n" \
https://api.example.com/health
# Output:
# DNS: 0.012s
# Connect: 0.045s
# TLS: 0.120s
# TTFB: 0.250s
# Total: 0.255s
# Size: 42 bytes
# HTTP Code: 200
# Set connection timeout
curl --connect-timeout 5 https://api.example.com/health
# Set maximum time for entire operation
curl --max-time 30 https://api.example.com/slow-endpoint
# Both together (standard for health checks)
curl --connect-timeout 5 --max-time 10 -sf https://api.example.com/health
SSL/TLS Options¶
# Skip certificate verification (DANGEROUS — dev/debug only)
curl -k https://self-signed.example.com
# Remember: -k is for "insecure" (think "k for kaution")
# Specify CA certificate
curl --cacert /etc/ssl/certs/internal-ca.pem https://internal.example.com
# Client certificate authentication (mTLS)
curl --cert /etc/ssl/client.pem --key /etc/ssl/client-key.pem \
https://mtls.example.com
# Show certificate details
curl -vvI https://example.com 2>&1 | grep -A 20 "Server certificate"
# Force specific TLS version
curl --tlsv1.2 https://example.com
curl --tlsv1.3 https://example.com
Multipart Form Data¶
# Upload a file
curl -F "file=@report.pdf" https://api.example.com/upload
# Upload with additional fields
curl -F "file=@report.pdf" -F "description=Monthly report" \
https://api.example.com/upload
# Multiple files
curl -F "file1=@doc1.pdf" -F "file2=@doc2.pdf" \
https://api.example.com/upload
# Specify content type for upload
curl -F "file=@data.csv;type=text/csv" https://api.example.com/import
Working with APIs¶
# REST API — create, read, update, delete
# Create
curl -s -X POST https://api.example.com/items \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"name": "widget", "count": 5}'
# Read
curl -s -H "Authorization: Bearer $TOKEN" \
https://api.example.com/items/42 | jq .
# Update
curl -s -X PUT https://api.example.com/items/42 \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"count": 10}'
# Delete
curl -s -X DELETE -H "Authorization: Bearer $TOKEN" \
https://api.example.com/items/42
# GraphQL
curl -s -X POST https://api.example.com/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ user(id: 1) { name email } }"}'
# Pagination (follow Link headers)
curl -sI https://api.example.com/items?page=1 | grep -i "^link:"
Retry and Resilience¶
# Retry on transient failures
curl --retry 3 --retry-delay 2 https://api.example.com/health
# Retry on connection refused and HTTP 5xx
curl --retry 5 --retry-all-errors https://api.example.com/health
# Retry with exponential backoff (max wait)
curl --retry 3 --retry-max-time 60 https://api.example.com/health
wget — Bulk Downloader and Site Mirrorer¶
wget is optimized for downloading files and mirroring sites. It handles recursive downloads, retries, and bandwidth limiting out of the box.
Basic Downloads¶
# Download a file
wget https://releases.example.com/app-v2.3.tar.gz
# Save with custom filename
wget -O app.tar.gz https://releases.example.com/app-v2.3.tar.gz
# Download to a specific directory
wget -P /tmp/downloads/ https://example.com/file.tar.gz
# Resume interrupted download
wget -c https://releases.example.com/large-file.iso
# Quiet mode (no output except errors)
wget -q https://example.com/file.tar.gz
# Background download
wget -b https://releases.example.com/huge.tar.gz
# Check progress: tail -f wget-log
Recursive Downloads and Mirroring¶
# Mirror a website for offline viewing
wget --mirror --convert-links --adjust-extension \
--page-requisites --no-parent \
https://docs.example.com/
# Same as above (shorthand)
wget -mkEpnp https://docs.example.com/
# Download specific file types only
wget -r -A "*.pdf" https://example.com/reports/
# Reject specific file types
wget -r -R "*.gif,*.jpg,*.png" https://example.com/docs/
# Limit recursion depth
wget -r -l 2 https://example.com/
# Download a list of URLs from a file
wget -i urls.txt
# Restrict to the same domain (do not follow external links)
wget -r --domains=example.com https://example.com/
wget vs curl¶
| Feature | curl | wget |
|---|---|---|
| HTTP methods | All (GET, POST, PUT, DELETE, PATCH) | GET, POST only |
| JSON/API work | Excellent | Poor |
| Recursive download | No | Yes |
| Site mirroring | No | Yes |
| Resume interrupted | Yes (-C -) |
Yes (-c) |
| Protocol support | HTTP, FTP, SMTP, IMAP, SCP, many more | HTTP, FTP |
| Output control | -o, -O, stdout |
-O, -P |
| Cookie handling | -b, -c |
--load-cookies, --save-cookies |
| Auth methods | Basic, Bearer, Digest, NTLM, Kerberos | Basic, Digest |
| Progress bar | Optional (--progress-bar) |
Default |
| Available by default | macOS, most Linux | Most Linux |
Rule of thumb: curl for APIs, headers, and HTTP method control. wget for downloading files and mirroring sites.
Remember: Mnemonic for when to reach for which tool: "curl Calls, wget Gets." curl is for calling APIs (interactive, bidirectional). wget is for getting files (download-and-save).
Interview tip: When asked "how would you test an API endpoint from a server with no browser," the answer is always curl. Bonus points for showing
-wformat strings to extract timing and status codes in a health-check script.
httpie — The Modern Alternative¶
httpie (http command) offers a more human-friendly syntax for API work:
# GET request
http https://api.example.com/status
# POST JSON (auto-sets Content-Type)
http POST https://api.example.com/users name=alice role=admin
# Bearer auth
http https://api.example.com/data "Authorization:Bearer token123"
# Form data
http --form POST https://example.com/login user=alice pass=secret
# Download
http --download https://example.com/file.tar.gz
httpie is not installed by default, so you cannot rely on it in scripts or on production servers. Learn curl first — it is universal. Use httpie for interactive API exploration on your workstation.
Fun fact: wget was written by Hrvoje Niksic in 1996 as part of the GNU project. The name is a portmanteau of "World Wide Web" and "get." Unlike curl, wget was designed from the start for non-interactive use -- downloading files unattended, which is why it writes to disk by default while curl writes to stdout.
Wiki Navigation¶
Prerequisites¶
- Linux Ops (Topic Pack, L0)
Related Content¶
- /proc Filesystem (Topic Pack, L2) — Linux Fundamentals
- Advanced Bash for Ops (Topic Pack, L1) — Linux Fundamentals
- Adversarial Interview Gauntlet (30 sequences) (Scenario, L2) — Linux Fundamentals
- Bash Exercises (Quest Ladder) (CLI) (Exercise Set, L0) — Linux Fundamentals
- Case Study: CI Pipeline Fails — Docker Layer Cache Corruption (Case Study, L2) — Linux Fundamentals
- Case Study: Container Vuln Scanner False Positive Blocks Deploy (Case Study, L2) — Linux Fundamentals
- Case Study: Disk Full Root Services Down (Case Study, L1) — Linux Fundamentals
- Case Study: Disk Full — Runaway Logs, Fix Is Loki Retention (Case Study, L2) — Linux Fundamentals
- Case Study: HPA Flapping — Metrics Server Clock Skew, Fix Is NTP (Case Study, L2) — Linux Fundamentals
- Case Study: Inode Exhaustion (Case Study, L1) — Linux Fundamentals