A CDN or proxy terminates the connection and makes its own to you. Your server therefore sees the proxy's address on every request, which quietly breaks anything that depends on knowing who is asking.

What breaks

  • Rate limiting - every visitor is one address, so either everyone is limited or nobody is.
  • Bans - fail2ban blocks the proxy, which blocks everybody.
  • Logs and analytics - one address for the whole world.
  • Country rules - the country of the edge node, not of the visitor.

Tell the server where the real address is

set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
# ... the rest of the provider's ranges
real_ip_header CF-Connecting-IP;
real_ip_recursive on;
Only trust that header from the proxy's own ranges. Trusting it from anywhere means anyone can send the header and claim to be any address they like, which turns your ban list into a weapon against your own visitors.

And where https is

If the proxy terminates TLS, your server sees plain http and may decide the visitor needs redirecting to https - which is already where they are. That is a redirect loop.

# PHP, before the framework loads
if ((@$_SERVER["HTTP_X_FORWARDED_PROTO"] ?? "") === "https") {
    $_SERVER["HTTPS"] = "on";
}

Check it worked

tail -5 /var/log/nginx/access.log

Real, varied addresses means it is right. The same address on every line means it is not.

Close port 80 and 443 to everything except the proxy ranges once it is working. Otherwise anyone who learns your origin address can bypass the proxy entirely.