`curl --fail-with-body` Is the Flag That Finally Lets Scripts Fail Correctly Without Hiding the Error Response
A practical guide to `curl --fail-with-body` for developers who need shell scripts and CI jobs to return non-zero on HTTP errors while still preserving the response body for debugging.
Why this command matters: shell scripts often face a bad tradeoff with HTTP calls. Either the script exits successfully on a 500 response and hides the failure, or it fails fast with
--failand throws away the body that explained what went wrong.
What curl --fail-with-body does
The curl man page describes --fail-with-body as returning an error on HTTP responses with status code 400 or greater while still allowing curl to output and save the response body. curl says it returns error 22 in that case.
A minimal example:
curl --fail-with-body https://example.com/apiThis matters because default curl behavior does not treat HTTP 4xx or 5xx responses as shell failure. If the transfer succeeds at the transport level, your script can appear “successful” even though the application request failed.
Why --fail is not always enough
Traditional --fail is useful, but curl documents that it suppresses the response body. That is exactly the problem in many CI and deployment workflows. When an API returns:
- a useful validation error
- a JSON problem detail
- a stack trace in staging
- a permission message from an internal service
you often need that body to understand the failure quickly.
--fail-with-body keeps the useful evidence while still giving your script a non-zero exit.
A practical CI example
Suppose your deploy script hits a health endpoint or internal release API:
curl --fail-with-body --silent --show-error https://api.example.com/releaseNow you get:
- proper shell failure on HTTP errors
- the actual response body for debugging
- cleaner logs than default noisy progress output
That is much better than writing a half-custom wrapper around status codes and temporary files unless you truly need more advanced handling.
Why this is such a big scripting improvement
Bad shell automation often comes from mixing up three different outcomes:
- network failure
- HTTP application failure
- successful response with an unsuccessful payload meaning
--fail-with-body does not solve all three, but it does fix one of the most annoying gaps: making HTTP error codes behave like errors while still surfacing the response content.
That is exactly what a lot of CI jobs and admin scripts were missing for years.
The important limitation
The curl documentation also notes that this option is mutually exclusive with --fail, and that --fail itself is not fail-safe in every authentication-related scenario. That means you should still think clearly about what constitutes failure in your system.
This flag improves the default shell contract. It does not replace all application-aware validation.
If your API can return a 200 with a semantic failure encoded in JSON, you still need to check that yourself.
Why it pairs well with --output
This option becomes especially useful when you want to preserve the body in a file for later inspection:
curl --fail-with-body --output response.json https://example.com/apiNow the command both saves the body and returns a failing exit code on HTTP errors.
That is a much saner default for automation than pretending the body and the exit code should always move in opposite directions.
The biggest mistake to avoid
Do not keep writing scripts that only check curl’s exit code without understanding whether the command treats HTTP errors as failures. That is how pipelines quietly pass after a 404 or 500 and only fail much later in a more confusing place.
The point of this flag is not cleverness. It is honesty.
Your script should fail when the server says the request failed.
Final recommendation
If your shell scripts call HTTP endpoints and you still want the error body when something breaks, switch from default curl behavior or bare --fail to curl --fail-with-body. It is one of the simplest upgrades you can make to CI and operational debugging.