Transient timeouts, HTTP 503s, rate-limit responses, and short startup races can make healthy automation fail when cURL gives up after the first attempt. A small retry policy lets read-only checks, CI probes, and scheduled fetches ride through brief faults without hiding a real outage.
Use --retry to repeat a transfer when cURL hits a transient error. In current curl builds, that means timeouts, FTP 4xx responses, and HTTP 408, 429, 500, 502, 503, 504, 522, and 524. --retry-delay sets a fixed wait instead of the default exponential backoff, --retry-max-time caps the overall retry budget, and --retry-connrefused treats a refused TCP connection as retryable too.
Keep retries for requests that are safe to repeat, such as GET, HEAD, or other duplicate-safe calls. Pair them with --fail when you want the command to return a non-zero exit code if the last HTTP response is still an error, and reserve --retry-all-errors for tightly controlled cases because it also retries permanent failures and can duplicate redirected input or output.
$ python3 - <<'PY'
import http.server
import socketserver
state = {"hits": 0}
class ReuseTCPServer(socketserver.TCPServer):
allow_reuse_address = True
class Handler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/flaky":
state["hits"] += 1
if state["hits"] < 3:
body = f"status=temporarily_unavailable attempt={state['hits']}\n".encode()
self.send_response(503)
self.send_header("Content-Type", "text/plain; charset=utf-8")
self.send_header("Content-Length", str(len(body)))
self.send_header("Retry-After", "1")
self.end_headers()
self.wfile.write(body)
return
body = b"status=ok retry_recovered=true\n"
self.send_response(200)
self.send_header("Content-Type", "text/plain; charset=utf-8")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
return
if self.path == "/always-503":
body = b"status=temporarily_unavailable\n"
self.send_response(503)
self.send_header("Content-Type", "text/plain; charset=utf-8")
self.send_header("Content-Length", str(len(body)))
self.send_header("Retry-After", "1")
self.end_headers()
self.wfile.write(body)
return
self.send_response(404)
self.end_headers()
def log_message(self, format, *args):
return
with ReuseTCPServer(("127.0.0.1", 18081), Handler) as httpd:
httpd.serve_forever()
PY
Leave this process running in its own terminal while the retry checks run, then stop it with Ctrl+C when you finish.
$ curl --silent --show-error --fail --retry 2 --retry-delay 1 --retry-max-time 5 --output /dev/null --write-out "response_code=%{response_code}\nexitcode=%{exitcode}\n" http://127.0.0.1:18081/flaky
curl: (22) The requested URL returned error: 503
curl: (22) The requested URL returned error: 503
response_code=200
exitcode=0
The failed attempts stay visible, but the final 200 and exit code 0 confirm that cURL recovered before the retry budget expired. When the server sends Retry-After, cURL uses that header to schedule the next retry.
$ /usr/bin/time -p curl --show-error --fail --retry 5 --retry-delay 1 --retry-max-time 2 --no-progress-meter --output /dev/null http://127.0.0.1:18081/always-503 curl: (22) The requested URL returned error: 503 Warning: Problem : HTTP error. Will retry in 1 seconds. 5 retries left. curl: (22) The requested URL returned error: 503 Warning: Problem : HTTP error. Will retry in 1 seconds. 4 retries left. curl: (22) The requested URL returned error: 503 real 2.02 user 0.00 sys 0.00
Even though --retry 5 allows more attempts, --retry-max-time 2 ends the loop after about two seconds. That timer covers the retry window, not the runtime of a single in-flight request, so use --max-time as well if each attempt also needs a hard ceiling.
$ curl --show-error --fail --retry 2 --retry-delay 1 --retry-max-time 4 --retry-connrefused --no-progress-meter --output /dev/null http://127.0.0.1:18082/ curl: (7) Failed to connect to 127.0.0.1 port 18082 after 0 ms: Couldn't connect to server Warning: Problem : connection refused. Will retry in 1 seconds. 2 retries left. curl: (7) Failed to connect to 127.0.0.1 port 18082 after 0 ms: Couldn't connect to server Warning: Problem : connection refused. Will retry in 1 seconds. 1 retries left. curl: (7) Failed to connect to 127.0.0.1 port 18082 after 0 ms: Couldn't connect to server
Keep connection-refused retries short. They are useful for brief startup races, but a wrong port, dead host, or bad service name should still fail quickly and visibly.