Question
How to fix Cloudflare 525 with Nginx
Cloudflare 525 means the edge reached your server but could not finish the TLS handshake. On Nginx, the root cause is usually one of four things: wrong certificate chain, wrong server block/SNI, broken TLS listener on 443, or a firewall/load balancer that interrupts TLS before Nginx can answer.
Do not start by changing Cloudflare SSL mode. First prove what certificate Nginx returns for the exact hostname and origin IP. That keeps you from masking the incident with Flexible mode while the origin is still misconfigured.
Triage in the order operators actually use
- Confirm Nginx is listening on
443 ssland the expected host is in the active server block. - Check certificate and intermediate chain files configured in
ssl_certificateandssl_certificate_key. - Verify the same hostname handshake works when forced to your origin IP.
- Confirm the default TLS server block is not serving a different certificate.
- Only after origin passes, retest through Cloudflare.
Command block for first response
sudo nginx -t
sudo systemctl status nginx --no-pager -n 30
sudo journalctl -u nginx --since '30 minutes ago' --no-pager | tail -n 120
sudo ss -lntp | grep ':443'
sudo nginx -T | sed -n '/server_name yourdomain.com/,+50p'
openssl s_client -connect yourdomain.com:443 -servername yourdomain.com -showcerts </dev/null | head -n 80
curl -Iv --resolve yourdomain.com:443:ORIGIN_IP https://yourdomain.com/
Read the openssl result
- Wrong subject or SAN: Nginx matched the wrong server block or the certificate does not cover the hostname.
- No peer certificate: the TLS listener is not completing the handshake, or a firewall/load balancer is interrupting before Nginx.
- Verify return code 20/21: the intermediate chain is likely incomplete.
- Handshake succeeds with origin IP but fails through Cloudflare: check Cloudflare SSL mode, origin firewall, and whether the edge can reach the same listener.
Check apex, www, IPv4, and IPv6 separately
Many 525 incidents are not site-wide. One hostname or one address family returns the wrong certificate while the other works. Test each public hostname against each origin address before editing Cloudflare settings.
for host in example.com www.example.com; do
curl -Iv --connect-timeout 8 --resolve "$host:443:ORIGIN_IPV4" "https://$host/"
done
# If you publish AAAA records, repeat with the IPv6 origin.
curl -g -Iv --connect-timeout 8 --resolve "example.com:443:[ORIGIN_IPV6]" https://example.com/
If apex succeeds but www fails, inspect the server_name block and certificate SAN list. If IPv4 succeeds but IPv6 fails, either mirror the TLS config on the IPv6 origin or remove the proxied AAAA record until the host is ready.
Minimal Nginx server block pattern
server {
listen 443 ssl http2;
server_name yourdomain.com www.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3000;
}
}
The important detail is fullchain.pem, not just the leaf certificate. With Let's Encrypt, using cert.pem instead of fullchain.pem is a classic way to create a TLS problem that appears only after stricter edge validation.
When the default server block is the real problem
Nginx can pass nginx -t and still serve the wrong certificate because the request falls into a default TLS server. Make the intended host explicit and check the first listen 443 ssl block in nginx -T.
sudo nginx -T | grep -nE 'listen 443|server_name|ssl_certificate'
# Safer default: close unmatched TLS names instead of serving a stale cert.
server {
listen 443 ssl default_server;
server_name _;
ssl_reject_handshake on;
}
Use ssl_reject_handshake only after confirming your real hostnames have their own matching blocks. It is better to reject unknown names than to leak a stale certificate, but a misplaced default can break valid traffic.
Frequent Nginx mistakes
- Serving leaf cert only, without intermediate chain.
- Default TLS server block catches the request, so wrong cert is returned.
- Old copied TLS config disables modern ciphers/protocols unexpectedly.
- Cloud provider load balancer terminates TLS while Nginx is also configured as if it receives raw HTTPS.
- IPv6 AAAA record points to a host that does not have the same Nginx TLS config as IPv4.
Cloudflare SSL mode cross-check
- Flexible: Cloudflare talks HTTP to origin. This mode avoids 525 but is usually not recommended for production security.
- Full: origin TLS is required, cert validity is less strict.
- Full (strict): origin TLS and certificate validity must both pass hostname and chain checks.
If you moved from Full to Full (strict) and 525 started, verify chain and SNI first, then review for 526-specific certificate validation issues.
When the config looks right but 525 remains
Check the layers between Cloudflare and Nginx. Provider firewalls, security groups, Docker port mappings, and load balancers can all make port 443 look open while the TLS stream never reaches the intended Nginx process. Compare ss -lntp, provider firewall rules, and origin access logs at the exact time of the Cloudflare Ray ID.
# Is Nginx really receiving the request?
sudo tail -n 100 /var/log/nginx/access.log
sudo tail -n 100 /var/log/nginx/error.log
# Is another service bound to 443?
sudo ss -lntp | grep ':443'
# Does the origin answer with SNI from a different network?
curl -Iv --connect-timeout 8 --resolve yourdomain.com:443:ORIGIN_IP https://yourdomain.com/
Align the Ray ID with origin logs
A 525 occurs before a normal HTTP request reaches the upstream app, so application logs may be empty. Use the error page timestamp and Ray ID to compare Cloudflare events with Nginx error logs and system TLS errors.
# Cloudflare error page gives a timestamp and Ray ID.
# On the origin, inspect the same window:
sudo journalctl -u nginx --since '10 minutes ago' --no-pager
sudo tail -n 200 /var/log/nginx/error.log
# If there is no origin-side entry at all, suspect firewall, security group,
# load balancer, or Cloudflare-to-origin reachability rather than app code.
Rollback-safe fix sequence
- Backup current Nginx config and certificate files.
- Apply one change only (certificate chain or server block mapping).
- Run
nginx -tand direct-origin handshake checks. - Reload Nginx, then retest through Cloudflare.
- If still failing, roll back and test the next suspected layer.
What success looks like in logs
After fix, Cloudflare-proxied requests to your public pages should return mostly 200/301/304. 525 should disappear from edge responses on repeated checks.
Useful references: Cloudflare 525 support docs and the LLMs File Cloudflare 525 checker.
If you see certificate validation errors instead of handshake errors, use this page next: How to fix Cloudflare 526 invalid certificate.
Using Caddy instead of Nginx? Follow: How to fix Cloudflare 525 with Caddy.