HTTP from the Terminal: curl and wget
Reviewed & published by Brayan K
By the end of this lesson you'll make HTTP requests straight from the shell: GET and POST with curl, send headers and bodies, authenticate, read status codes, pipe responses into other tools, and download or mirror files with wget — plus a clear sense of when to reach for each tool.
Part of the free Command Line course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
What You'll Learn
- Make a plain GET with curl and save output with -o vs -O
- Follow redirects with -L and include response headers with -i
- Send a POST body with -d and custom headers with -H
- Authenticate with -u basic auth or a Bearer token header
- Print just the status code and pipe the body into other tools
- Download, resume (-c), and mirror sites (-r) with wget
1. curl Basics: GET and Saving the Response
A plain curl URL sends a GET and prints the response body to standard output. To save it instead, use -o name to choose your own filename or -O to reuse the remote filename. Add -L so curl follows redirects (301/302) to the final page rather than printing the redirect itself.
# curl makes a single HTTP request and prints the RESPONSE BODY to
# stdout by default. A plain GET is just the URL:
curl https://example.com
echo "--- save to a filename YOU choose with -o ---"
# -o (lowercase) writes the body to the file you name.
curl -o page.html https://example.com
echo "--- save using the REMOTE filename with -O ---"
# -O (uppercase) reuses the last part of the URL as the filename,
# here it writes a local file called robots.txt
curl -O https://example.com/robots.txt
echo "--- follow redirects with -L ---"
# Many URLs answer 301/302 and point elsewhere. -L follows them
# to the final destination instead of printing the redirect.
curl -L https://example.com2. Response Headers, Request Headers, and Silent Mode
Use -i to include the response headers (status line, Content-Type, and so on) above the body. To send your own request headers, repeat -H 'Header: value'. In scripts, add -s (silent) to hide the progress meter so only the real output is printed.
3. Sending Data: POST, -d, and JSON
-d 'key=value' sends a request body; using -d implies POST and sets Content-Type: application/x-www-form-urlencoded for you. For a JSON API, put JSON in -d and declare it with -H 'Content-Type: application/json'. Use -X to pick another method explicitly, such as PUT or DELETE.
# -d sends a request BODY. Using -d implies -X POST and sets
# Content-Type: application/x-www-form-urlencoded automatically.
curl -d "name=ada&role=admin" https://api.example.com/users
echo "--- send JSON: set the header yourself ---"
# For a JSON API, send JSON in -d and declare it with -H.
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-d '{"name":"ada","role":"admin"}'
echo "--- other methods: -X picks the verb ---"
# -X sets the method explicitly when you need PUT, DELETE, etc.
curl -X DELETE https://api.example.com/users/424. Authentication: Basic Auth and Bearer Tokens
For HTTP basic auth, pass -u user:pass and curl builds the Authorization header for you. Most modern APIs use a Bearer token instead, which you send as a header: -H "Authorization: Bearer TOKEN". Either approach combines naturally with -X, -H, and -d for authenticated requests.
# HTTP basic auth: -u sends a username and password.
curl -u ada:s3cret https://api.example.com/private
echo "--- token auth: send an Authorization header ---"
# Most modern APIs use a Bearer token instead of basic auth.
curl -H "Authorization: Bearer YOUR_TOKEN_HERE" \
https://api.example.com/me
echo "--- combine with JSON POST and silent mode ---"
curl -s -X POST https://api.example.com/orders \
-H "Authorization: Bearer YOUR_TOKEN_HERE" \
-H "Content-Type: application/json" \
-d '{"item":"book","qty":2}'5. Status Codes and Piping the Response
To print just the status code, silence curl, discard the body to /dev/null, and ask for it with the write-out option: curl -s -o /dev/null -w "%{http_code}" URL. Because curl prints the body to stdout, you can pipe it straight into tools like grep — or into a JSON parser, which is the focus of the next lesson.
# Print JUST the status code: silence everything, throw the body
# away to /dev/null, and ask for the http_code with -w.
curl -s -o /dev/null -w "%{http_code}\n" https://example.com
echo "--- pipe the body straight into other tools ---"
# Because curl prints the body to stdout, it pipes naturally.
# Here: download a page and pull out the <title> line.
curl -s https://example.com | grep -i "<title>"
echo "--- feed JSON to a parser ---"
# Pipe an API response into a JSON tool (see the next lesson on jq).
curl -s https://api.example.com/users/1 | head -c 806. wget: Downloading, Resuming, and Mirroring
wget is built for downloads: it saves to disk by default using the remote filename. Add -c to resume a partial download after a dropped connection. To mirror part of a site, use -r (recursive) with -np (no-parent, stay in the starting folder) and -l to limit the depth.
# wget is built for DOWNLOADING. It saves to disk by default,
# using the remote filename — no -O needed for the common case.
wget https://example.com/files/archive.tar.gz
echo "--- resume a half-finished download with -c ---"
# If the connection dropped, -c (continue) picks up where it left
# off instead of starting the whole file over.
wget -c https://example.com/files/big.iso
echo "--- mirror part of a site recursively ---"
# -r follows links to download recursively. -np (no-parent) stays
# within the starting folder, and -l sets how deep to go.
wget -r -np -l 2 https://example.com/docs/🎯 Your Turn: fetch a page following redirects
Fill in the one blank marked ___ using the # 👉 hint so curl follows redirects and saves the page, then run it and check the Output panel matches.
# 🎯 YOUR TURN — fetch a page, following redirects, and save it.
# 1) Follow any 301/302 redirects to the final page.
curl ___ -o site.html https://example.com # 👉 replace ___ with -L
# 2) Confirm it worked by printing curl's exit status (0 = success).
echo "exit: $?"
# ✅ Expected output:
# exit: 0🎯 Your Turn: print only the status code
Make curl print just the HTTP status code by filling in the write-out variable. Fill in the blank, then run it.
# 🎯 YOUR TURN — print ONLY the HTTP status code of a URL.
# Silence output, discard the body, and ask curl to write the
# status code. Fill in the write-out variable.
curl -s -o /dev/null -w "%{___}\n" https://example.com # 👉 replace ___ with http_code
# ✅ Expected output (for a healthy page):
# 200Pro Tips
- 💡 Pick the right tool: reach for curl when you talk to APIs or pipe the body into other commands; reach for wget when you just want to download files or mirror a site to disk.
- 💡 Scripting status checks: curl -s -o /dev/null -w "%{http_code}" URL prints only the status code — perfect for a health check in an if test.
- 💡 curl pipes because it prints to stdout: curl -s URL | grep ... or piping JSON into a parser works precisely because the body goes to standard output by default.
- 💡 Combine short flags: curl -sL URL is the same as curl -s -L URL — silent plus follow-redirects, a common pairing in scripts.
Common Errors (and the fix)
- curl prints an empty body for a moved page — the URL returned a 301/302 redirect. Add -L so curl follows it to the final destination.
- My JSON POST is rejected — you sent -d without declaring JSON, so it defaulted to form encoding. Add -H 'Content-Type: application/json' alongside the JSON body.
- I got a file literally named after the URL path I did not expect — you used -O, which reuses the remote filename. Use -o name.ext to choose the filename yourself.
- My script output is cluttered with a progress bar — add -s to silence the meter, and discard the body with -o /dev/null when you only want the status code.
- wget started the whole download over after a drop — re-run it with -c to continue from where the partial file stopped.
📋 Quick Reference
| Task | Command | Notes |
|---|---|---|
| Plain GET | curl URL | Body printed to stdout |
| Save, chosen name | curl -o file.html URL | You name the file |
| Save, remote name | curl -O URL | Uses URL filename |
| Follow redirects | curl -L URL | 301/302 to final |
| Include resp. headers | curl -i URL | Headers then body |
| Send a header | curl -H 'K: v' URL | Repeat for many |
| POST a body | curl -d 'k=v' URL | -d implies POST |
| Basic auth | curl -u user:pass URL | Or Bearer header |
| Status code only | curl -s -o /dev/null -w "%{http_code}" URL | Prints e.g. 200 |
| Download a file | wget URL | Saves to disk |
| Resume download | wget -c URL | Continue partial |
| Mirror a folder | wget -r -np -l 2 URL | Recursive, no-parent |
Mini-Challenge: a tiny HTTP toolkit
No blanks this time — just a brief and an outline. Combine a status-code check, a download with curl and wget, a JSON POST, and a resumed wget into a few commands that demonstrate everything from this lesson, and check your output against the hints in the comments.
# 🎯 MINI-CHALLENGE: a tiny HTTP toolkit
# Write a few commands (or a small script) that:
#
# 1. Use curl to print ONLY the status code of a URL
# (hint: -s -o /dev/null -w "%{http_code}\n").
# 2. Download a file with curl using the REMOTE filename (-O),
# then download the same URL with wget and compare.
# 3. POST some JSON to an API with curl: set -H Content-Type
# to application/json and send the body with -d.
# 4. Use wget -c to resume a download you start and interrupt.
#
# Hint: pipe a page body into grep to find a line:
# curl -s URL | grep -i "<title>"
# your commands here🎉 Lesson Complete!
- ✅ curl URL prints the body to stdout; save with -o name or the remote name with -O, and follow redirects with -L
- ✅ -i includes response headers, -H sends a request header, and -s silences the progress meter
- ✅ -d sends a POST body (form-encoded by default); add -H 'Content-Type: application/json' for JSON, and -X to pick the method
- ✅ Authenticate with -u user:pass or an Authorization: Bearer header; get the status with -w "%{http_code}" and pipe the body onward
- ✅ wget downloads to disk, -c resumes, and -r -np -l mirror a folder
Practice quiz
What does curl print by default when you run curl https://example.com ?
- The response body to standard output
- Nothing unless you pass -o
- The response saved to a file named example.com
- Only the HTTP status code
Answer: The response body to standard output. curl writes the response body to stdout by default, which is exactly why it pipes so nicely into tools like grep or a JSON parser.
Which curl flag tells it to follow HTTP redirects such as 301 and 302?
- -r
- -f
- -L
- -R
Answer: -L. -L (location) makes curl follow redirects to the final URL. Without it curl just prints the redirect response and stops.
What is the difference between curl -o name.html URL and curl -O URL?
- They are identical
- -o saves to a filename you choose; -O saves using the remote filename
- -o follows redirects; -O does not
- -O saves to a filename you choose; -o uses the remote filename
Answer: -o saves to a filename you choose; -O saves using the remote filename. Lowercase -o lets you name the output file. Uppercase -O reuses the last path segment of the URL as the filename.
What does curl -d 'name=ada' https://api.example.com/users do?
- Downloads the page to a file
- Sends a GET request with a query string
- Deletes the resource
- Sends a POST request with that data as the body
Answer: Sends a POST request with that data as the body. Using -d sends a request body and implies POST, with Content-Type set to application/x-www-form-urlencoded unless you override it.
Which flag adds a request header in curl?
- -H 'Content-Type: application/json'
- -A 'Content-Type: application/json'
- -D 'Content-Type: application/json'
- -X 'Content-Type: application/json'
Answer: -H 'Content-Type: application/json'. -H (header) adds a single request header. Repeat -H to send several headers such as Content-Type and Authorization.
What does the -s flag do in curl?
- Shows the response headers
- Silences the progress meter and error messages
- Saves the body to a file
- Sends the request securely over TLS
Answer: Silences the progress meter and error messages. -s (silent) hides the progress meter and error text, which is handy in scripts so only the real output is printed.
Which curl flag includes the response headers in the printed output along with the body?
- -H
- -h
- -v
- -i
Answer: -i. -i (include) prints the response status line and headers before the body. -H, by contrast, sends a request header.
How do you resume an interrupted download with wget?
- wget -O URL
- wget -L URL
- wget -c URL
- wget -r URL
Answer: wget -c URL. wget -c (continue) resumes a partially downloaded file from where it stopped instead of starting over.
Which curl flag sets a username and password for HTTP basic authentication?
- -b user:pass
- -u user:pass
- -p user:pass
- -a user:pass
Answer: -u user:pass. -u user:pass sends HTTP basic auth credentials. For token APIs you instead send a header like -H 'Authorization: Bearer TOKEN'.
Which tool is built primarily for downloading files and recursively mirroring sites, saving to disk by default?
- wget
- grep
- ssh
- curl
Answer: wget. wget saves to disk by default and supports recursive mirroring with -r. curl is a flexible single-request transfer tool that prints to stdout and pipes well.
Continue this course
- Previous: Finding Files with find and xargs
- Next: Processing JSON with jq — Filter, map and reshape JSON on the command line with jq
- Quick reference: Command Line cheat sheet
Frequently asked questions
curl vs wget — when should I use which?
Use curl when you need flexible single-request transfers, especially for APIs and scripting: it speaks many methods (GET, POST, PUT, DELETE), sends custom headers and bodies, and prints the response body to stdout so it pipes cleanly into tools like grep or a JSON parser. Use wget when your goal is downloading files or mirroring a site: it saves to disk by default and supports recursive download with -r and resuming with -c. Rule of thumb: curl for talking to APIs and piping, wget for grabbing files.
Why does my POST not send any data?
You probably forgot -d, or you sent the wrong Content-Type. Using -d 'key=value' both sends a body and implies POST, defaulting to Content-Type application/x-www-form-urlencoded. For a JSON API you must also add -H 'Content-Type: application/json' and put JSON in -d, otherwise the server may reject or misread the body.
What is the difference between -o and -O in curl?
Lowercase -o lets you choose the output filename, for example curl -o page.html URL. Uppercase -O reuses the last segment of the URL as the local filename, so curl -O https://example.com/robots.txt writes a file called robots.txt. Use -o when you want a specific name and -O when the remote name is fine.
How do I send an API token or log in with curl?
For HTTP basic auth pass -u user:pass, which curl encodes into an Authorization header for you. Most modern APIs use bearer tokens instead, which you send as a header: -H 'Authorization: Bearer YOUR_TOKEN'. You can combine either with -X, -H 'Content-Type: application/json', and -d to make an authenticated POST.
How do I get just the HTTP status code or follow redirects?
To follow redirects (301, 302, and friends) add -L so curl chases the Location header to the final URL. To print only the status code, run curl -s -o /dev/null -w '%{http_code}' URL: -s hides the progress meter, -o /dev/null throws the body away, and -w with %{http_code} writes just the number, which is handy in health-check scripts.