Scripts that treat an error page as success silently ship broken data; correct failure handling prevents that.
A surprise for many: curl considers an HTTP error response a successful transfer. curl https://api.example.com/missing returns exit code 0 while printing the 404 body.
To make HTTP errors fail:
-f/--failmakes curl exit with code 22 for status 400 and above and suppresses the body.--fail-with-body(7.76+) also exits 22 but still outputs the body, which usually contains the useful error message from the API. Prefer it in scripts.
Other exit codes worth recognising:
- 6 could not resolve host (DNS), 7 failed to connect (nothing listening or firewall),
- 28 operation timed out, 35 TLS handshake problem, 60 certificate could not be verified,
- 56 failure receiving data (connection reset).
man curl lists all of them under EXIT CODES, and with -sS curl prints a one-line explanation such as curl: (7) Failed to connect....
A robust script pattern is curl -sS --fail-with-body ... inside set -euo pipefail, so a failure stops the script with a readable error. When you need to branch on the status rather than fail, capture it with -w '%{http_code}', covered next. Without pipefail, curl ... | jq reports jq's exit status and hides curl's failure.
Example
curl -s https://api.example.com/missing; echo "exit $?"
# {"error":"not found"} exit 0 <- looks like success!
curl -sf https://api.example.com/missing; echo "exit $?"
# exit 22 (no body)
curl -sS --fail-with-body https://api.example.com/missing; echo "exit $?"
# {"error":"not found"} exit 22
curl -sS http://127.0.0.1:1/; echo "exit $?"
# curl: (7) Failed to connect to 127.0.0.1 port 1 ... exit 7
set -euo pipefail
curl -sS --fail-with-body https://api.example.com/items | jq '.items | length'Remember
- HTTP 4xx/5xx is exit 0 unless you pass --fail or --fail-with-body
- Exit 6 DNS, 7 connect, 22 HTTP error, 28 timeout, 60 certificate
- Use set -o pipefail when piping curl into other tools
Watch out for
- Using -f in scripts and then wondering why the API's error message is missing; use --fail-with-body.