curl --retry: retry transient errors (and what it does not retry)
- tool
- curl
- command
- curl
- flag
- --retry <num>
- default
- 0 (no retries)
curl --retry 3 URL repeats a failed transfer up to three more times, but only for errors curl considers transient: a timeout, an FTP 4xx reply, or HTTP 408, 429, 500, 502, 503, 504, 522 or 524. A 404, a refused connection or a DNS failure is not retried unless you add --retry-all-errors or --retry-connrefused. Between attempts curl waits one second, then doubles the wait (exponential backoff) up to 10 minutes; --retry-delay N replaces the backoff with a fixed delay.
Without and with --retry
The local server answers 503 twice, then 200:
node -e 'const c = {}; require("http").createServer((q, r) => { c[q.url] = (c[q.url] || 0) + 1; if (c[q.url] < 3) { r.statusCode = 503; return r.end("busy\n"); } r.end("ok\n"); }).listen(5344, "127.0.0.1"); setTimeout(() => process.exit(0), 9000)' &
sleep 1
echo "== no retry"
curl --no-progress-meter http://127.0.0.1:5344/a; echo "exit: $?"
echo "== --retry 3"
curl --no-progress-meter --retry 3 --retry-delay 1 http://127.0.0.1:5344/b; echo "exit: $?"
echo "== -sSf, saved to a file"
curl -sSf --retry 3 --retry-delay 1 -o out.txt -w '%{http_code} after %{num_retries} retries\n' http://127.0.0.1:5344/c; echo "exit: $?"; cat out.txt
waitOutput
== no retry busy exit: 0 == --retry 3 busy Warning: Problem : HTTP error. Retrying in 1 second. 3 retries left. busy Warning: Problem : HTTP error. Retrying in 1 second. 2 retries left. ok exit: 0 == -sSf, saved to a file curl: (22) The requested URL returned error: 503 curl: (22) The requested URL returned error: 503 200 after 2 retries exit: 0 ok
Two details are visible here. Without -f, the 503 body of each failed attempt is written to standard output before the retry, so piped output can contain the error pages followed by the real answer; with -o, curl discards the failed data and the file contains only the final body. And -s hides the "Warning: Problem : HTTP error" lines, while -S still shows each curl: (22) error.
What gets retried
| Failure | --retry | + --retry-connrefused | + --retry-all-errors |
|---|---|---|---|
| Timeout (exit 28) | yes | yes | yes |
| HTTP 408, 429, 500, 502, 503, 504, 522, 524 | yes | yes | yes |
| Connection refused (exit 7) | no | yes | yes |
| Could not resolve host (exit 6) | no | no | yes |
| HTTP 404 and other 4xx | no | no | only with --fail |
An HTTP error is a successful transfer for curl unless --fail is set, which is why --retry-all-errors alone does not retry a 404.
Related options
| Option | Effect |
|---|---|
| --retry-delay SECONDS | Fixed wait between attempts instead of backoff; decimals from curl 8.16.0 |
| --retry-max-time SECONDS | Stop starting new retries once this much time has passed |
| --retry-connrefused | Also retry ECONNREFUSED, for example a service still starting |
| --retry-all-errors | Retry on any error; risky with uploads or piped output |
| --max-time SECONDS | Limit each attempt, so a hung request becomes a retryable timeout |
| -w %{num_retries} | Print how many retries were done (curl 8.9.0 and later) |
Waiting for a service in CI
curl -fsS --retry 10 --retry-delay 2 --retry-connrefused --max-time 5 \
http://127.0.0.1:8080/healthThis polls a health endpoint for roughly 20 seconds while the service starts, fails fast per attempt, and exits with the last error code (see curl exit codes) if it never comes up. Errors such as Failed to connect: Connection refused are retried only because of --retry-connrefused.
FAQ
Does curl --retry retry a 404?
No. Only timeouts, FTP 4xx and HTTP 408, 429, 500, 502, 503, 504, 522 and 524 count as transient. To retry every HTTP error, combine --retry with --fail and --retry-all-errors.
Why does curl not retry when the server is down?
A refused connection is not transient by default. Add --retry-connrefused, or --retry-all-errors, which covers every failure.
How long does curl wait between retries?
One second before the first retry, then double each time up to 10 minutes. --retry-delay N sets a fixed delay, and a Retry-After header from the server overrides both. --retry-max-time caps the total.