wget and curl are the two command-line tools everyone reaches for to move data over a network, and they get lumped together because both can download a file with one line. But they were built for different jobs, and knowing which job you have makes the choice obvious. wget is a downloader; curl is a data-transfer tool. That one distinction explains almost every difference below.
Two different philosophies
wget was designed to retrieve files, especially in bulk and especially unattended. It can follow links, mirror an entire site, resume a broken download and keep retrying on a flaky connection, all on its own. It is the tool you want when the goal is “get these files onto disk,” and it is happy to run in a script with no hand holding.
curl was designed to transfer data, in either direction, over an unusually long list of protocols. It is built on libcurl, a library that countless other programs use under the hood, and it exposes fine control over a single request: headers, methods, authentication, upload bodies, cookies. It is the tool you want when the goal is “make exactly this request and show me the response.”
There is also a quieter difference in how they report trouble. By default, curl treats an HTTP error like a 404 or a 500 as a successful transfer, because it did receive a response; you print the error page and the exit code stays zero unless you add -f to make curl fail on server errors. wget, built for unattended jobs, is stricter: a failed download is a non-zero exit, which is exactly what a script watching for failures wants. That single behavioral gap bites in CI, where a curl step can look green while quietly downloading an error page.
What each does that the other does not
The clearest way to keep them straight is by their non-overlapping strengths.
wget’s territory
- Recursive mirroring. Point wget at a URL with the right flags and it will crawl links and pull down a whole directory or site. curl has no equivalent; it fetches the URLs you name and nothing more.
- Resume by default is easy. wget is built around interrupted downloads: it can retry, resume a partial file, and keep going until the transfer completes.
- Unattended by nature.Continue on error, log to a file, wait between requests: wget’s defaults suit long batch jobs left running.
curl’s territory
- Many protocols. Beyond HTTP and FTP, curl speaks HTTP/2 and HTTP/3, plus SFTP, SCP, SMTP, IMAP, LDAP and more. wget sticks to HTTP, HTTPS and FTP.
- Uploads and complex requests. curl sends POST and PUT bodies, custom headers, form data and file uploads with ease. It is the standard way to poke an API from a shell.
- A library behind it. libcurl means the exact behavior you script on the command line is available inside applications in dozens of languages.
- Writes to stdout by default. curl prints the response to your terminal, which makes it a natural pipeline stage feeding into jq or grep. wget saves to a file by default instead.
Side by side
| curl | wget | |
|---|---|---|
| Default action | Print response to stdout | Save file to disk |
| Recursive download / mirror | ||
| Resume a partial download | ||
| HTTP/2 and HTTP/3 | ||
| Protocols beyond HTTP/FTP | ||
| Upload / POST bodies / forms | ||
| Backed by a reusable library | ||
| Follow redirects | With -L | By default |
The same task, in both tools
Most of the day-to-day overlap comes down to a handful of tasks, each written both ways below.
Download a file
Note the two curl flags: lowercase -o takes a filename you supply, while uppercase -O reuses the name from the URL. wget just saves under the remote name with no flag at all, which is the whole point of it.
Resume an interrupted download
POST JSON to an API
This is curl’s home turf. You set the method, a content-type header, and the body.
wget can send the same body with --post-data and a header flag, but it is clumsier and rarely worth it. If you are talking to an API, use curl.
Follow redirects
This is a common curl surprise: without -L, curl shows you the redirect response itself rather than the page it points to. wget follows the chain without asking.
Save and reuse cookies
Inspect response headers
When you are debugging why a request behaves oddly, the headers tell the story. curl shows them with -I for a headers-only request, or -ito include them above the body. wget writes the server’s response headers with -S.
That --spider flag on wget is a quiet way to check whether a URL exists without pulling the file, which is useful in a script that just needs a yes or no.
Mirror a whole site
This is the task with no curl equivalent. wget’s --mirror turns on recursion, timestamping and infinite depth in one flag, and the companion options rewrite links and pull in the page assets so the copy works offline.
Read it as: mirror the site, rewrite links so they point at your local copy, and grab the images, stylesheets and scripts each page needs. There is simply no way to express this in curl, which is the single sharpest line between the two tools.
So which should you pick?
Let the shape of the task pick the tool.
- Reach for wget when you want files on disk: grabbing a release archive, mirroring a docs site, or running a long unattended download that might drop and need to resume.
- Reach for curl when you are working with a request: hitting an API, sending JSON, inspecting headers, testing an endpoint, or piping a response into another tool. It is also the one to learn if you script in a language that ships libcurl bindings.
In practice most developers keep both installed and use them for exactly these two moods. If you find yourself copying the same curl invocation for an API you hit constantly, that is a snippet worth keeping rather than rebuilding; the same goes for the jq filter you pipe it through. The sibling jq cheat sheet pairs naturally with curl, and the cluster pillar, the regex cheat sheet, covers the pattern-matching you end up doing on the output. For copying these commands around a shell without mangling them, see copy and paste in the terminal.