What 521 means
With Cloudflare in front of a site, every request travels in two legs: the visitor’s browser to Cloudflare, and Cloudflare to your server (the origin). Error 521, “Web server is down”, is about the second leg. Cloudflare tried to open a TCP connection to the origin and the origin said no: the connection was refused.
“Refused” has a precise meaning. Cloudflare’s connection attempt got an immediate rejection, usually a TCP reset, because nothing is listening on that port or a firewall rule rejects the connection. That’s different from silence, where the packets vanish and Cloudflare eventually gives up. Silence is 522.
The error page shows three boxes: browser working, Cloudflare working, host error. Despite the name, the server is not necessarily down. It’s only refusing Cloudflare on that port.
If you’re just visiting the site
There’s nothing to fix on your side. Reloading, clearing the cache, or switching browsers or networks won’t help, because the failure is between Cloudflare and the site’s server. Try again later. If you know the owner, send them the time and the Ray ID from the bottom of the error page.
Which Cloudflare error is it?
The 52x codes describe how far Cloudflare’s connection to the origin got:
| Code | Where it stopped |
|---|---|
| 521 | The origin refused the TCP connection (nothing listening, or a firewall rejecting it) |
| 522 | The TCP connection timed out (packets dropped, origin unreachable or overloaded) |
| 525 | TCP connected, but the TLS handshake failed |
| 526 | The handshake finished, but in Full (strict) the origin certificate was invalid |
| 520 | A connection was made, but the origin’s response was empty or something Cloudflare couldn’t parse |
So 521 tells you the network path to the origin’s address works. A machine at that address answered, and the answer was “no”.
Which port Cloudflare is knocking on
Before testing anything, work out which port Cloudflare uses, because a refusal on one port says nothing about the other. It depends on the SSL/TLS mode and on how the visitor arrived:
| SSL/TLS mode | Visitor uses HTTPS | Visitor uses HTTP |
|---|---|---|
| Flexible | origin port 80 | origin port 80 |
| Full / Full (strict) | origin port 443 | origin port 80 |
Two classic traps follow from this table. An origin that only listens on 443 gives 521 in Flexible mode. And an origin in Full mode with port 80 closed gives 521 only for http:// visitors, which looks random until you notice the scheme. Turning on “Always Use HTTPS” in Cloudflare redirects HTTP visitors at the edge, so they never hit port 80. Cloudflare SSL modes explains the modes in detail.
(Cloudflare also proxies some other ports, such as 8080 and 8443, and connects to the origin on the same port the visitor used. If your URLs use one of those, test that port.)
Step 1: is anything listening on the server?
Log in to the origin and check the web server and its sockets:
sudo systemctl status nginx # or apache2 / httpd / caddy / your app
sudo ss -ltnp '( sport = :80 or sport = :443 )'
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=812,fd=6))
LISTEN 0 511 0.0.0.0:443 0.0.0.0:* users:(("nginx",pid=812,fd=7))
LISTEN 0 511 [::]:443 [::]:* users:(("nginx",pid=812,fd=8))
Read the Local Address column carefully. 0.0.0.0 and [::] mean all IPv4 and IPv6 addresses. 127.0.0.1:80 means local connections only, which refuses everyone else, including Cloudflare. In this example nothing listens on IPv6 port 80, which matters if the domain has a proxied AAAA record (cause 3).
Then ask the server itself for the site, with the right Host header:
curl -sI -H 'Host: www.example.com' http://127.0.0.1/
curl -skI --resolve www.example.com:443:127.0.0.1 https://www.example.com/
If the service is dead, find out why before restarting it, or it’ll die again:
sudo journalctl -u nginx --since "2 hours ago" --no-pager | tail -50
sudo nginx -t
sudo journalctl -k --since today | grep -iE 'out of memory|oom-kill'
Step 2: can the outside reach it?
From a machine that isn’t the server, connect to the origin IP directly. The origin IP is the A or AAAA value in the Cloudflare DNS dashboard; public DNS only returns Cloudflare’s addresses for proxied records.
curl -v --resolve www.example.com:443:203.0.113.10 https://www.example.com/ -o /dev/null
nc -vz -w 5 203.0.113.10 80
curl: (7) Failed to connect to www.example.com port 443 after 4 ms: Connection refused
Newer curl versions may print “Couldn’t connect to server” instead; exit code 7 and the near-zero time are what matter. The port check does the same test from outside. Point it at the origin IP, not the hostname, because the hostname resolves to Cloudflare.
How to read it:
- Refused, quickly: you’ve reproduced 521. Either nothing listens (step 1 will have shown that) or a firewall rejects connections.
- Timeout: the packets are dropped, which from Cloudflare’s side would be 522, not 521. If your firewall only allows Cloudflare’s ranges, this is the expected result for your test machine and tells you nothing. Test from the server itself instead.
- Works from your machine but Cloudflare still gets 521: something treats Cloudflare’s addresses differently from yours. That’s cause 4.
The usual causes
1. The web server stopped and didn’t come back
Most 521s are this. Typical paths: a config edit followed by a restart that failed (nginx -t would have caught it), the kernel’s OOM killer taking out PHP-FPM or the app, a crash loop that systemd gave up on, or a reboot after which the service was never enabled.
Fix: correct the error that nginx -t or the journal shows, start the service, and make it survive a reboot with sudo systemctl enable --now nginx. If the OOM killer is involved, the server needs more memory or fewer workers; restarting only buys time.
2. It’s listening, but not where Cloudflare connects
- The app binds to
127.0.0.1(a common default for Node, Python and Go servers) instead of0.0.0.0. - A Docker container whose port was never published.
docker psshows0.0.0.0:80->80/tcpin the PORTS column when it is;80/tcpalone means it isn’t. - The app moved to another port, such as 8080, and the reverse proxy in front of it was removed.
Fix: put a reverse proxy (nginx, Caddy, Apache) on 80 and 443 in front of the app, or bind the app to the public interface on the right port.
3. IPv6 is in DNS but the server doesn’t listen on IPv6
If the record set in Cloudflare includes a proxied AAAA record, Cloudflare can connect to your origin over IPv6. A server configured with listen 80; but no listen [::]:80; refuses those connections, and 521 appears for some requests and not others.
Fix: add the IPv6 listen directives (and open IPv6 in the firewall), or delete the AAAA record if the server isn’t meant to serve IPv6.
4. A firewall rejects Cloudflare’s IP ranges
The server is fine and works for you, but Cloudflare’s connections are rejected. Behind Cloudflare, all your traffic arrives from Cloudflare’s addresses, which makes them easy to block by accident:
- fail2ban, CrowdSec or a similar tool banned a Cloudflare edge IP because “it” sent too many requests or failed logins. Visitors routed through that edge get 521; everyone else is fine. That’s why this kind of 521 often hits one region or one ISP.
- A hosting panel or security plugin blocking foreign countries. Cloudflare’s addresses are registered to a US company, so a “block overseas IPs” setting can block Cloudflare even for local visitors. This is common on Korean hosting, where blocking foreign traffic is a popular default.
- Connection limits per source IP (iptables
connlimitand similar) that reject connections over a threshold. A handful of Cloudflare IPs carry all your visitors and hit the limit quickly. - A firewall rule you just tightened, for example a
ufwdefault-deny that didn’t include 80/443 from Cloudflare.
Whether a block shows up as 521 or 522 depends on how it blocks. A rule that rejects (ufw reject, iptables REJECT) answers immediately and gives 521; a rule that drops (ufw deny, most cloud security groups) stays silent and gives 522.
Fix: allow Cloudflare’s published ranges on the ports Cloudflare uses, and exclude them from automatic bans. With ufw:
for ip in $(curl -s https://www.cloudflare.com/ips-v4) $(curl -s https://www.cloudflare.com/ips-v6); do
sudo ufw allow proto tcp from "$ip" to any port 80,443
done
The same lists are available as JSON from https://api.cloudflare.com/client/v4/ips. They change occasionally, so refresh the rules from a scheduled job rather than once by hand. In fail2ban, add the ranges to ignoreip.
Then make your logs and ban tools see the visitor instead of Cloudflare. In nginx, with the realip module:
set_real_ip_from 173.245.48.0/20; # one line per Cloudflare range
real_ip_header CF-Connecting-IP;
Without this, every ban you set lands on Cloudflare and causes the next 521.
5. The DNS record points at the wrong server
The proxied A record still points at an old server, a rebuilt VPS that got a new IP, or a typo. Something at that address refuses the connection, or a different machine answers that has never heard of your site.
On a cloud VM, the address you see with ip addr is often a private one; the public IP is in the provider’s console. Compare it with the record value in Cloudflare DNS.
Fix: update the record. If the IP changes regularly (home server, dynamic IP), use Cloudflare Tunnel or a dynamic DNS updater instead of a static A record.
6. It comes and goes
Intermittent 521s usually mean one of several origins is broken (several proxied A records, a load balancer pool, one dead backend), a service that crashes under load and gets restarted, or a ban tool from cause 4 that bans and unbans edges over time. Test each origin IP separately, and check the service’s restart count:
systemctl show nginx -p NRestarts
After the fix
- Reload the page. Each error page has a new Ray ID; a fresh 521 means the origin is still refusing.
- Test both
http://andhttps://and every proxied hostname, not only the one that was reported. - Run the port check against the origin IP again, and the HTTP check against the public URL to confirm the site answers through Cloudflare.
Related errors
- Cloudflare error 522: Cloudflare’s connection gets no answer and times out.
- Cloudflare error 525: the connection works, the TLS handshake doesn’t.
- Cloudflare error 526: the handshake works, but Full (strict) rejects the origin certificate.
- Port 443 closed checklist: checking a closed or filtered HTTPS port step by step.
Check your own domain now
Free, no sign-up. Runs the exact check this guide describes and shows what to fix.