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.
$ 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.
$ 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 $?
0
HTTP status is application-level information. Without --fail or --fail-with-body, curl only reports transport failure.
$ 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.
$ 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.
$ 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 $?
22
The curl exit code should drive shell control flow, while {response_code} keeps the exact HTTP result visible in logs and retry decisions.
$ 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 $?
0
If 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.