Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
Use Net::HTTP#max_retries= for a small, standard-library policy that retries documented transport failures on idempotent requests. Use Faraday’s retry middleware when you also need selected HTTP-status retries, backoff, jitter, or Retry-After handling. In either case, bound the number of retries and do not automatically repeat a non-idempotent operation such as an order or payment unless the API provides idempotency protection.
What a “failed request” actually means
A request can fail before the server receives it, while the response is being read, or after the server has already applied the operation but before your client sees the response. A timeout therefore does not prove that nothing happened. Retrying a POST that creates an account, submits an order, or charges a card can duplicate the side effect.
HTTP idempotence means that repeating a request has the same intended effect as making it once. Safe methods, plus PUT and DELETE, are defined as idempotent by HTTP Semantics (IETF RFC 9110, Section 9.2.2: https://www.rfc-editor.org/rfc/rfc9110.html). The RFC says a client SHOULD NOT automatically retry a request with a non-idempotent method
unless it knows the semantics are actually idempotent or can detect that the original request was never applied.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Scan for outdated or missing drivers - takes under a minute3Clear out junk files and repair common Windows errors- Retry automatically when the operation is safe to repeat and the failure is plausibly transient.
- For a
POST, retry only when the API documents an idempotency key, a request token, or another way to prove that repeating it is safe. - Do not treat a permanent response such as invalid input or failed authentication as a transient network problem.
Option 1: Net::HTTP’s built-in retries
Ruby’s Net::HTTP has a max_retries= setting. The current master documentation and the Ruby 3.2 documentation state that its initial value is 1; the setting is the maximum number of retries, not the total number of attempts. It applies to idempotent requests after the documented transport failures, including Net::ReadTimeout, IOError, EOFError, connection reset or abort errors, broken pipes, OpenSSL::SSL::SSLError, and Timeout::Error (Ruby current Net::HTTP API; Ruby 3.2 Net::HTTP API).
#1 Best Overall
This setting is not a blanket retry for HTTP status codes. A completed response such as 503 or 429 is not one of those transport exceptions, so your application must decide what to do with it.
Runnable GET example
require "net/http"
require "uri"
uri = URI("https://api.example.com/v1/profile")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = (uri.scheme == "https")
http.open_timeout = 5
http.read_timeout = 15
http.max_retries = 2 # two retries, up to three total attempts
request = Net::HTTP::Get.new(uri)
request["Accept"] = "application/json"
begin
response = http.request(request)
puts "#{response.code} #{response.body}"
rescue Net::ReadTimeout, IOError, EOFError, Timeout::Error => e
warn "request failed after retries: #{e.class}: #{e.message}"
exit 1
end
Keep max_retries non-negative. The value 2 above is an example policy, not a universal recommendation. Choose a limit that fits the operation’s latency budget and the remote service’s rate limits.
What Net::HTTP does not decide for you
- It does not know whether your particular
POSTis safe to repeat. - It does not turn every 4xx or 5xx response into a retry.
- It does not define what your caller receives after the final failed attempt; rescue the final exception and return or raise an application-level error.
Option 2: Faraday retry middleware
Faraday is the better fit when the application already uses Faraday or needs a policy around response statuses as well as exceptions. The faraday-retry middleware documents two retries by default, default retryable methods of GET, HEAD, OPTIONS, PUT, and DELETE, and configurable exception classes and retry statuses. Confirm option names against the version installed in your bundle; the project’s current source is at https://github.com/lostisland/faraday-retry/blob/main/lib/faraday/retry/middleware.rb.
Rank #2
Example with bounded exponential backoff
require "faraday"
conn = Faraday.new(url: "https://api.example.com") do |f|
f.request :retry,
max: 2,
interval: 0.1,
backoff_factor: 2,
max_interval: 2,
interval_randomness: 0.2,
retry_statuses: [429, 503]
f.adapter Faraday.default_adapter
end
begin
response = conn.get("/v1/profile")
puts "#{response.status} #{response.body}"
rescue Faraday::Error => e
warn "request failed after retries: #{e.class}: #{e.message}"
exit 1
end
Here, max: 2 means two retries after the initial request. The interval, backoff factor, cap, randomness, and status list are illustrative choices. Faraday’s middleware calculates increasing delays, caps them at max_interval, adds configured randomness, and considers a parsed Retry-After or rate-limit reset value when available. It can parse either an HTTP date or a delay in seconds, as specified by RFC 9110 Section 10.2.3: Servers send the “Retry-After” header field to indicate how long the user agent ought to wait before making a follow-up request.
Select statuses deliberately
The example retries 429 and 503 because those statuses commonly indicate throttling or temporary service unavailability. Do not describe this as automatic retrying of every 5xx response: the list is configurable, and a remote API may use a status differently. Treat authentication failures, malformed requests, validation errors, and authorization failures as permanent unless that API explicitly says otherwise.
Restrict methods and protect writes
Leave the middleware’s default method set for ordinary read and replacement operations, or configure methods explicitly in the version you use. A write that is technically a POST should be included only when the service supplies an idempotency key or another reliable way to detect that the first request was not applied. Otherwise, let the first failure reach the caller for reconciliation instead of guessing.
Rank #3
Net::HTTP or Faraday: which should you choose?
| Decision point | Net::HTTP | Faraday retry middleware |
|---|---|---|
| Dependency | Built into Ruby’s standard library | Requires Faraday and its retry middleware |
| Default scope | Documented idempotent transport failures | Configured exceptions plus configured response statuses |
| Methods | Ruby decides eligibility for its idempotent retry behavior | Defaults to GET, HEAD, OPTIONS, PUT, and DELETE; configurable |
| Retry-After | No status-policy layer is provided by max_retries= |
Parses Retry-After and combines it with interval and maximum-delay settings |
| Delay controls | No backoff policy exposed by this setting | Interval, backoff factor, maximum interval, and randomness |
| Exhausted outcome | Final exception or response is handled by your code | Final exception or response is handled by your code |
Use Net::HTTP when a small, dependency-free client only needs the standard idempotent transport retry behavior. Use Faraday when status-code policy, server-directed waiting, or explicit backoff and jitter are requirements.
Designing a safe retry policy
Bound attempts and total latency
Every retry consumes connection time, worker capacity, and rate-limit budget. Count the initial request separately from retries: two retries can produce three network attempts. Set connection and read timeouts, then cap the retry count and maximum delay so a request cannot occupy a worker indefinitely.
Use exponential growth with jitter
A fixed delay can synchronize many clients after an outage. Exponential growth spreads attempts over time; a cap prevents an unreasonably long wait. Jitter adds a small random component so a fleet does not wake up simultaneously. Faraday exposes these controls; with Net::HTTP, implement an outer policy only if you fully understand which errors the library already retries, otherwise you can accidentally double-retry.
Rank #4
Honor server guidance
When a response contains Retry-After, prefer the server’s requested wait, subject to a safety maximum. The value can be a number of seconds or an HTTP date. Faraday’s middleware handles this header; with a lower-level client, parse it carefully and reject unreasonable or past values.
Make the exhausted state explicit
- Return a typed failure or raise a documented exception that the caller can handle.
- Include the URL host, method, attempt count, elapsed time, final status (if any), and exception class in logs.
- Never log authorization headers, cookies, API keys, or sensitive request bodies.
- For an uncertain write, mark the result as “unknown” and reconcile with the provider rather than issuing an unprotected duplicate.
Troubleshooting common retry problems
“My 500 or 429 response was not retried.”
Net::HTTP’s setting covers listed transport failures, not arbitrary response statuses. In Faraday, add only the transient statuses appropriate for that API through retry_statuses, and verify that the middleware is installed before the adapter.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchPC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11“The request ran three times when I set two.”
That is the expected counting model: two retries plus the initial attempt. Check whether another library, job runner, or reverse proxy is also retrying.
Best Value
“A POST created two records.”
The operation was repeated after an ambiguous failure. Stop automatic retries for that method unless the API supports idempotency keys or another deduplication mechanism, then reconcile the original request before trying again.
“Retries make outages slower.”
Lower the retry count, set a maximum interval, and use exponential backoff with jitter. A bounded failure returned quickly is often safer than holding a web request open through many attempts.
“Faraday raises immediately.”
Check the configured retryable exception classes, method list, and middleware order. Also check whether the failure is a permanent TLS, authentication, or validation problem rather than a transient timeout.
Free tools Windows power users keep installed
One-click scans. No signup required.
“The final error has no useful context.”
Wrap the call at your application boundary. Record attempt number, elapsed time, selected policy, and the final exception or status, while redacting credentials and payloads. Return a stable error type to the caller so it can decide whether to queue, reconcile, or show an error.
Or skip the browser setup
If the work behind your Ruby job is taking website screenshots, you can avoid maintaining a browser, consent-banner selectors, and page-load retry code with ScreenshotNeo. It is a website screenshot API and MCP server; one GET request returns a PNG, JPEG, WebP, or PDF.
For a direct capture, see the parameter details in the ScreenshotNeo documentation:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Before the capture, ScreenshotNeo accepts the cookie or consent banner like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response reports the page verdict and billing result in X-Page-Verdict and X-Billed headers. Its MCP server lets Claude, Cursor, and other MCP clients call take_screenshot, get_page_info, and capture_pdf. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Create a free ScreenshotNeo account to get the 1,000 monthly screenshots without adding a card.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

