curl and wget are the two command-line download tools of Linux. Installing a tool from a script, fetching a GitHub release, testing a REST API, downloading a database dump: everything goes through one or the other. This guide shows the concrete moves for both, and how to choose between them. Every output was measured on Debian 13 with curl 8.14.1 and wget 1.25.0.
Neither is guaranteed: the Debian 13 container image ships neither curl nor wget, the AlmaLinux 10 image only has curl. Install them with apt install curl wget or dnf install curl wget.
What you will learn
- Download a file with
curl -L -Oand withwget - Resume an interrupted download with
wget -candcurl -C - - Use the silent mode
curl -fsSLin scripts - Get the URL of the latest GitHub release dynamically
- Pass headers and authentication tokens
- Diagnose an HTTP error with
curl -vor--fail - Choose between curl and wget for the situation
curl vs wget: what is the difference?
Both commands speak HTTP and can save a file, but they were not designed for the same use. wget is a content grabber: it follows redirects, resumes and walks a tree without being asked. curl is a protocol client: it sends exactly the request you describe, REST methods and headers included, and adds no implicit behaviour. The table below turns this difference of philosophy into concrete options.
| Capability | curl | wget |
|---|---|---|
| Simple download | Yes | Yes |
| Resume a partial file | With -C - | With -c |
| New attempt after a network cut | With --retry | By default (20 tries) |
| Follow redirects | With -L | By default |
| Recursive download (web mirror) | No | Yes |
| REST requests (POST, PUT, DELETE) | Yes, excellent | Basic |
| Send data read on stdin | Yes (-d @-, -T -) | No |
| Installed by default | macOS, Windows 10 and later, AlmaLinux 10 image | Rarely in a minimal image |
| Primary use | API, scripting, diagnostics | Simple download, mirror |
Practical rule: wget to download simply a file or a mirror. curl for scripts, APIs and HTTP diagnostics.
curl: download a file
By default, curl writes what it receives to standard output, so to your terminal. Saving to a file is an explicit choice, made with -O or -o. This logic explains most beginner surprises: without an output option, a binary archive is printed on screen, and without -L, only the redirect page is retrieved instead of the expected file.
Basic download
The file lands in the current directory, never elsewhere: -O takes the last segment of the URL as the name, ignoring the name the server suggests in its headers.
curl -L -O https://example.com/file.tar.gz| Option | Role |
|---|---|
-L | Follow HTTP redirects (301/302), almost always needed |
-O | Save with the file name from the URL |
-o name.tar.gz | Save under a specific name |
Without -L, curl stops at the first redirect and saves the body of the redirect response, not the file: the symptom is a "file" of a few hundred bytes containing HTML.
Download with a progress bar
Without an option, curl prints a statistics table (rate, volume, remaining time) updated line by line, hard to read on a long download. --progress-bar, abbreviated -#, replaces it with a single bar. This bar can only fill up if the server announces the file size in its Content-Length header.
curl -L -O --progress-bar https://example.com/archive.tar.gzSilent mode for scripts
These four letters are remembered as a block, each one answers a flaw of curl in an automated context: an HTTP error taken as a success, a progress bar that pollutes the logs, a total silence including on errors, and a redirect not followed.
curl -fsSL https://example.com/install.sh -o install.sh| Option | Role |
|---|---|
-f | Fail: return an error code if HTTP ≥ 400 (otherwise curl prints the error body) |
-s | Silent: no progress bar nor statistics |
-S | Still show errors even in -s mode |
-L | Follow redirects |
Resume an interrupted download
Resuming is only possible if the server accepts partial requests (Accept-Ranges header). Otherwise, curl refuses with error 33, HTTP server does not seem to support byte ranges. Cannot resume., and leaves the partial file intact (measured against a Python http.server).
curl -L -C - -O https://example.com/archive-10gb.tar.gz-C - asks curl to detect automatically the resume position in the existing file: it measures its size and only requests the following bytes.
curl: fetch the latest GitHub release
Hard-coding a version number in an install script dooms it to become obsolete. The fix is to ask the server which is the current version, then build the download URL from the answer. Two sources lend themselves to this: a text file published by the project, or the GitHub Releases API, which returns JSON.
A common use case in install scripts, with the official dl.k8s.io download endpoint and the check of the binary:
# Latest stable kubectl version, then binary and checksumRELEASE=$(curl -fsSL https://dl.k8s.io/release/stable.txt)curl -fsSLO "https://dl.k8s.io/release/${RELEASE}/bin/linux/amd64/kubectl"curl -fsSLO "https://dl.k8s.io/release/${RELEASE}/bin/linux/amd64/kubectl.sha256"echo "$(cat kubectl.sha256) kubectl" | sha256sum -cThe old storage.googleapis.com/kubernetes-release address still answers, but its stable.txt is frozen: on 29 September 2026, it announced v1.31.0 while dl.k8s.io announced v1.37.1. A script left on the old URL installs an outdated kubectl, without the slightest error.
With the GitHub Releases API, which answers in JSON. Without authentication, it limits each IP address to 60 calls per hour: a script run in a loop from a shared CI ends up receiving a 403 error.
# Latest jq versionVERSION=$(curl -fsSL https://api.github.com/repos/jqlang/jq/releases/latest | grep '"tag_name"' | cut -d'"' -f4)curl -fsSLO "https://github.com/jqlang/jq/releases/download/${VERSION}/jq-linux-amd64"curl: pass headers and authenticate
An HTTP header is a line of metadata sent with the request: it tells the server the expected format, the caller's identity or the type of the content sent. Most APIs and private repositories refuse an anonymous request and answer 401 or 403. The three forms below cover most cases met on a server.
Custom HTTP header
-H adds a header line to the request and replaces the one curl would have sent by default if the name is identical. The option is repeatable as many times as needed in the same command. Accept: application/json asks the server for a structured response rather than an HTML page, which makes the output usable by jq.
curl -H "Accept: application/json" https://api.example.com/dataBearer token (REST API)
A Bearer token is a secret string that replaces the user name / password pair: whoever holds it is authenticated. The common form goes through an environment variable, which avoids writing the secret in the script and in the shell history:
curl -H "Authorization: Bearer ${API_TOKEN}" https://api.example.com/resourceThis variable does not protect from ps: the shell expands it before starting curl, and the full command line is visible to every account on the machine. Measured with curl 8.14.1, ps -eo args prints curl -H Authorization: Bearer s3cr3tXYZ .... On a shared server, put the header in a file readable by you only and pass it with -H @file: ps then only shows the file name.
printf 'Authorization: Bearer %s\n' "$API_TOKEN" > ~/.api-headerchmod 600 ~/.api-headercurl -H @"$HOME/.api-header" https://api.example.com/resourceBasic authentication
HTTP Basic authentication sends the user name / password pair encoded in base64, which is not encryption: on an http:// URL, the credentials travel in clear text. Keep this form for HTTPS connections. By omitting the password (curl -u user), curl asks for it interactively, which keeps it out of the shell history. In ps, curl itself masks the -u argument with asterisks (measured), unlike a header passed with -H.
curl -u user:password https://nexus.example.com/repository/artifacts/curl: diagnose an HTTP error
When a download fails, the question to settle is whether the problem comes from the network, the certificate or the server's answer. curl -I only sends a HEAD request and prints the headers without downloading the body, ideal to check a status code or a redirect without using bandwidth. -v also details the DNS resolution, the TLS negotiation and the headers sent, each line prefixed with > for the request and < for the response.
# See the response headerscurl -I https://example.com/
# Verbose mode: request AND response headerscurl -v https://example.com/
# Print only the HTTP codecurl -o /dev/null -s -w "%{http_code}\n" https://example.com/200To test an API endpoint:
# POST JSON and print the formatted responsecurl -s -X POST \ -H "Content-Type: application/json" \ -d '{"key": "value"}' \ https://api.example.com/endpoint | jq .wget: download a file
wget takes the decisions curl leaves to you: it writes to a file, follows redirects and shows a progress bar without being asked. It also adds a behaviour curl only has as an option, automatic new attempts after a network cut, which explains its popularity for large files over unstable links. The downside is that it tells you less about what really happens when it fails.
Simple download
If a file of the same name already exists, wget does not overwrite it: it saves under file.tar.gz.1 (measured), a behaviour that surprises when you run a command twice. A script that then reads file.tar.gz works on the old version.
wget https://example.com/file.tar.gzwget follows redirects automatically and saves with the original name.
Download under a different name
Upper-case -O imposes the destination file name, not to be confused with curl's -O, which reuses the name in the URL. A special case useful in scripts: -O - writes the content to standard output, which lets you chain it in a pipe. With several URLs, -O concatenates everything into the same file, which is rarely the desired result.
wget -O my-file.tar.gz https://example.com/release-1.0.tar.gzSilent mode
-q suppresses all output, error messages included: there is no equivalent of curl's -sS that keeps errors visible. In a script, always test wget's exit code, the only indicator of a failure left. The -nv (non-verbose) variant offers a compromise: it hides the progress but keeps the errors and the summary line.
wget -q https://example.com/file.tar.gzResume an interrupted download
-c compares the size of the local file with the one announced by the server and only requests the missing bytes; without this option, wget would create a second file suffixed .1 instead of completing the first one. Against a server that does not handle partial requests, wget does not complain like curl: it downloads the file again from the start and exits with code 0 (measured).
wget -c https://example.com/archive-10gb.tar.gz-c (continue) resumes where the download stopped, with a shorter option than curl's.
Limit the bandwidth
--limit-rate caps the rate of this download, expressed in bytes per second, with the suffixes k for kilobytes and m for megabytes. The limit applies to the wget instance started, not to the machine: two simultaneous downloads therefore use twice as much. It is the reflex to have before fetching a large archive from a production server whose network link is shared with users.
wget --limit-rate=1m https://example.com/archive.tar.gzwget: recursive download
wget can grab a website or an FTP directory by following the links of each page. The four options below bound the recursion to the targeted directory and store the files without reproducing the server's whole tree:
# Recursively download a directory of a sitewget -r -np -nH --cut-dirs=1 https://example.com/docs/
# -r : recursive# -np : no-parent (do not climb to the upper level)# -nH : no host-name directory# --cut-dirs=1 : remove the first path level /docs/Compare curl and wget in practice
The three tabs below repeat the same tasks with each tool, to measure what one imposes and what the other automates. On an isolated download, the difference is cosmetic. It becomes structural in an install script, where error handling decides whether the script stops cleanly or goes on with an empty file.
# curlcurl -L -O https://example.com/archive.tar.gz
# wgetwget https://example.com/archive.tar.gzBoth produce the same result. wget is slightly shorter.
# 1. Download the script, without running itcurl -fsSL https://release.example.com/install.sh -o install.sh
# 2. Read it: this is code that will run as root on your machineless install.sh
# 3. Run it only after reviewsudo bash install.shThe -fsSL combination is the right one for an automated download: it fails
cleanly on an HTTP error and follows redirects. On the other hand, the
widespread curl ... | sudo bash shortcut is to be banned.
# wget: resume with -cwget -c https://example.com/archive-10gb.tar.gz
# curl: resume with -C -curl -L -C - -O https://example.com/archive-10gb.tar.gzBoth resume a partial file. The difference shows on a server without partial requests: curl -C - refuses with error 33, wget -c starts again from zero without warning.
Check after downloading
After a download, always check the integrity of the file if the site provides a checksum. sha256sum -c recomputes the checksum of the received file and compares it with the published one: a single different byte gives FAILED and exit code 1.
# Download the file and its checksumwget https://example.com/archive.tar.gzwget https://example.com/archive.tar.gz.sha256
# Checksha256sum -c archive.tar.gz.sha256archive.tar.gz: OKIf the .sha256 file only holds the hash, sha256sum -c rejects it with no properly formatted checksum lines found: rebuild the line as in the kubectl example. For a complete check with GPG, see the guide Check the integrity of a file.
Common troubleshooting
A failing download rarely leaves an explicit message. The message prefix still guides the diagnosis: curl: (6) or unable to resolve host points to DNS resolution, curl: (7) to a refused connection, curl: (60) to a certificate problem. Check these three layers in order before suspecting the remote server: the failure is most often local.
| Symptom | Likely cause | Solution |
|---|---|---|
curl: (60) SSL certificate problem | Certificate not recognised | Fix the CA store: apt install ca-certificates or dnf install ca-certificates. curl -k turns off all checking, to be banned |
curl: (33) HTTP server does not seem to support byte ranges | Resume asked from a server without partial requests | Delete the partial file and run again without -C - |
curl: (22) The requested URL returned error: 404 | Wrong URL or file removed, reported thanks to -f | Check the URL and the requested version |
wget: unable to resolve host | DNS not configured | ping 8.8.8.8 to test connectivity, cat /etc/resolv.conf |
| Exit code 0 despite a 404 error | No -f on curl | Add -f or test with curl -o /dev/null -w "%{http_code}" |
| Very slow download | Limited bandwidth or saturated server | wget --limit-rate to avoid saturating the local network |
wget: command not found | wget not installed | apt install wget or dnf install wget |
Key points
curl -L -Oandwgetdownload a file;wgetfollows redirects automatically,curlneeds-L.curl -fsSLis the standard combination for install scripts: fail on HTTP errors, no progress, show errors, follow redirects.wget -candcurl -C -resume a partial file; on a server without partial requests, curl refuses (error 33) and wget starts again from zero.- A token passed with
-Hstays visible inps, even from a variable:-H @filehides it. - Always check the checksum of a downloaded file before using it in production.
wgetdoes recursion,curlhandles complex REST requests.
Knowledge check
Check that the essentials of this guide stuck. The questions cover only what has just been explained here.
Knowledge check
Check what you have learned with this interactive quiz
How it works
- The timer starts when you click Start
- Multiple choice, true/false and short answer questions
- You can move between questions
- Detailed results are shown at the end
Starts the quiz and the timer
Review
(0/0)Score by domain
A domain groups several guides and gets several questions, so its score is more reliable than a per-topic score, which often rests on a single question.
Summary
Strengths
No topic passed at 80% on at least two questions yet.
Weak areas
No topic below the pass mark.
Recommended next
Hints before you try again?
Continue the course: Check the integrity of a file: checksums and GPG signatures
A new full quiz with random questions
Work again only on the questions you missed