By default, curl treats an HTTP reply as a successful transfer as long as it connected, sent the request, and received a response. That means a missing file, broken API route, or expired token can still leave shell automation with exit code 0 unless you explicitly tell curl to fail on HTTP status errors.
Use --fail when any HTTP 4xx or 5xx response should stop the command immediately, and use --fail-with-body when the same failure should still save the response body for debugging or parsing. The upstream curl docs note that --fail is not completely fail-safe around some authentication flows, especially 401 and 407, so log the HTTP status separately when that distinction matters.
The two fail options are mutually exclusive, and --fail-with-body requires curl 7.76.0 or newer. When scripts, CI jobs, or health checks need a stable record of what happened, pair the failure option with --write-out "response_code=%{response_code}\n" so the HTTP status is still visible in logs.
Steps to fail on HTTP errors with cURL:
- Start a disposable local HTTP server so the next commands can hit a path that deliberately returns 404.
$ python3 -m http.server 18081 --bind 127.0.0.1 Serving HTTP on 127.0.0.1 port 18081 (http://127.0.0.1:18081/) ...
Leave this process running in its own terminal while you test the next commands. Press Ctrl+C when you are finished.
- Request a missing path without a fail option to confirm the default behavior. The server returns 404, but curl still exits successfully because the transfer itself worked.
$ curl --silent --output /dev/null --write-out "response_code=%{response_code}\n" http://127.0.0.1:18081/missing-release.json response_code=404 $ echo $? 0HTTP status is application-level information. Without --fail or --fail-with-body, curl only reports transport failure.
- Add --fail when an HTTP error should stop the command and return error 22.
$ curl --silent --show-error --fail --output /dev/null http://127.0.0.1:18081/missing-release.json curl: (22) The requested URL returned error: 404 $ echo $? 22
Use --fail when you only need a clean success or failure signal and there is no reason to keep the error response body.
- Switch to --fail-with-body when the server's error payload still needs to be saved locally.
$ curl --silent --show-error --fail-with-body --output missing-release.html http://127.0.0.1:18081/missing-release.json curl: (22) The requested URL returned error: 404 $ echo $? 22 $ ls missing-release.html missing-release.html
--fail-with-body still returns error 22 for HTTP failures, but it preserves the response body instead of discarding it.
- Print the HTTP status into the same log stream when wrappers, CI jobs, or alerts need both the curl exit and the server's response code.
$ curl --silent --show-error --fail --output /dev/null --write-out "response_code=%{response_code}\n" http://127.0.0.1:18081/missing-release.json 2>&1 curl: (22) The requested URL returned error: 404 response_code=404 $ echo $? 22The curl exit code should drive shell control flow, while {response_code} keeps the exact HTTP result visible in logs and retry decisions.
- Reuse the same fail option on a known-good path to confirm that successful HTTP responses still exit cleanly.
$ curl --silent --show-error --fail --output /dev/null --write-out "response_code=%{response_code}\n" http://127.0.0.1:18081/ response_code=200 $ echo $? 0If a script needs to branch on HTTP failures, treat any non-zero curl exit as failure. Do not rely on parsing the response body alone.
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.
