All articles

curl 8.21 vs GNU Wget 1.25: Safer Defaults for Automation

10 minutes read


Linux publication

Share this article

𝕏✉

Illustration for curl 8.21 vs GNU Wget 1.25: Safer Defaults for Automation

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: 8

curl 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.gz

An 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 mv creates app.tar.gz in 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/redirect

The 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.json

For automation, name the output either way:

curl -fL -o catalog.json https://downloads.example/catalog.json
wget -O catalog.json https://downloads.example/catalog.json

Writing 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.

Sources and further reading
  1. curl command-line manual
  2. curl exit codes
  3. GNU Wget manual
  4. Everything curl — retries
  5. curl release table
  6. GNU Wget 1.25 release announcement