Cloudflare Error 521 on Your VPS: Causes and Fixes

Published on

Cloudflare error 521 means your VPS refused Cloudflare's connection. Check the web server, ports, SSL mode, DNS and firewall in order, with the exact commands.

Illustration of a worried person kneeling beside server racks and a monitor that shows a server error

Your site shows Cloudflare's "Web server is down" page with error 521, and refreshing changes nothing. Cloudflare itself is fine: it is serving you that page. What fails is the hop from Cloudflare to your VPS: Cloudflare reached the server's IP address and the server refused the connection. The cause is almost always on the server or its firewall, so a few commands will find it. Here they are, cheapest first, for Ubuntu 24.04 and Debian 12.

Quick answer: Error 521 means Cloudflare reached your server's IP address and the server refused the connection. On a VPS the usual causes are a stopped web server, a server that listens only on 127.0.0.1 or on one IP version, nothing on port 443 while the SSL/TLS mode is Full or Full (strict), a DNS record with the wrong IP, or a firewall rule that rejects Cloudflare's addresses, such as a fail2ban ban or a UFW limit rule. Test the origin with curl --resolve from your own computer, then check the web server, ss -ltnp, the SSL/TLS mode, DNS and the firewall.

What Error 521 Means

Cloudflare's Error 521 page defines it in one sentence: the error "occurs when the origin web server refuses connections from Cloudflare." The two most common causes it names are an offline web server application and blocked Cloudflare requests.

A refusal is an instant answer: nothing listens on the port, or a firewall rule rejects the connection and sends an error back. A firewall that drops packets silently gives no answer, and when no SYN+ACK arrives within 19 seconds Cloudflare shows 522 instead.

ErrorCloudflare's nameWhat Cloudflare sawCheck first
521Web server is downThe origin refused the connectionWeb server, listening address and port, reject rules
522Connection timed outNo SYN+ACK within 19 seconds, or no ACK of the request within 90 secondsDropped packets, wrong IP, an overloaded server
523Origin is unreachableNo route to the origin's IP addressA and AAAA records, routing
525SSL handshake failedThe TLS handshake with the origin failed in Full or Full (strict)Certificate, port 443, SNI, cipher suites
526Invalid SSL certificateThe origin certificate could not be validated in Full (strict)Expiry, issuer, hostname, certificate chain

Cloudflare's own 521 checklist covers the same ground as the steps below: a responsive web server and its logs, all Cloudflare IP ranges allowed and not rate limited, and HTTPS on port 443 for Full modes or port 80 for Flexible.

Step 1: Test the Origin Without Cloudflare

Run this on your own computer, replacing example.com with your domain and 203.0.113.10 with your VPS's IPv4 address:

curl -sS -k -o /dev/null -w '%{http_code}\n' --connect-timeout 10 \
  --resolve example.com:443:203.0.113.10 https://example.com/
curl -sS -o /dev/null -w '%{http_code}\n' --connect-timeout 10 \
  --resolve example.com:80:203.0.113.10 http://example.com/

--resolve makes curl use that address instead of the DNS answer, which points at Cloudflare. -k skips the certificate check, so a Cloudflare Origin CA certificate, which browsers don't trust, doesn't fail the test. Cloudflare's troubleshooting guide does the same with --connect-to ::203.0.113.10. The messages below are from curl 8.5.0 (Ubuntu 24.04).

  • A status code such as 200, 301 or 404: the server answers you, so Cloudflare's connection differs from yours: another IP version or port (steps 3 and 4), another IP (step 5), or a firewall that treats Cloudflare's addresses differently (step 6).
  • curl: (7) Failed to connect to example.com port 443 after 31 ms: Couldn't connect to server, then 000: refused, exactly what Cloudflare sees. Go to steps 2 and 3. -v also shows Connection refused.
  • curl: (28) Failed to connect to example.com port 443 after 10002 ms: Timeout was reached: nothing answered. That points to 522 causes: the IP (step 5) or a firewall that drops packets (step 6).

Don't run it on the VPS itself: traffic to the server's own address is looped back and delivered locally, and UFW accepts everything on the loopback interface, so the firewall never gets a say. If ports 80 and 443 only accept Cloudflare's ranges, your computer is blocked too; go straight to step 2.

Step 2: Check That the Web Server Is Running

An offline web server is the first cause on Cloudflare's list. For nginx:

sudo systemctl status nginx
sudo journalctl -u nginx -n 50 --no-pager
sudo nginx -t

nginx -t checks the configuration syntax and tries to open the files it refers to, so a typo or a missing certificate file shows up here. If the log shows bind() to 0.0.0.0:80 failed (98: Address already in use), another program holds the port, and step 3 shows which. When the test passes:

sudo systemctl restart nginx

If nginx was running and then vanished, check whether the kernel killed it for memory. The kernel logs Out of memory: Killed process with the process ID and name:

sudo journalctl -k | grep -i "killed process"

Apache's service is apache2 (check its syntax with sudo apache2ctl configtest) and Caddy's is caddy. If a Node app or a container answers on 80 or 443 itself, check that process instead. If nginx runs but the app behind it is down, Cloudflare shows a 502 rather than a 521; our nginx 502 guide covers that case.

Step 3: Check Which Address and Port It Listens On

A running server still refuses Cloudflare if it listens on the wrong address or port:

sudo ss -ltnp '( sport = :80 or sport = :443 )'

-l shows listening sockets, -t TCP, -n numbers instead of names and -p the process. Read the Local Address:Port column:

Local Address:PortAccepts
0.0.0.0:80IPv4 on every address
[::]:80IPv6 on every address, no IPv4
*:80IPv4 and IPv6
127.0.0.1:80 or [::1]:80Only connections from the server itself
No :443 lineNothing serves HTTPS

Cloudflare's IPv6 page says that when a proxied record has both an IPv4 and an IPv6 origin address, Cloudflare prefers IPv4. With only an AAAA record it has to use IPv6. So [::]:80 alone fails when you have an A record, and 0.0.0.0:80 alone fails when you only have an AAAA record.

In nginx, give each server block a listen line per IP version. The listen docs say ipv6only is on by default, so listen [::]:80; on its own takes no IPv4:

server {
    listen 80;
    listen [::]:80;
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name example.com www.example.com;
    # ssl_certificate, ssl_certificate_key and your location blocks follow
}

The default site in the Ubuntu 24.04 and Debian 12 packages already has both port 80 lines, so check the sites you added. To list every listen line nginx loads, included files and all, and apply a change:

sudo nginx -T 2>/dev/null | grep -E '^\s*listen'
sudo nginx -t && sudo systemctl reload nginx

An app on 127.0.0.1 is fine behind nginx, as long as nginx listens publicly.

Step 4: Match the SSL/TLS Mode to Your Ports

Open SSL/TLS Overview in the Cloudflare dashboard. The encryption mode decides which port Cloudflare connects to:

  • Flexible: plain HTTP to port 80.
  • Full and Full (strict): the visitor's scheme, so an HTTPS visitor means HTTPS to port 443.

A server with only port 80 open under Full refuses every HTTPS visitor's connection, which is a 521. If something listens on 443 but the handshake fails, you get 525, and under Full (strict) a certificate Cloudflare cannot validate gives 526.

Automatic SSL/TLS, which Cloudflare is rolling out as the default, raises the mode when it finds that your origin supports a more secure one, and per the encryption modes page it "will not change your setting to a less secure encryption mode." Remove HTTPS from a server after such an upgrade and you get 521s until port 443 is back.

The fix is a certificate on the server and a listen 443 ssl; line:

  • A Let's Encrypt certificate from certbot, as in our nginx reverse proxy with SSL guide.
  • A free Cloudflare Origin CA certificate, created on the Origin Server page under SSL/TLS. Install it, enable port 443, then choose Full (strict). Browsers don't trust it, so it only works while the record is proxied, and Cloudflare sends no expiry reminders for it.

Flexible hides the problem rather than fixing it (see the FAQ). If your app listens on a port such as 3000, put nginx in front of it, or use an Origin Rule to override the destination port; Origin Rules are on every plan.

Step 5: Check That the DNS Record Points at This VPS

A public lookup of a proxied record returns Cloudflare's anycast addresses, so compare the A and AAAA records under DNS > Records in the dashboard with the addresses on the VPS or in your provider's panel:

ip -brief address show

Three mistakes to look for:

  • An old IP after a move or a reinstall. If that machine refuses, you get 521; if nothing answers, 522 or 523.
  • Two A records on one name. Once one record on a name is proxied, Cloudflare treats all its A and AAAA records as proxied, so the old one counts as an origin too. Delete it.
  • An AAAA record without an A record. Cloudflare then connects over IPv6, so the address must be on the VPS and nginx must listen on it (step 3).

Step 6: Check That the Firewall Lets Cloudflare In

Behind Cloudflare, visitors arrive from a small set of Cloudflare addresses. Cloudflare's IP address page warns that to a firewall this "can look like a limited number of sources sending a high volume of traffic", which can trigger blocking or rate limiting. On a Space-Node VPS you get full root access on KVM, so all of the rules below are yours to change.

UFW. Run sudo ufw status verbose and look for:

  • No rule for 80/tcp and 443/tcp. With the default deny (incoming), UFW drops those packets silently, which shows up as 522, and with a reject (incoming) default you get 521. Either way, open them with sudo ufw allow 80/tcp and sudo ufw allow 443/tcp.
  • LIMIT on 80 or 443. The ufw manual says limit denies an address that opens 6 or more connections within 30 seconds, and UFW's source shows the limit chain ends in REJECT. One Cloudflare address carries many visitors, so it can pass that limit quickly, and every rejected connection is a 521.
  • A REJECT rule that matches Cloudflare's addresses.

Delete such a rule by repeating it with delete in front. That removes the IPv4 and IPv6 copies together, while deleting by number from sudo ufw status numbered removes only one:

sudo ufw delete limit 443/tcp

Cloudflare recommends accepting web traffic only from its ranges. This loop allows them; afterwards, remove the general rules with sudo ufw delete allow 80/tcp and sudo ufw delete allow 443/tcp, or sudo ufw delete allow 'Nginx Full' if you opened the ports with the UFW profile that the nginx package ships:

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

The ranges rarely change, and Cloudflare adds new ones to the list before it uses them, so rerun the loop now and then. Fetching the lists needs curl on the server (sudo apt install curl).

Does your site run in a Docker container with a published port? Then these UFW rules do not apply to that port at all, because Docker handles published ports before UFW sees the traffic. Our guide to Docker and UFW shows how to check and fix that.

fail2ban. Unless you restore visitor IPs, nginx logs a Cloudflare address for every request, so a jail that reads those logs, such as nginx-limit-req or nginx-botsearch, can ban a Cloudflare address. The fail2ban packages on Ubuntu 24.04 (nftables) and Debian 12 (iptables) ban with a reject by default, so that address gets a refusal, which is a 521. Check and lift bans:

sudo fail2ban-client status
sudo fail2ban-client banned
sudo fail2ban-client unban 172.68.10.20   # replace with the Cloudflare address you found

To stop it from coming back, put Cloudflare's ranges in ignoreip. fail2ban's jail.conf says to make changes in jail.local:

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 printed line under [DEFAULT] in /etc/fail2ban/jail.local
# (create the file with a [DEFAULT] line if it does not exist), then:
sudo fail2ban-client reload

Logging real visitor addresses helps too. The nginx builds on Ubuntu and Debian include the realip module, and Cloudflare's instructions use set_real_ip_from for each Cloudflare range plus real_ip_header CF-Connecting-IP;:

{ 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
sudo nginx -t && sudo systemctl reload nginx

Other rules. Without UFW, read the firewall with sudo nft list ruleset or sudo iptables -S and look for reject or REJECT on ports 80 and 443. On Ubuntu 24.04, sudo nft list table inet f2b-table shows fail2ban's bans. Check your provider's network firewall too, if it has one.

If It Still Fails

  • In the Cloudflare dashboard, open HTTP Traffic and filter on Edge status code 521 to see which URLs and data centers are affected. These analytics use a 1% sample.
  • If only some visitors see the error, have them open https://example.com/cdn-cgi/trace and send you the colo line, the Cloudflare data center serving them. Errors that follow particular data centers point back to the fail2ban and UFW limit checks.
  • When you contact your host, send what Cloudflare asks for: the error code, the time with time zone and the full URL, plus your step 1 output.
  • Pausing Cloudflare or switching the record to DNS only also tests the origin, but a DNS-only record "exposes your origin IP address to anyone who queries the record". The step 1 test doesn't.
  • To have no inbound web ports at all, Cloudflare Tunnel runs cloudflared on your server and connects outbound, so there is no inbound connection to refuse. Our Cloudflare Tunnel guide covers where a tunnel fits.

FAQ

Is error 521 a Cloudflare outage? No. Cloudflare reached your server and the server refused the connection, so the fix is on the server or its firewall. Cloudflare's support only works with the domain owner, so a visitor can only tell the site owner.

Why do only some visitors get a 521? Requests reach your server from many Cloudflare addresses. If fail2ban or a UFW limit rule rejected some of them, only visitors whose requests come through those addresses see the error. IPv4 against IPv6 (step 3) and HTTP against HTTPS under Full (step 4) can also split visitors.

Should I switch SSL/TLS to Flexible to fix it? Only as a stopgap. Flexible connects on port 80, so a missing port 443 stops mattering, but Cloudflare's Flexible page lists the costs: no encryption between Cloudflare and your server, a redirect loop if your server redirects HTTP to HTTPS, and it is not meant for sites with logins or personal data. Install a certificate and use Full (strict).

Do I have to allow every Cloudflare IP range? Yes, on the ports you serve through Cloudflare; Cloudflare's 521 page says to allow all of them. On 29 October 2026 the list at cloudflare.com/ips had 15 IPv4 and 7 IPv6 ranges, also as plain text at /ips-v4 and /ips-v6.

Does a 521 hurt my Google rankings? It can if it lasts. Google's crawler documentation says 5xx errors slow its crawlers down, and indexed URLs are kept for a while but eventually dropped when the errors persist. Once the server returns 2xx codes again, Google gradually raises the crawl rate.

Sources

All opened on 29 October 2026: