curl -f (--fail): exit with an error on HTTP 4xx and 5xx
- 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:
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: $?"
waitOutput
<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
Related options
| Option | Exit code on HTTP >= 400 | Body output | Since |
|---|---|---|---|
| (none) | 0 | yes | - |
| -f, --fail | 22 | no | curl 4.0 |
| --fail-with-body | 22 | yes | curl 7.76.0 |
| --fail-early | stops 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
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:
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"
waitOutput
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 code | Meaning with -f |
|---|---|
| 0 | Final response below 400 (2xx, or 3xx when -L is not used) |
| 22 | HTTP status 400 or above; the message names the status |
| 6 | Host name could not be resolved; -f does not change it |
| 7 | Connection refused or failed; no HTTP response at all |
| 28 | Timeout 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
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.