When an API becomes busy or temporarily unavailable, the server may not want clients to retry immediately. It may need a few seconds to recover, reduce traffic, or wait for a rate-limit window to reset. The HTTP Retry-After header exists for this purpose.
Retry-After gives an API server a way to tell the client when it should try the request again. It is especially useful with rate limiting, temporary service overload, maintenance windows, and some server-side failures.
The important part is that Retry-After is guidance from the server. A client that understands it can avoid sending another request too early. A client that ignores it and retries immediately can add more traffic to a service that is already struggling.
What Is the HTTP Retry-After Header?
Retry-After is an HTTP response header that tells a client how long it should wait before making another request.
A server can return it like this:
HTTP/1.1 429 Too Many Requests
Retry-After: 10
The value 10 means the client should wait 10 seconds before retrying.
The header can also contain an HTTP date:
Retry-After: Wed, 12 Nov 2026 10:30:00 GMT
In this form, the server provides a specific time after which the client can retry.
So Retry-After has two basic forms:
Retry-After: 10
or:
Retry-After: <HTTP-date>
The first represents a delay in seconds. The second represents a point in time.
Why Retry-After Matters
Without Retry-After, a client may use its own retry strategy.
For example:
Request fails
↓
Wait 200 ms
↓
Retry
↓
Fails again
↓
Wait 400 ms
↓
Retry
That approach can work when the client is dealing with a temporary network problem.
However, the server may know something the client does not.
For example, the server may be rate limiting requests for the next 15 seconds:
Client → Request
Server → 429 + Retry-After: 15
If the client ignores that information and retries after 200 milliseconds, it is likely to receive another 429 response.
A server-provided delay can therefore be more useful than a client guessing how long to wait.
Retry-After With HTTP 429
The most common use case is rate limiting.
A server may allow an application to make a certain number of requests during a time window.
For example:
100 requests per minute
Once the limit is reached, the server can respond:
HTTP/1.1 429 Too Many Requests
Retry-After: 20
This tells the client that it should wait before trying again.
The correct client behavior is roughly:
Receive 429
↓
Read Retry-After
↓
Wait for the specified period
↓
Send request again
This prevents the client from continuously hitting the rate limit.
Retry-After Is Not a Guarantee of Success
A common misunderstanding is treating Retry-After as a promise that the next request will succeed.
It is not.
Suppose the server responds:
HTTP/1.1 429 Too Many Requests
Retry-After: 10
After waiting 10 seconds, the client may still receive another 429.
The server could still be busy, another client could have consumed the available capacity, or the rate limit could have changed.
Therefore, Retry-After should be part of a broader retry policy.
A client should still have:
A maximum retry count
A reasonable timeout
Backoff handling
Jitter where appropriate
Error classification
Logging and monitoring
Retry-After tells the client when to try. It does not remove the need for a retry strategy.
Retry-After With 503 Service Unavailable
Retry-After can also be used with a 503 response.
For example:
HTTP/1.1 503 Service Unavailable
Retry-After: 30
This can indicate that the service expects the client to wait before trying again.
The difference from a 429 is important.
A 429 generally indicates that the client has exceeded a rate limit.
A 503 generally indicates that the service is temporarily unable to handle the request.
In both cases, Retry-After can provide useful timing information.
The client should still determine whether retrying the operation is appropriate.
Retry-After Can Use an HTTP Date
Instead of specifying a number of seconds, the server can provide a date.
For example:
Retry-After: Wed, 12 Nov 2026 10:30:00 GMT
The client must calculate the amount of time between the current time and the specified date.
Conceptually:
Server says:
Retry at 10:30:00
Current time:
10:29:45
Calculated delay:
15 seconds
This introduces a practical issue: clock differences.
The client's system clock and the server's clock may not be perfectly synchronized.
For this reason, clients should handle HTTP-date values carefully and avoid assuming that the calculated delay will always be exact.
If the calculated delay is negative because the date has already passed, the client should generally treat the delay as zero rather than waiting for a negative duration.
A Simple .NET Client Example
Using HttpClient, the response headers can be inspected directly.
using var response = await httpClient.GetAsync(url);
if ((int)response.StatusCode == 429)
{
if (response.Headers.RetryAfter?.Delta is TimeSpan delay)
{
await Task.Delay(delay);
}
}
This handles the delta-based Retry-After form.
However, production code should not assume that the header is always present.
A server may return 429 without Retry-After, or the header may use a date instead of a delay.
A more complete approach can inspect both forms.
var retryAfter = response.Headers.RetryAfter;
if (retryAfter?.Delta is TimeSpan delay)
{
await Task.Delay(delay);
}
else if (retryAfter?.Date is DateTimeOffset retryAt)
{
var delayFromDate = retryAt - DateTimeOffset.UtcNow;
if (delayFromDate > TimeSpan.Zero)
{
await Task.Delay(delayFromDate);
}
}
This gives the client a way to support both forms.
Do Not Trust Retry-After Without a Maximum
Even though Retry-After comes from the server, a client should avoid blindly waiting for an unlimited amount of time.
Imagine a server responds with:
Retry-After: 3600
That means one hour.
Whether waiting an hour makes sense depends on the application.
For a background synchronization task, waiting may be acceptable.
For a user-facing API request, waiting an hour inside an HTTP request handler would probably be a poor design.
A client can impose a maximum retry delay:
var maxDelay = TimeSpan.FromSeconds(30);
if (retryAfter?.Delta is TimeSpan delay)
{
delay = delay > maxDelay ? maxDelay : delay;
await Task.Delay(delay);
}
The appropriate maximum depends on the application.
The important point is that retry policy should have boundaries.
Combine Retry-After With Exponential Backoff
Retry-After does not mean that exponential backoff is no longer useful.
Consider a client receiving a temporary server failure without Retry-After.
The client can use its normal backoff:
Attempt 1
↓
Wait 200 ms
↓
Attempt 2
↓
Wait 400 ms
↓
Attempt 3
If the server provides Retry-After, the client can use the server's guidance instead.
A practical strategy can be:
Retry-After present?
↓
Yes → use server-provided delay
↓
No → use exponential backoff
This allows the server to control retry timing when it has information about its current state.
Add Jitter When Many Clients Retry
Even when Retry-After is present, many clients may receive the same value.
Suppose 50,000 clients receive:
Retry-After: 10
If every client retries exactly 10 seconds later, the server may receive another large traffic spike.
This is where jitter can help.
Instead of:
Retry after exactly 10 seconds
the client can use a small randomized variation:
Client A → 9.6 seconds
Client B → 10.2 seconds
Client C → 10.7 seconds
Client D → 9.9 seconds
The exact jitter strategy should be chosen carefully because the server's Retry-After value should still be respected.
A client should not intentionally retry significantly earlier than the server's suggested time just to spread traffic.
One reasonable approach is to treat Retry-After as the minimum delay and add a small random amount.
Do Not Retry Non-Transient Errors
Retry-After is useful only when retrying makes sense.
For example, if an API returns:
400 Bad Request
repeating the same invalid request will generally not solve the problem.
Similarly, authentication failures usually require a credential or authorization change rather than another identical request.
A retry policy should therefore distinguish between:
Transient failure
↓
Retry may help
Permanent/request failure
↓
Retry probably will not help
This prevents unnecessary traffic and makes failures easier to diagnose.
Handle Retry-After in API Client Libraries
If your application communicates with several APIs, it is better to centralize retry behavior rather than implementing it independently in every service method.
For example:
Application code
↓
HTTP client
↓
Retry policy
↓
External API
Application code should ideally be able to call:
await client.GetAsync("/orders");
without having to implement rate-limit handling in every method.
A shared HTTP client policy can handle:
Retryable status codes
Retry-After
Backoff
Jitter
Attempt limits
Logging
Timeout behavior
This makes behavior more consistent across the application.
Be Careful With User-Facing Requests
Retry behavior that works for background jobs may not work for interactive requests.
Suppose a user clicks:
Create Order
and the API returns:
429 Too Many Requests
Retry-After: 30
Keeping the user's HTTP request open for 30 seconds just to retry may produce a poor experience.
Instead, the application may return an appropriate response to the user or queue the work for later processing.
Background workloads have more flexibility:
Queue job
↓
Receive 429
↓
Read Retry-After
↓
Delay
↓
Retry
Interactive requests require more careful timeout and user-experience decisions.
Retry-After and Background Workers
Background workers are a good use case for server-guided retry timing.
For example:
while (!cancellationToken.IsCancellationRequested)
{
var response = await client.GetAsync(url, cancellationToken);
if ((int)response.StatusCode == 429)
{
var delay = GetRetryAfter(response);
await Task.Delay(delay, cancellationToken);
continue;
}
break;
}
The worker can wait without blocking a user request.
However, the worker should still have a maximum retry policy.
A continuously failing dependency should not result in an infinite loop that retries forever.
Retry-After and Rate-Limit Headers
Some APIs provide additional rate-limit information alongside Retry-After.
For example:
HTTP/1.1 429 Too Many Requests
Retry-After: 20
X-RateLimit-Remaining: 0
The exact header names vary between APIs.
If the API documents rate-limit information, clients can use it to make better decisions.
For example:
Remaining requests = 0
Retry-After = 20 seconds
The client knows that continuing to send requests immediately is unlikely to help.
This can also be useful for reducing unnecessary error logging and network traffic.
Common Mistakes
Ignoring Retry-After
A client may implement exponential backoff but completely ignore the server's retry guidance.
When Retry-After is available, it should normally be considered by the retry policy.
Retrying Immediately After 429
Sending another request immediately after receiving 429 defeats the purpose of rate limiting.
Wait before retrying.
Assuming Retry-After Always Exists
Not every server provides it.
Your client should have a fallback retry strategy.
Waiting Forever
A server can provide a very long retry delay.
Apply sensible limits based on the application.
Retrying Unsafe Operations
Waiting before retrying does not make an operation safe to repeat.
Consider idempotency before retrying state-changing operations.
Retrying Inside User Requests for Too Long
A long Retry-After value may not belong inside an interactive HTTP request lifecycle.
Consider asynchronous processing instead.
Ignoring Cancellation
A background worker waiting for Retry-After should still respond to application shutdown and cancellation.
For example:
await Task.Delay(delay, cancellationToken);
This allows the process to stop cleanly.
Best Practices
Treat Retry-After as server guidance, not an optional header to ignore.
Support both delay-based and HTTP-date-based values.
Use Retry-After for appropriate 429 and 503 responses.
Keep retry counts bounded.
Apply a sensible maximum delay.
Use exponential backoff when Retry-After is unavailable.
Add controlled jitter when many clients may retry together.
Make sure the operation is safe to repeat before retrying.
Centralize retry behavior where possible.
Do not keep interactive HTTP requests waiting for long retry periods.
Allow background workers to respect cancellation while waiting.
Monitor 429 responses, retry counts, retry delays, and repeated failures.
A Practical Retry Decision Flow
A production API client can follow a decision process like this:
Request fails
↓
Is the error retryable?
↓
No ──→ Return failure
↓
Yes
↓
Does Retry-After exist?
↓
Yes ──→ Calculate server-provided delay
↓
No ──→ Calculate exponential backoff
↓
Apply retry limits
↓
Check whether operation is safe to repeat
↓
Wait
↓
Retry
This is much safer than simply writing a loop around an HTTP request.
The retry policy becomes a controlled part of the client's resilience design rather than an emergency response added after failures begin.
Summary
Retry-After lets an API tell clients when they should try again, which is especially useful for rate limiting and temporary service failures.
A good client should support both Retry-After formats, respect server guidance, keep retries bounded, and use backoff when the server does not provide a delay.
The goal is simple: when a server asks clients to slow down, the client should actually slow down instead of becoming part of the outage.
Join the conversation! Your thoughts help the community grow.