Short answer: Cloudflare Error 521 (“Web server is down”) means Cloudflare reached your origin’s IP address, but the server refused the TCP connection. Either nothing is listening on the port Cloudflare uses (80 or 443, depending on your SSL/TLS mode), or a firewall, fail2ban or CrowdSec is rejecting Cloudflare’s IP ranges. Most fixes happen on the origin server; only the SSL/TLS mode and the DNS record are changed in the Cloudflare dashboard. Check in this order: is the web server running, does it listen on the right address and port, does your SSL/TLS mode expect a port you actually serve, is a firewall rejecting Cloudflare, and does the DNS record point to the right server.
# 1. Is the web server running?
sudo systemctl status nginx # or: apache2, caddy, your container
# 2. Does it listen on 80/443 on a public address (not just 127.0.0.1)?
sudo ss -tlnp | grep -E ':(80|443)\s'
# 3. Did fail2ban or another tool ban Cloudflare addresses?
sudo grep ' Ban ' /var/log/fail2ban.log | tail -n 20# 4. Does the origin answer when Cloudflare is skipped?
curl -skv -o /dev/null --resolve example.com:443:203.0.113.10 https://example.com/ 2>&1 | grep -E 'Connected|Established|refused|HTTP/'Replace example.com with your hostname and 203.0.113.10 with the origin IP. Public DNS lookups only return Cloudflare’s own addresses for proxied records, so take the origin IP from the DNS records page in the Cloudflare dashboard or from your hosting panel.
What does Cloudflare Error 521 mean?
Every request to a proxied hostname ends at a Cloudflare data centre, which opens a second connection to your server. Error 521 is Cloudflare telling the visitor that this second connection was actively refused: your server, or something in front of it, answered the connection attempt with a reset or an ICMP “port unreachable”. The machine is up and routable. The service on the port is not accepting Cloudflare.
The neighbouring codes look similar on the error page but point at different layers, so make sure you are chasing the right one:
| Code | What Cloudflare saw | Look at first |
|---|---|---|
| 520 | The origin answered with something empty or invalid | App crashes, oversized headers |
| 521 | Connection refused | Web server stopped, port closed, firewall rejecting Cloudflare |
| 522 | No answer to the connection attempt (timed out) | Firewall silently dropping packets, server overloaded or off |
| 523 | No route to the origin IP | Wrong A/AAAA record, routing problem |
| 524 | Connected, but no HTTP response in time | Slow scripts, long exports |
| 525 | Port open, TLS handshake failed | No TLS on 443, protocol or cipher mismatch |
| 526 | Certificate rejected in Full (strict) | Expired, self-signed or wrong-host certificate |
The difference between 521 and Error 522 matters most. A firewall rule that drops packets produces a timeout and therefore 522. A rule that rejects them, or a port with nothing behind it, produces 521. UFW’s deny drops silently, while fail2ban’s default actions reject. So a sudden 521 on a server that worked yesterday often points at fail2ban, and a 522 at the firewall.
Test the origin directly, without Cloudflare
Before changing anything, find out whether the origin refuses everyone or only Cloudflare. curl --resolve sends the request straight to your server while keeping the right hostname for TLS and virtual hosts. Add -k if the origin uses a Cloudflare Origin CA certificate, which your PC does not trust; it only skips the certificate check for this test.
# HTTPS, as Cloudflare uses in Full and Full (strict)
curl -skv -o /dev/null --resolve example.com:443:203.0.113.10 https://example.com/
# Plain HTTP, as Cloudflare uses in Flexible
curl -sv -o /dev/null --resolve example.com:80:203.0.113.10 http://example.com/curl.exe -skv -o NUL --resolve example.com:443:203.0.113.10 https://example.com/
Test-NetConnection 203.0.113.10 -Port 443On Windows, type curl.exe: in Windows PowerShell 5.1, plain curl is an alias for Invoke-WebRequest. Test-NetConnection checks only the TCP port and ends with TcpTestSucceeded : True or False.
- A line ending in
failed: Connection refusedreproduces the 521. The web server is down, listens elsewhere, or a reject rule hits everyone. Work through causes 1, 2 and 4 below. - You get an HTTP status line (
HTTP/1.1 200,301, even404): the origin works for you. The block is specific to Cloudflare (cause 4), or Cloudflare uses the other port because of your SSL mode (cause 3). - It hangs and times out: that is the 522 pattern, a firewall dropping traffic. See Error 522.
- A TLS error after
Connected: TCP works, so you are looking at a 525 or 526, not a 521.
No second network at hand? Enter the origin IP and port 443 (80 for Flexible) in the open port checker. It connects from our server in Amsterdam and reports closed for a refused connection, the 521 pattern, and no answer for a dropped one, the 522 pattern. If your firewall admits only Cloudflare’s ranges, expect one of those two even on a healthy origin, because the test does not come from a Cloudflare address.
Cause 1: the web server is not running
The most common reason, and the one the error title names. nginx or Apache stopped because a config edit broke the reload, the kernel’s OOM killer ended it, it was never enabled at boot, or a container that published port 80/443 exited.
sudo systemctl status nginx --no-pager
sudo journalctl -u nginx -n 50 --no-pager
# Test the config before starting, so you see the broken line
sudo nginx -t # Apache: sudo apache2ctl configtest
# Start it and make sure it comes back after a reboot
sudo systemctl enable --now nginx
# Was it killed for memory?
sudo journalctl -k | grep -i -E 'out of memory|oom-kill' | tail -n 5
# Docker: is the container that owns port 80/443 still up?
docker ps -a --format '{{.Names}}\t{{.Status}}\t{{.Ports}}'If nginx -t reports an error, it names the file and line. Fix that first; restarting a server with a broken config just fails again. If the OOM killer is involved, the web server will keep dying until memory pressure is solved: find the process that eats the RAM before you restart anything.
Cause 2: it runs, but not on the address or port Cloudflare uses
A running server can still refuse Cloudflare if it is bound to the wrong place. Read the Local Address column of ss:
sudo ss -tlnp | grep -E ':(80|443)\s'# Good: reachable from outside
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))
# Bad: only the server itself can connect
LISTEN 0 511 127.0.0.1:443 0.0.0.0:* users:(("nginx",pid=812,fd=7))- Bound to localhost: a
listen 127.0.0.1:80;line or an app started with--host 127.0.0.1only accepts local connections. Listen on80/443without an address, or on the public IP. - Nothing on 443: common on a fresh server that only has the default
listen 80site. See cause 3. - IPv6 only in DNS: when a proxied hostname has both A and AAAA records, Cloudflare prefers IPv4 to reach the origin. If it only has an AAAA record, Cloudflare connects over IPv6, and a server block with only
listen 443 ssl;refuses. Addlisten [::]:443 ssl;. - App on a non-standard port: Cloudflare does not guess that your Node app runs on 3000. Put nginx in front on 80/443, or set the destination port with a Cloudflare Origin Rule. The nginx reverse proxy guide shows the first option with a certificate.
Cause 3: your SSL/TLS mode needs a port you do not serve
The SSL/TLS encryption mode (SSL/TLS → Overview in the dashboard) decides which port Cloudflare connects to. A mismatch between mode and origin is a classic 521 after someone “tightens security” on one side only.
| Mode | Cloudflare connects to the origin over | If the origin is wrong |
|---|---|---|
| Off, Flexible | Plain HTTP on port 80 | 521 if port 80 is closed; a redirect loop if the origin forces HTTPS |
| Full | The visitor's scheme: 443 for HTTPS visitors, 80 for HTTP | 521 if nothing listens on 443; 525 if 443 does not speak TLS. Any certificate is accepted |
| Full (strict) | Same as Full, but the certificate must be valid | 521 or 525 as above; 526 for an expired, self-signed or wrong-host certificate |
Two patterns cause most of these:
- Mode is Full or Full (strict), origin has no HTTPS. Fix it properly: install a certificate on the origin (Let’s Encrypt, or a free Cloudflare Origin CA certificate) and listen on 443, then use Full (strict). Dropping back to Flexible makes the error go away but leaves the Cloudflare-to-origin leg unencrypted.
- Mode is Flexible, origin closed port 80. Someone allowed only 443 in the firewall or removed the HTTP server block. Flexible always uses port 80, so either reopen it or move to Full (strict).
Cause 4: a firewall or ban tool rejects Cloudflare
Behind Cloudflare, every request reaches your server from a Cloudflare IP, not from the visitor. To fail2ban, CrowdSec, a WAF or rate limiting, one busy Cloudflare address looks like one very aggressive client. Once that address is banned, every request Cloudflare sends from it is refused and shows a 521, while requests that arrive from other Cloudflare addresses still load the site. Intermittent, location-dependent 521s are the signature of this cause.
# Current Cloudflare ranges (the files have no final newline, hence the echo)
{ curl -s https://www.cloudflare.com/ips-v4; echo; curl -s https://www.cloudflare.com/ips-v6; echo; } > /tmp/cf-ips.txt
# fail2ban: which bans hit Cloudflare?
sudo apt install -y grepcidr
sudo grep ' Ban ' /var/log/fail2ban.log | grepcidr -f /tmp/cf-ips.txt
# Lift one (the jail name is shown in [brackets] in the log line)
sudo fail2ban-client set nginx-limit-req unbanip 162.158.0.1
# CrowdSec, if installed
sudo cscli decisions list
# Plain firewall rules that reject or drop
sudo nft list ruleset | grep -n -E 'reject|drop'
sudo ufw status numberedUnbanning only clears the symptom. Stop it from happening again by fixing what the ban tool sees (next section) and by telling it never to ban Cloudflare. In /etc/fail2ban/jail.local, under [DEFAULT], add Cloudflare’s ranges to ignoreip. This prints the line with the current ranges filled in:
echo "ignoreip = 127.0.0.1/8 ::1" $(curl -s https://www.cloudflare.com/ips-v4) $(curl -s https://www.cloudflare.com/ips-v6)
# paste the output into /etc/fail2ban/jail.local under [DEFAULT], then:
sudo systemctl restart fail2banIf you lock 80/443 down to Cloudflare only, which is a sensible hardening step, allow every published range, not a hand-picked few:
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 comment 'Cloudflare'
done
sudo ufw status numberedMake your logs show visitors, not Cloudflare
Cloudflare sends the real client address in the CF-Connecting-IP header. Trust it only from Cloudflare’s ranges, and nginx logs, rate limits and fail2ban filters see the visitor again:
{ for ip in $(curl -s https://www.cloudflare.com/ips-v4) $(curl -s https://www.cloudflare.com/ips-v6); do
echo "set_real_ip_from $ip;"
done
echo "real_ip_header CF-Connecting-IP;"
} | sudo tee /etc/nginx/conf.d/cloudflare-realip.conf > /dev/null
sudo nginx -t && sudo systemctl reload nginxOn Apache, enable mod_remoteip (sudo a2enmod remoteip) and set RemoteIPHeader CF-Connecting-IP plus one RemoteIPTrustedProxy line per Cloudflare range. One catch for fail2ban: a firewall ban on a visitor’s own IP does nothing behind Cloudflare, because that visitor’s packets still arrive from Cloudflare. For web jails, fail2ban ships a cloudflare-token action that blocks the address at Cloudflare’s edge instead.
Cause 5: the DNS record points to the wrong server
After a migration or a reinstall with a new IP, the proxied A or AAAA record in Cloudflare may still point at the old machine, where nothing listens any more. Open the DNS tab, compare the record’s content with the IP shown in your hosting panel, and check every proxied hostname: www and the apex are separate records and drift apart easily. The guide to pointing a domain at a VPS walks through the records.
How to keep 521 from coming back
- Restart on failure.
sudo systemctl edit nginx, then addRestart=on-failureandRestartSec=5sunder[Service]. Keepsystemctl enableon for every service that serves 80/443. - Test before reload. Chain
sudo nginx -t && sudo systemctl reload nginxso a typo never takes the server down. - Keep Cloudflare ranges current. Regenerate the real-IP file, the firewall rules and
ignoreipfrom the published lists on a schedule, for example a weekly cron job. A new range that your firewall does not know turns into 521s or 522s for part of your traffic. - Monitor the origin as well as the hostname. A check that only goes through Cloudflare tells you that something broke; one that hits the origin with the right Host header tells you where.
- Leave the server enough memory. A web server that the OOM killer keeps ending will keep producing 521s. If the box is simply too small, a Standard KVM VPS with more RAM is cheaper than the downtime. Our Amsterdam VPS works behind Cloudflare like any other origin, with always-on DDoS protection in front of the server itself.
Frequently asked
What is the difference between Cloudflare Error 521 and 522?
521 means the origin actively refused Cloudflare's connection: nothing listens on the port, or a firewall or fail2ban rejects Cloudflare's IP addresses. 522 means the connection attempt got no answer at all, usually because a firewall silently drops the packets or the server is overloaded or off.
Can fail2ban cause Cloudflare Error 521?
Yes. Behind Cloudflare every request arrives from a Cloudflare IP, so fail2ban can ban a Cloudflare address, and its default actions reject the connection, which Cloudflare reports as 521. Restore the visitor IP from the CF-Connecting-IP header and add Cloudflare's ranges to ignoreip so it cannot happen again.
Does the Cloudflare SSL/TLS mode affect Error 521?
Yes. Flexible connects to your origin over HTTP on port 80, while Full and Full (strict) connect over HTTPS on port 443 for HTTPS visitors. If the port your mode needs is closed on the origin, Cloudflare gets a refused connection and shows 521.