Troubleshooting

Cloudflare error 520: web server is returning an unknown error — find what your origin sent back

Error 520 means your server sent Cloudflare an empty, reset or malformed response. Find the cause: a crashed worker, nginx 444, oversized cookies or HTTP/2.

Published ~9 min read

Short answer: Cloudflare error 520 “Web server is returning an unknown error” means Cloudflare connected to your origin server, but what came back was not a usable HTTP response. It was empty, cut off by a connection reset, or malformed. It is Cloudflare’s catch-all for “the origin answered with something I cannot pass on”. On a server you run yourself, the cause is nearly always one of these: the process holding the connection crashed or was killed, a config rule closes the connection on purpose (nginx return 444), response headers exceed Cloudflare’s 128 KB limit (usually cookies), the origin’s HTTP/2 is broken, or a firewall cuts connections mid-request.

Linux · macOS · from your computer
curl -sSkv -o /dev/null --resolve example.com:443:203.0.113.10 \
  https://example.com/failing/path 2>&1 | tail -n 5
Debian · Ubuntu · on the server, right after a 520
sudo tail -n 50 /var/log/nginx/error.log           # or /var/log/apache2/error.log
sudo journalctl -k --since "15 min ago" | grep -iE 'killed process|segfault'

Replace example.com/failing/path with the URL that failed and 203.0.113.10 with your origin’s IP, which the DNS records page in the Cloudflare dashboard shows. --resolve sends the request straight to the origin with the right hostname, skipping Cloudflare, -k accepts a Cloudflare Origin CA certificate for this test, and -S keeps curl’s error line visible despite -s. If your firewall only admits Cloudflare’s ranges, run the same command on the server itself with 127.0.0.1 as the address. The last lines of curl’s output tell you which kind of 520 you have:

What curl showsWhat happenedTypical cause
curl: (52) Empty reply from serverThe origin accepted the connection and closed it without sending a single bytenginx return 444, a crashed worker, an app server that exits mid-request
curl: (56) Recv failure: Connection reset by peerSomething reset the connection while the request was runningA process killed by the OOM killer, a segfault, a firewall or IPS cutting the connection
A normal status, but the headers run to many KBThe response is valid for curl and too large for CloudflareToo many or too large Set-Cookie headers, a huge custom header
Works with --http1.1, fails with --http2The origin advertises HTTP/2 and then mishandles itA broken or outdated HTTP/2 setup on the origin
Always works directly; rare, random 520s through CloudflareReused idle connections are being closed under CloudflareKeep-alive disabled or set to a few seconds at the origin

520 or 502: which process talks to Cloudflare?

Before you chase causes, look at what holds Cloudflare’s connection on your server. The answer decides which error you can get:

  • nginx in front of PHP-FPM, Node, gunicorn or similar: if the backend dies, nginx is still alive and answers with a proper 502, which Cloudflare passes through. A 520 then means nginx itself sent nothing usable: a 444, a crashed worker, or headers that are too large. The nginx 502 guide covers the backend side.
  • Apache with mod_php: PHP runs inside the Apache worker, so a PHP crash kills the process that holds the connection, and nothing is left to write an error page. That fits Cloudflare’s own note that 520s are common with PHP applications that crash the web server.
  • An app server listening on 80 or 443 directly (Node, Go, Python): any unhandled crash or restart drops the connection and gives a 520.

Cause 1: the process crashed or was killed mid-request

This is the most common 520 on a VPS. Confirm it from the logs written at the moment of the error:

Debian · Ubuntu
sudo grep -E 'exited on signal' /var/log/nginx/error.log | tail
sudo grep -E 'exit signal' /var/log/apache2/error.log | tail        # e.g. AH00052 ... Segmentation fault (11)
sudo journalctl -k --since today | grep -i 'killed process'        # the kernel's OOM killer
systemctl status your-app                                           # restarts and exit codes of an app server
sudo journalctl -u your-app --since "1 hour ago" | tail -n 50
  • Out of memory: Killed process … (apache2), or the name of your app: the server ran out of RAM and the kernel killed the process that was serving the request. Cap the worker count to what fits in memory (Apache MaxRequestWorkers, PHP-FPM pm.max_children), find the request that balloons, and add swap as a buffer. The details are in processes killed when memory runs out.
  • exited on signal 11 or Segmentation fault (11): a real crash, usually in a PHP extension, an Apache module or a third-party nginx module. Note which URL was requested at that second in the access log, update the packages, and disable recently added modules one at a time until it stops.
  • An app server that restarts: systemd shows the restart counter and the exit status. Fix the crash in the application. In the meantime, putting nginx in front turns each crash into a logged 502 instead of an anonymous 520.

If the crashes come from memory pressure at normal traffic, the server is too small. More memory per worker, from a larger Standard KVM plan for example, fixes that for good where tuning only buys time.

Cause 2: nginx closes the connection on purpose (return 444)

return 444 is nginx’s own code: it closes the connection without sending any response. Hardening guides put it in a catch-all default_server block for requests to unknown hostnames, and in bot filters. Behind Cloudflare, a 444 shows up as 520. The usual way to trigger it by accident: you add a hostname in Cloudflare, such as www or a new subdomain, but not to server_name. The request then lands in the catch-all.

Debian · Ubuntu · find 444 rules and the requests they hit
sudo nginx -T 2>/dev/null | grep -nE 'return 444|default_server|server_name'
sudo awk '$9 == 444' /var/log/nginx/access.log | tail     # combined log format: field 9 is the status

Add the missing name to the right server block, or make the filter skip legitimate traffic, then run sudo nginx -t && sudo systemctl reload nginx. A filter that matches on the client IP needs real visitor IPs first. Otherwise every request seems to come from a Cloudflare address. The error 522 guide shows the realip setup. A catch-all that uses ssl_reject_handshake on fails one step earlier, at the TLS handshake, and Cloudflare reports that as 525 (SSL handshake failed) instead.

Cause 3: response headers over 128 KB (cookies)

Cloudflare accepts at most 128 KB of response headers. An app that writes session data into cookies, sets dozens of tracking cookies, or adds a huge debug or policy header can pass that limit on some pages. Those pages get a 520 even though the origin returned 200. Measure the response headers directly:

Linux · macOS · from your computer
curl -sk -o /dev/null -D - --resolve example.com:443:203.0.113.10 https://example.com/ | wc -c
curl -sk -o /dev/null -D - --resolve example.com:443:203.0.113.10 https://example.com/ | grep -ci '^set-cookie'

A few kilobytes is normal. Tens of kilobytes means something is wrong. Test in a private window too: if the 520 only hits logged-in users or one browser, the cookie jar grows with each visit. Keep session state on the server, and send one session cookie instead of the data itself.

Cause 4: HTTP/2 between Cloudflare and the origin

Cloudflare enables HTTP/2 to Origin by default. It uses HTTP/2 whenever your server offers it during the TLS handshake. If the origin offers HTTP/2 and then handles it badly, the result is a 520. Compare both protocols against the origin:

Linux · macOS · from your computer
for v in --http1.1 --http2; do
  curl -sSk $v -o /dev/null -w "$v -> http %{http_version}, status %{http_code}\n" \
    --resolve example.com:443:203.0.113.10 https://example.com/
done
Example output · both protocols work
--http1.1 -> http 1.1, status 200
--http2 -> http 2, status 200
Debian · Ubuntu · where HTTP/2 is switched on
sudo nginx -T 2>/dev/null | grep -n http2

A failure only with --http2, such as curl: (92) or a reset, confirms it. If both lines report http 1.1, the origin does not offer HTTP/2 at all, and this cause is ruled out. Update the web server, or remove the HTTP/2 setting from the affected server block. As a quick test from the Cloudflare side, go to Speed → Settings → Protocol Optimization and turn HTTP/2 to Origin off. If the 520s stop, you have found the cause.

Cause 5: idle keep-alive connections closing under Cloudflare

Cloudflare keeps connections to your origin open and reuses them for up to 900 seconds without traffic. Its connection-limits page maps that idle limit to 520 and asks you to keep HTTP keep-alive on at the origin to avoid connection resets. The usual failure is a race: your server closes an idle connection at the moment Cloudflare sends a request down it. The pattern is distinctive: a small, random share of requests fail, the origin test always passes, and nothing shows in the error log.

  • Keep keep-alive switched on at the origin, as Cloudflare asks. Check for keepalive_timeout 0 in nginx or KeepAlive Off in Apache.
  • Short idle timeouts mean the origin closes connections all the time. Apache’s KeepAliveTimeout defaults to 5 seconds, and so does Node’s server.keepAliveTimeout up to and including Node 26 (the change to 65 seconds comes after that release line). nginx uses 75. If a Node or Python app server faces Cloudflare directly, put nginx in front of it, or raise the app’s keep-alive timeout.

Cause 6: a firewall or security layer cuts the connection

Cloudflare also lists firewalls and security plugins that block its IPs. A rule that refuses new connections gives 521 (reject) or 522 (drop). A rule that kills a connection while a request is running can give 520. Typical cases: a ban that lands on a Cloudflare address while its connection is open, an IPS that resets “suspicious” responses, or a web-application firewall module that aborts instead of returning a 403. The check for banned Cloudflare addresses and the real-IP fix are in the error 522 guide. One more setup error: if Authenticated Origin Pulls is on in Cloudflare but the origin is not set up for client certificates the way Cloudflare expects, requests fail with 520 as well.

What to check on the Cloudflare side

  • HTTP Traffic analytics (Pro plans and above; the Free plan’s version has no filters): filter by Edge status code 520, then by Origin status code. If the origin returned 200 and Cloudflare served 520, the response was malformed: oversized headers or an early close.
  • Origin Analytics (under Speed) ranks paths by error rate in its Top endpoints table, so you see which ones produce 520s. One path points at one script. Every path points at the server.
  • For a support ticket, Cloudflare asks for the full URL, the Ray ID from the error page, the output of https://example.com/cdn-cgi/trace, and two HAR files: one with Cloudflare on and one with it paused.

Once the origin test passes, check the public URL from outside: the HTTP header checker requests it through Cloudflare from our server in Amsterdam and shows the status code and the headers that come back, CF-Ray included. It sends no cookies, so a 520 that only logged-in visitors get will not show up there.

How to keep 520s away

  • Put nginx in front of every application, so a backend failure becomes a logged 502 with a reason and not an empty connection.
  • Size worker pools to memory, not to hope, so the OOM killer never picks a web worker.
  • Keep every proxied hostname in server_name, and log 444s so a catch-all never hides real traffic.
  • Watch cookie and header size after app or plugin updates.
  • Restore real visitor IPs before you enable any IP-based filter.

If the request reaches the origin and simply takes too long, you get a different error: error 524, Cloudflare’s 125-second timeout. And if the causes above come down to a server that is simply too small, pick the next one by workload: a Hi-CPU KVM for code that leans on one or two cores rather than many, a Standard KVM for many parallel workers.

Frequently asked

Is Cloudflare error 520 a Cloudflare problem or a server problem?

Almost always a server problem. Cloudflare reached your origin, but the response it got back was empty, reset or malformed, so it shows 520 instead. Test the origin directly with curl's --resolve option and read the web server and kernel logs from the time of the error.

What is the difference between Cloudflare error 520 and 502?

A 502 is a valid error response that your origin, usually nginx, sent because the application behind it failed. A 520 means the origin sent no valid response at all: the connection closed empty, was reset, or carried headers Cloudflare could not accept. Putting nginx in front of an application turns many 520s into logged 502s.

Can too many cookies cause Cloudflare error 520?

Yes. Cloudflare accepts at most 128 KB of response headers, and an application that sets many or very large cookies can pass that limit on some pages. Measure the header size with curl, then keep session data on the server and send a single session cookie.

Related articles

All troubleshooting