Cloudflare's dashboard shows your site as active, DNS is proxied, SSL says "Full" or "Full (strict)" — and visitors still land on an orange error page reading Error 520: Web server returned an unknown error. Unlike 521/522/523, your origin isn't down and isn't unreachable. It answered. Cloudflare just couldn't make sense of what came back. Here's how to track down why and get it fixed on a cPanel or VPS-hosted site.

Symptom: What You're Actually Seeing

520 is the catch-all Cloudflare uses when the origin responds with something outside the HTTP spec, or the connection gets cut mid-response. That covers a handful of very different root causes, which is exactly why it's more annoying to chase than 521-523:

  • Empty response body with a 200 status (or no status line at all)
  • Response headers larger than Cloudflare's limit (roughly 8KB combined by default)
  • The origin sends a raw TCP RST instead of closing the connection cleanly
  • A malformed or duplicate Content-Length/Transfer-Encoding header
  • PHP-FPM or your app crashes mid-request, killing the socket before Nginx/Apache can send a proper response

The tell: your own server logs are often unhelpful or contradictory. Nginx's access log might show a 200 for the same request Cloudflare says failed — because from the server's point of view, it did answer. The failure happened in how (or whether) that answer reached Cloudflare intact.

Cause: Why the Origin's Response Confuses Cloudflare

1. A security module is silently killing the connection

ModSecurity, Imunify360, or a WAF rule can match a request, decide to block it, and terminate the connection abruptly instead of returning a clean 403. Cloudflare sees the dropped connection and reports 520 rather than a proper block page. If 520s cluster around specific URLs (login pages, POST requests, admin-ajax.php), this is the first thing to check.

2. PHP-FPM workers are crashing or running out

If pm.max_children is too low for your traffic, PHP-FPM starts queueing or killing requests once the pool is exhausted. A request that gets killed mid-execution can leave Nginx with nothing to forward, which Cloudflare reports as 520 instead of a 502 in some configurations.

3. Oversized response headers

WordPress security plugins, session cookies, or a misconfigured caching layer can stack up cookies and custom headers until the combined header size trips Cloudflare's limit. This is common right after adding a new plugin that sets multiple cookies per request.

4. A stale or half-open keep-alive connection

If Nginx or Apache's keep-alive timeout is shorter than Cloudflare's, Cloudflare can reuse a connection to your origin that the origin already closed on its end. The next request over that "dead" connection gets a broken response.

5. Origin firewall or fail2ban rate-limiting Cloudflare's own IPs

If you're not using the real visitor IP restore rules and CSF/fail2ban sees a burst of requests all appearing to come from a Cloudflare edge IP, it can temporarily block that IP mid-session — cutting connections that were already in progress.

Fix: Working Through It in Order

Step 1 — Reproduce it directly against the origin

Bypass Cloudflare and hit the server directly to see if the problem is really at your origin:

curl -v --resolve yourdomain.com:443:YOUR_SERVER_IP https://yourdomain.com/

If this also fails or hangs, you're dealing with a server-side issue, not a Cloudflare-specific quirk. If it works fine here but fails through Cloudflare, look closer at headers, response size, and keep-alive settings.

Step 2 — Check for a WAF or security plugin killing the connection

In cPanel, check ModSecurity's audit log (WHM > ModSecurity Tools) or Imunify360's log for entries matching the timestamps of your 520s. On WordPress, temporarily disable Wordfence, All In One WP Security, or similar and retest — if the 520s stop, you've found it. Whitelist Cloudflare's IP ranges in the security tool rather than leaving it fully disabled:

# Cloudflare's current IP ranges
https://www.cloudflare.com/ips-v4
https://www.cloudflare.com/ips-v6

Step 3 — Raise PHP-FPM capacity if workers are maxed out

Check your pool's error log for "server reached pm.max_children" warnings:

tail -f /var/log/php-fpm/www-error.log
# or, in cPanel:
tail -f /opt/cpanel/ea-php*/root/usr/var/log/php-fpm/error_log

If you're seeing that message, increase pm.max_children in MultiPHP INI Editor or the pool config, restart PHP-FPM, and monitor memory usage afterward — each extra child costs RAM.

Step 4 — Trim response headers

Count what a real request is sending back:

curl -sI https://yourdomain.com/ | wc -c

If it's creeping past a few KB, look for plugins setting excessive cookies, or a caching layer stacking Vary and Set-Cookie headers on every response. Consolidate or drop what you don't need.

Step 5 — Align keep-alive timeouts

Set your origin's keep-alive timeout higher than Cloudflare's (Cloudflare holds connections open for up to 100 seconds by default). In Nginx:

keepalive_timeout 120s;
keepalive_requests 1000;

Reload Nginx (nginx -t && systemctl reload nginx) and re-test.

Step 6 — Make sure your firewall trusts Cloudflare's IPs

Confirm CSF, UFW, or fail2ban aren't rate-limiting or banning Cloudflare edge IPs mid-session, and that your web server config is restoring the real visitor IP (via mod_cloudflare/mod_remoteip or Cloudflare's real-IP header) so your own rate limits apply to actual visitors, not to Cloudflare itself.

Prevention

  • Whitelist Cloudflare's IP ranges in any WAF or brute-force tool, and set them to refresh periodically — Cloudflare's ranges change occasionally.
  • Size your PHP-FPM pool with headroom above normal peak traffic, and use monitoring (Netdata or similar) that flags it early when usage climbs toward the ceiling.
  • Keep an eye on response header size after installing new plugins, especially anything that sets its own session cookie.
  • Match keep-alive timeouts between Cloudflare and your origin rather than leaving server defaults untouched.
  • Enable Cloudflare's "Always Online" or at least set up an uptime monitor that alerts you the moment 5xx responses start, rather than waiting for a support ticket.

Frequently Asked Questions

Is Error 520 the same as a 502 Bad Gateway?

Not exactly. A 502 usually means your reverse proxy (Nginx) got a bad response from an upstream (PHP-FPM) it's talking to directly. A 520 is Cloudflare's way of saying it got a response from your origin that didn't fit HTTP rules at all — the failure is one layer further out, between Cloudflare's edge and your entire origin, not between two local services.

Why does my error log show a 200 when Cloudflare reported 520?

Your web server can log a request as successfully handled while the actual bytes sent back never reached Cloudflare intact — a dropped connection, a truncated response, or a security module intercepting it after the log entry was written. The server-side log and the Cloudflare-side error describe two different points in the same request's journey.

Can DNS-only (grey cloud) mode fix this temporarily?

Yes, switching a record to DNS-only bypasses Cloudflare's proxy entirely, so visitors connect straight to your server and never see a 520. It's a reasonable way to confirm the site itself is fine while you debug, but it also removes Cloudflare's caching, WAF, and DDoS protection for that record — put it back on proxy once you've found the fix.

Could a plugin update be the actual trigger?

Very often, yes. A plugin that starts setting a new cookie, adds a redirect loop under specific conditions, or changes how it handles POST requests can be enough to tip you over a header-size or connection-handling edge case that wasn't a problem before. Check your site's changelog for anything updated right before the 520s started.

Do I need to open a Cloudflare support ticket for this?

Usually not — 520 is almost always something to fix at the origin, and Cloudflare's own dashboard (Analytics > the specific ray ID in an error page) gives you enough detail to correlate with your server logs. Reach out to Cloudflare only if you've ruled out the origin entirely and direct requests (bypassing the proxy) still fail in a way that only appears differently through their edge.