Skip to content

/flags/curl/curl-fail

curl -f (--fail): exit with an error on HTTP 4xx and 5xx

verified onenvironmentlocal · windows 10.0.26200 arm64 · GNU bash 5.3.15 (Git Bash) · curl 8.21.0 (x86_64-w64-mingw32) libcurl/8.21.0 Schannel zlib/1.3.2updated
tool
curl
command
curl
flag
-f, --fail
default
off (HTTP errors exit 0 and the body is output)

By default curl treats any HTTP response as success: a 404 Not Found page is written to the output and curl exits with 0. curl -f (--fail) makes curl return exit code 22 for any status of 400 or above and suppress the response body, so scripts, CI jobs and set -e notice the failure. Add -sS to hide the progress meter but still show the error message.

Without and with -f

A local server answers 404 with an HTML body:

bash
node -e 'require("http").createServer((q, r) => { r.statusCode = 404; r.end("<h1>Not Found</h1>\n"); }).listen(4302, "127.0.0.1"); setTimeout(() => process.exit(0), 3000)' &
sleep 1
curl -s http://127.0.0.1:4302/missing; echo "exit: $?"
curl -sS -f http://127.0.0.1:4302/missing; echo "exit: $?"
curl -sS --fail-with-body http://127.0.0.1:4302/missing; echo "exit: $?"
wait

Output

<h1>Not Found</h1>
exit: 0
curl: (22) The requested URL returned error: 404
exit: 22
curl: (22) The requested URL returned error: 404
<h1>Not Found</h1>
exit: 22
OptionExit code on HTTP >= 400Body outputSince
(none)0yes-
-f, --fail22nocurl 4.0
--fail-with-body22yescurl 7.76.0
--fail-earlystops at the first failing URL when several are given-curl 7.52.0

--fail-with-body is useful for APIs that explain the error in a JSON body: the script fails and the log still shows why. -f and --fail-with-body are mutually exclusive.

Get the status code instead

bash
code=$(curl -s -o response.json -w '%{http_code}' https://api.example.org/items)
if [ "$code" -ge 400 ]; then echo "request failed with HTTP $code"; cat response.json; fi

-w '%{http_code}' prints the status after the transfer, which lets the script handle each status itself.

Redirects and output files

-f looks only at the final response curl receives. Without -L, a redirect is that final response, so the request "succeeds" with exit 0 even if the target would be a 404. With -o, a failed request writes no file, so an old copy is not overwritten with an error page:

bash
node -e 'require("http").createServer((q, r) => { if (q.url === "/old") { r.statusCode = 302; r.setHeader("Location", "/missing"); return r.end(); } r.statusCode = 404; r.end("not found\n"); }).listen(4990, "127.0.0.1"); setTimeout(() => process.exit(0), 3000)' &
sleep 1
curl -sS -f -o /dev/null -w '%{http_code}\n' http://127.0.0.1:4990/old; echo "exit: $?"
curl -sS -fL -o /dev/null http://127.0.0.1:4990/old; echo "exit: $?"
curl -sS -f -o page.html http://127.0.0.1:4990/missing; echo "exit: $?"
[ -e page.html ] && echo "page.html written" || echo "no page.html"
wait

Output

302
exit: 0
curl: (22) The requested URL returned error: 404
exit: 22
curl: (22) The requested URL returned error: 404
exit: 22
no page.html

Exit codes you will see

Exit codeMeaning with -f
0Final response below 400 (2xx, or 3xx when -L is not used)
22HTTP status 400 or above; the message names the status
6Host name could not be resolved; -f does not change it
7Connection refused or failed; no HTTP response at all
28Timeout from --max-time or --connect-timeout

Codes other than 22 come from the network layer and happen with or without -f; the full list is in curl exit codes, and the two most common have their own pages: Could not resolve host and Failed to connect: Connection refused.

In scripts

bash
set -euo pipefail
curl -fsSL https://example.org/install.json -o install.json   # aborts the script on 404 or 500

-fsSL is the common combination: fail on HTTP errors, silent, but show errors, follow redirects.

FAQ

Does curl -f fail on redirects?

No. 3xx responses are below 400, so curl -f exits with 0 and does not follow them. Add -L to follow the redirect; then -f applies to the final response.

Does curl -f retry failed requests?

No. Add --retry N to retry transient errors: timeouts and HTTP 408, 429, 500, 502, 503, 504, 522 and 524. A 404 is not transient and is never retried.

How do I see the error body when curl -f fails?

Use --fail-with-body instead of -f (curl 7.76.0 or later). curl still exits with 22, but writes the response body, so the API's error message appears in the log or the output file.

Sources