What causes a 503 error
- Overload. More traffic than the servers can take, from a spike, a slow database or too few app servers.
- Maintenance. Many sites answer 503 on purpose while they deploy or migrate, often with
Retry-After. - No healthy servers behind the load balancer. When every backend fails its health check, the load balancer has nowhere to send the request and answers 503.
- Rate limiting. Some servers use 503 instead of 429 for rate limits. nginx's
limit_reqmodule, for example, rejects excess requests with 503 unlesslimit_req_statusis changed. - A gateway that can't complete the request. A CDN or proxy between you and the site can answer 503 when it can't reach a working server.
Did the proxy or the site send the 503?
For an https:// URL your client first asks the proxy to open a tunnel with a CONNECT request. A status sent in reply to CONNECT, before the tunnel opens, comes from the proxy, and nothing reached the site; Python requests raises a ProxyError for it instead of returning a response. Once the tunnel is open, the proxy only relays encrypted bytes, so a status that comes back as a normal response came from the site's side: its server, load balancer or CDN.
The headers and body help too: a maintenance page, a Server header naming the site's CDN, or a Retry-After value all point at the site.
A quick test: request a simple endpoint such as https://api.ipify.org through the same line. If that works and the site still answers 503, the site is the one that is unavailable. If the site also answers 503 without the proxy, the proxy isn't involved at all.
How to fix a 503 error
As a visitor or client
- Wait and retry. Honour
Retry-Afterif it is there; otherwise back off exponentially. - Check the site's status page or announcements for an outage or maintenance notice.
- If the 503s appear only when you send many requests, treat them as a rate limit and lower your concurrency.
- Don't retry in a tight loop. Extra requests to an overloaded server make the outage worse for everyone.
As the site owner
- Find which layer answered: CDN, load balancer, web server or app. Each logs it in a different place.
- Check backend health checks and capacity, and slow dependencies such as the database.
- During planned maintenance, answer 503 with
Retry-After, so clients and crawlers know to come back later. - If you use 503 for rate limiting, consider 429 instead, which tells clients exactly what happened.
Retry a 503 with backoff: curl and Python
# --retry repeats on timeouts and on 408, 429, 500, 502, 503 and 504 responses.
# curl 7.66.0 and later wait for Retry-After when the server sends one;
# otherwise the wait starts at 1 second and doubles after each try.
curl --retry 5 --retry-max-time 300 -x "http://USERNAME:PASSWORD@HOST:PORT" https://example.com/import random
import time
from datetime import datetime, timezone
from email.utils import parsedate_to_datetime
import requests
RETRY_STATUSES = {429, 502, 503, 504}
def retry_after_seconds(value):
"""Seconds to wait from a Retry-After header: a number of seconds or an HTTP date."""
if not value:
return None
value = value.strip()
if value.isdigit():
return int(value)
try:
when = parsedate_to_datetime(value)
return max(0.0, (when - datetime.now(timezone.utc)).total_seconds())
except (TypeError, ValueError):
return None
def get_with_backoff(url, proxies=None, attempts=5, max_wait=120):
response = None
for attempt in range(attempts):
response = requests.get(url, proxies=proxies, timeout=30)
if response.status_code not in RETRY_STATUSES or attempt == attempts - 1:
break
wait = retry_after_seconds(response.headers.get("Retry-After"))
if wait is None:
wait = min(60, 2 ** attempt) + random.uniform(0, 1) # exponential backoff with jitter
if wait > max_wait:
break # a longer pause than this job allows: stop and come back later
time.sleep(wait)
return response
proxy = "http://USERNAME:PASSWORD@HOST:PORT"
r = get_with_backoff("https://example.com/", proxies={"http": proxy, "https": proxy})
print(r.status_code)The function retries only 429, 502, 503 and 504, and raises anything else to you. A requests.exceptions.ProxyError (a refused CONNECT, or a proxy that can't be reached) is raised too, not retried: if it says 407, retrying the same credentials can't succeed.
503 errors when you use proxies
A 503 from the site is about the site's capacity, not your IP address. Switching exits won't help an overloaded server recover, so slow down and retry with backoff.
When only a few requests fail, a rotating line gives the next request a different exit, which helps if the trouble was on one exit's side. If every exit gets a 503 from the same site, the site is the problem.
Before blaming the site, rule out your account: check in your dashboard that the product still has bandwidth, and test the line against a simple endpoint as above.
503 Service Unavailable: frequently asked questions
What does HTTP error 503 mean?
Service Unavailable: the server can't handle the request right now, usually because of overload or maintenance. It is meant to be temporary.
How long does a 503 error last?
It depends on the cause. Maintenance usually ends on schedule, and the server may say when in a Retry-After header. Overload can clear in seconds or last as long as an outage. Retry with growing waits rather than constantly.
Is 503 the same as 502 or 504?
No. 503 means the server knows it can't serve you right now. 502 means a gateway got an invalid response from the server behind it, and 504 means the gateway gave up waiting for one.
Can a 503 be a rate limit?
Yes. Some servers, nginx's request limiting for example, answer 503 instead of 429 when a client sends too much. If 503s appear only when you send many requests, slow down.