
curl URL and wget URL can both retrieve a file. In automation, the decisive
difference is what each tool considers success and where it places the response.
This review used current curl 8.21.0 5 and GNU Wget 1.25.0
6 on 13 August 2026. curl ran from the official
curlimages/curl@sha256:7c12af72…fa64bb13 image on musl with OpenSSL 3.5.7.
Wget was installed in the official
debian@sha256:76b6251a…3d241c7d sid-slim image and reported HTTPS support.
The x86-64 host ran Ubuntu 24.04, Linux 6.8.0-100-generic, Docker 29.7.1, and
Zsh; POSIX sh orchestrated each container. A Node 24.18.0 HTTP fixture returned
fixed 200, 302, and 404 responses. This was one functional pass completed
in under ten minutes, not a performance, external-network, DNS, or TLS-handshake
benchmark. No sponsorship or vendor access was involved.
The 404 test changes the script
The fixture exposed /missing with an HTTP 404 and a short text body:
curl -sS http://127.0.0.1:42831/missing >/dev/null
printf 'curl default: %s\n' "$?"
curl -sS --fail-with-body http://127.0.0.1:42831/missing >/dev/null
printf 'curl fail: %s\n' "$?"
wget -qO- http://127.0.0.1:42831/missing >/dev/null
printf 'wget: %s\n' "$?"Observed:
curl default: 0
curl fail: 22
wget: 8curl successfully transferred an HTTP response by default, even though its
status was 404. --fail-with-body converts HTTP 400-or-greater responses into a
failure while retaining the response body; curl documents exit 22 for an HTTP
page not retrieved under fail mode 1 2. GNU Wget used
exit 8 for the server error 3.
Therefore this is unsafe for a package download:
curl -sS https://downloads.example/app.tar.gz -o app.tar.gzAn HTML error body can become app.tar.gz. For an artifact, use --fail (which
does not retain the error body), write to a fresh temporary path, verify it, and
only then publish the final filename:
Caution: The final
mvcreatesapp.tar.gzin the current directory. The guard refuses to overwrite an existing file; verify the destination and the publisher’s checksum or signature first.
artifact=$(mktemp "${TMPDIR:-/tmp}/app.tar.gz.XXXXXX")
if curl --fail --show-error --location \
--output "$artifact" \
https://downloads.example/app.tar.gz; then
printf '%s\n' 'transfer complete; verify before publishing'
else
status=$?
printf 'download failed; temporary path retained: %s\n' "$artifact" >&2
exit "$status"
fi
# Verify the publisher's checksum or signature here.
test ! -e app.tar.gz || { printf '%s\n' 'app.tar.gz exists' >&2; exit 1; }
mv -- "$artifact" app.tar.gz--fail-with-body remains useful when an API error body is diagnostic, but that
body belongs in a diagnostic stream or temporary file—not the final artifact.
A successful HTTP status does not establish artifact integrity.
Redirect behavior is intentionally different
Against /redirect:
curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:42831/redirect
curl -sSL http://127.0.0.1:42831/redirect
wget -qO- http://127.0.0.1:42831/redirectThe first curl command reported 302; curl followed and returned ok only with
-L. Wget followed the redirect by default and returned ok.
Explicit curl redirects are useful because credentials can cross an origin
boundary. Read the manual before combining --location with options that resend
authentication. A convenient redirect policy must not leak a token to a target
you did not intend.
Output defaults express different jobs
curl writes response data to standard output unless directed elsewhere. That makes it natural in a pipeline:
curl -fsSL https://api.example/status | jq -e '.healthy == true'Wget is oriented toward downloading and mirroring; it normally derives a local filename and has recursion, timestamping, and page-requisite features documented in its manual 3.
wget --https-only --timestamping https://downloads.example/catalog.jsonFor automation, name the output either way:
curl -fL -o catalog.json https://downloads.example/catalog.json
wget -O catalog.json https://downloads.example/catalog.jsonWriting directly to the final path can leave a partial or bad file. A robust job downloads to a fresh temporary path, validates it, then moves it atomically into place on the same filesystem.
Retries need an error policy
Neither “retry everything forever” nor “never retry” is safe. A timeout or
temporary 503 can be transient; a 401, bad signature, or invalid URL usually is
not. curl documents --retry, delay, maximum time, connection refusal, and the
error classes considered transient 4.
artifact=$(mktemp "${TMPDIR:-/tmp}/artifact.XXXXXX")
curl --fail --location \
--connect-timeout 10 --max-time 120 \
--retry 4 --retry-all-errors --retry-max-time 300 \
-o "$artifact" \
https://downloads.example/artifact--retry-all-errors is broad. Use it only when repeating the request is safe;
that is usually true for a GET, not automatically true for an upload or API
mutation. Wget has its own timeout, tries, retry-on-host-error, and retry-on-http-
error options. Do not translate flags by spelling alone.
Security boundaries
For either client:
- keep certificate validation enabled;
- avoid credentials in command-line URLs, process listings, and shell history;
- restrict protocol schemes when consuming untrusted redirect or input data;
- cap time, retries, and output size at the surrounding job boundary;
- verify the downloaded artifact independently.
curl supports a wider protocol and API-oriented feature surface; Wget’s recursive retrieval is the clearer fit for controlled mirroring. More features are not a reason to expose more protocols than a job requires.
Verdict
Choose curl for explicit request construction, APIs, pipelines, and response metadata. Choose Wget for straightforward downloads, timestamping, and recursive retrieval. For a single file, both are capable.
The automation contract must still state HTTP failure behavior, redirects, output destination, retry limits, timeouts, and integrity verification. The lab’s most important result is the easiest one to miss: plain curl treated the 404 transfer as success.