521 Web Server Is Down
Server error response defined by developers.cloudflare.com.
Last updated July 27, 2026
Common causes at a glance
- The web server process is stopped, crashed, or failed to restart after a deploy
- A firewall or security plugin rejecting Cloudflare IP ranges with REJECT
- The server is listening on the wrong port for the zone's SSL/TLS mode
- The service bound to 127.0.0.1 only, so external connections are refused
Reproduce it
curl -sS --connect-timeout 5 --resolve www.example.com:443:203.0.113.10 https://www.example.com/What 521 Web Server Is Down means
The distinction between 521 and 522 is the
whole point of having two codes, and it maps onto two network behaviours. 521
is ECONNREFUSED: your host received the SYN and answered with a
RST. Something is home, it just said no. 522 is silence — the SYN vanished.
Refusal is a dead process or a REJECT rule; silence is a
DROP rule.
Cloudflare adds a wrinkle: the port it dials depends on your SSL/TLS mode. Flexible connects to port 80, Full and Full (strict) to 443. Switching mode without opening the matching port produces an instant 521 on an otherwise healthy server.
Where it comes from
521 has no RFC. It does not appear in the IANA registry and is defined only by Cloudflare’s documentation. Under RFC 9110 §15 a client that does not recognise 521 treats it as 500, which is why some tools report a generic server error instead. Because the edge synthesises this response after its connection was rejected, your origin never processed a request and never logged one. People waste real time grepping application logs for a 521 that by definition cannot be there; the evidence is in firewall counters and process state.
What actually causes it
The banal cause is the most common: the application is not
running. A failed deploy, an OOM kill, or a unit that did not come back after a
reboot all present identically. The second cause is Cloudflare-specific and
recurs forever, because Cloudflare’s IP ranges change occasionally
and hardcoded allowlists go stale. Anything keeping its own list — a WAF
appliance, an .htaccess file, fail2ban, a security group — will
eventually reject a range added after someone copied it. The bind-address trap
catches containerised apps: a service on 127.0.0.1:8080 works in a
local shell and refuses everything from outside.
How to debug it
Confirm the refusal from a machine outside the network. A refused connection fails in milliseconds, which is itself the diagnostic — a hang means you are looking at 522, not 521:
curl -sv --connect-timeout 5 --resolve www.example.com:443:203.0.113.10 \
https://www.example.com/ 2>&1 | grep -E 'Connected|refused|Failed'
On the origin, check what is bound where with ss -ltnp and read
the address column carefully — a 127.0.0.1 binding is the answer
more often than people expect. Then dump firewall rules looking for a stale
Cloudflare allowlist, and confirm the
record is actually proxied and points at the host you are
testing. The DNS tool shows what it resolves to today.
Working examples
curl
# A refusal returns almost instantly. A hang means 522, not 521.
curl -sv --connect-timeout 5 \
--resolve www.example.com:443:203.0.113.10 \
https://www.example.com/ 2>&1 | grep -E 'Connected|refused|Failed'
# Check both ports: Flexible mode dials 80, Full and Full (strict) dial 443.
nc -vz 203.0.113.10 80
nc -vz 203.0.113.10 443
Python (requests)
import requests
from requests.exceptions import ConnectionError
# Reproduce the refusal the edge is seeing, straight at the origin IP.
try:
requests.get(
"https://203.0.113.10/",
headers={"Host": "www.example.com"},
verify=False,
timeout=5,
)
except ConnectionError as exc:
# "Connection refused" -> 521. A read timeout instead -> 522.
print("refused:", "refused" in str(exc).lower())
# What the visitor actually gets back through Cloudflare.
print(requests.get("https://www.example.com/", timeout=10).status_code)
Node (fetch)
// fetch() surfaces the edge's 521 as a normal response.
const res = await fetch("https://www.example.com/");
console.log(res.status); // 521
// Probe the origin directly to classify the failure.
import net from "node:net";
const sock = net.connect({ host: "203.0.113.10", port: 443 });
sock.setTimeout(5000);
sock.on("connect", () => { console.log("port open — look elsewhere"); sock.destroy(); });
sock.on("timeout", () => { console.log("silent drop — this is a 522"); sock.destroy(); });
sock.on("error", (e) => console.log(e.code)); // ECONNREFUSED -> 521Reference and tooling
The authoritative list of assigned status codes is theIANA HTTP Status Code Registry. For a searchable copy with the semantics summarised, use theHTTP status code reference on this site. To see exactly what a server is sending, theheader inspector shows the raw status line and response headers, and thecurl builder assembles the request flags for you.
Other 5xx codes
- 500 Internal Server Error
- 501 Not Implemented
- 502 Bad Gateway
- 503 Service Unavailable
- 504 Gateway Timeout
- 505 HTTP Version Not Supported
- 506 Variant Also Negotiates
- 507 Insufficient Storage
- 508 Loop Detected
- 510 Not Extended
- 511 Network Authentication Required
- 520 Web Server Returns an Unknown Error
- 522 Connection Timed Out
- 523 Origin Is Unreachable
- 524 A Timeout Occurred
- 525 SSL Handshake Failed
- 526 Invalid SSL Certificate
- 530 Unable to Resolve Origin Hostname