What the four errors mean
These errors come from Cloudflare when it cannot talk to your origin server, the VPS. Cloudflare is working. The problem is between it and your server. 521 means the origin refused the connection or is down. 522 means Cloudflare could not complete a connection in time. 525 means the TLS handshake with the origin failed. 526 means the origin's certificate is not valid while Cloudflare's SSL mode requires a valid one.
Always test the origin directly, bypassing Cloudflare, and compare. That tells you quickly whether the server or the setup is at fault.
Isolate the problem
Check that the DNS record points to the right IP address, that the web server is running and that it answers when you talk to it directly. The curl command below sends a request to the origin's IP with your domain name, so the right site and certificate are selected, without going through Cloudflare.
curl -sSI --resolve example.com:443:ORIGIN_IP https://example.com/ | head -n 5
curl -sSI --resolve example.com:80:ORIGIN_IP http://example.com/ | head -n 5sudo ss -lntp | grep -E ":(80|443) "
systemctl is-active nginx apache2 httpd caddy 2>/dev/null521: the origin is down or refusing connections
Nothing is accepting connections on the port Cloudflare uses, or something is actively rejecting them. Check that the web server runs, that it listens on 443 (and 80), and that the firewall allows these ports. A crashed service, a failed reload after a bad configuration, or a recent firewall change are the usual causes.
sudo systemctl status nginx --no-pager | head -n 8
sudo nginx -t
sudo tail -n 20 /var/log/nginx/error.log
sudo ufw status verbose 2>/dev/null | head -n 20
sudo firewall-cmd --list-all 2>/dev/null522: connection timed out
Cloudflare reached your IP address but got no answer in time. Typical causes are a firewall silently dropping Cloudflare's addresses, the wrong IP in the DNS record, a server too overloaded to accept connections, or a provider-level firewall. Security tools sometimes cause it: Fail2ban or a rate limiter can ban a Cloudflare address, because all your visitors appear to come from a few Cloudflare IPs.
sudo fail2ban-client status 2>/dev/null
sudo fail2ban-client status sshd 2>/dev/null | tail -n 4
uptime
free -h
dig +short A example.comAllow Cloudflare to reach the origin
If your firewall only allows certain addresses, allow Cloudflare's published ranges on ports 80 and 443. Fetch the lists from Cloudflare, since they change occasionally, and refresh your rules from time to time. This also lets you block everyone else from the origin, so attackers cannot bypass Cloudflare.
for ip in $(curl -s https://www.cloudflare.com/ips-v4) $(curl -s https://www.cloudflare.com/ips-v6); do
sudo ufw allow from "$ip" to any port 80,443 proto tcp
done
sudo ufw status numbered | head -n 20for ip in $(curl -s https://www.cloudflare.com/ips-v4); do
sudo firewall-cmd --permanent --add-rich-rule="rule family=ipv4 source address=$ip port port=443 protocol=tcp accept"
done
for ip in $(curl -s https://www.cloudflare.com/ips-v6); do
sudo firewall-cmd --permanent --add-rich-rule="rule family=ipv6 source address=$ip port port=443 protocol=tcp accept"
done
sudo firewall-cmd --reloadRestore real visitor addresses in your logs
Behind Cloudflare, your server logs show Cloudflare's addresses. Tell Nginx to trust Cloudflare's ranges and read the visitor address from the CF-Connecting-IP header, so that logs, rate limits and Fail2ban see the real client, and not Cloudflare itself.
# generate the set_real_ip_from lines from https://www.cloudflare.com/ips-v4 and ips-v6
set_real_ip_from 173.245.48.0/20;
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 | sudo tee /etc/nginx/conf.d/cloudflare-realip-ranges.conf
sudo nginx -t && sudo systemctl reload nginx525: TLS handshake with the origin failed
In Full or Full (strict) mode, Cloudflare connects to your origin over HTTPS. A 525 means that failed: nothing serves TLS on port 443, the server has no certificate for the domain, or it only offers protocols or ciphers that Cloudflare does not accept. Test the TLS handshake against the origin directly.
openssl s_client -connect ORIGIN_IP:443 -servername example.com </dev/null 2>&1 | grep -E "subject=|issuer=|Verify return|alert|handshake"sudo nginx -T 2>/dev/null | grep -nE "listen .*443|ssl_certificate|server_name"526: the origin certificate is not valid
In Full (strict) mode, the origin must present a certificate that is valid for the domain, not expired, and issued by a trusted authority or by Cloudflare's own Origin CA. Self-signed certificates give a 526. Fix it by installing a Let's Encrypt certificate with the reverse proxy guide, or a free Cloudflare Origin CA certificate. Check the dates and the names on what the origin presents.
echo | openssl s_client -connect ORIGIN_IP:443 -servername example.com 2>/dev/null | openssl x509 -noout -subject -issuer -datessudo certbot certificates
sudo certbot renew --dry-runCheck SSL mode and keep it healthy
In the Cloudflare dashboard under SSL/TLS, use Full (strict) once the origin has a valid certificate. If you use Let's Encrypt's HTTP validation, make sure renewal still works behind Cloudflare, as the renewal troubleshooting guide describes. Monitor your site from outside, set a certificate-expiry alert and keep a note of the origin IP address and your firewall rules, because these errors almost always come back to one of them.
curl -sI https://example.com/ | head -n 8
curl -sI https://example.com/ | grep -i -E "server|cf-ray"Official documentation
These upstream references provide more detail on the commands and workflows in this guide.