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.
Steps to configure retries for transient errors with cURL:
- Start a disposable local endpoint that returns 503 twice, then 200, plus a second path that always returns 503.
$ 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() PYLeave this process running in its own terminal while the retry checks run, then stop it with Ctrl+C when you finish.
- Retry a safe GET with a fixed attempt count, a short delay, and a bounded retry window so a brief 503 can recover cleanly.
$ 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=0The 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.
- Cap the total retry window so a persistent outage stops quickly even if the retry count is higher.
$ /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.
- Add --retry-connrefused when the target service may not have opened its port yet, such as during container startup or a rolling restart.
$ 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.
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.
