520 Web Server Returns an Unknown Error
Server error response defined by developers.cloudflare.com.
Last updated July 27, 2026
Common causes at a glance
- Origin response headers over 128 KB, usually runaway cookies
- A backend process crashing mid-response and closing the socket early
- HTTP/2 advertised via ALPN at the origin but not correctly implemented
- A firewall or security plugin resetting connections from Cloudflare IP ranges
Reproduce it
curl -sD - -o /dev/null https://www.example.com/ | grep -iE '^(HTTP|cf-ray|server)'What 520 Web Server Returns an Unknown Error means
Treat 520 as “the origin answered, but not in HTTP”. The TCP connection succeeded, so this is not 521 or 522. TLS succeeded, so it is not 525. The response itself failed. Cloudflare documents the usual shapes: headers exceeding 128 KB, typically from cookie accumulation; a reply with no status line or body; an origin that advertises HTTP/2 through ALPN and then does not honour the protocol.
The other big producer is a process dying mid-response. Cloudflare calls out PHP applications that crash the web server, which is what a segfaulting FPM worker looks like from outside: connection accepted, socket closed early.
There is no RFC
520 is not in the IANA HTTP Status Code Registry. Cloudflare invented it for its 5xx troubleshooting docs. RFC 9110 §15 says a client that does not recognise a status code should read the class from the first digit and treat it as the x00 of that class, so 520 becomes 500 downstream. The practical consequence matters more: this page is generated by Cloudflare’s edge, not your origin, so your access log holds no 520 line. It may hold a 200 that Cloudflare rejected, or nothing.
What actually causes it
The cookie case is the one people miss. Header size grows
quietly as analytics and session middleware pile on Set-Cookie
values, and one day a logged-in user with a full cookie jar crosses the limit
while anonymous traffic stays healthy. That produces a 520 which only
reproduces when authenticated, sending everyone hunting through application
code instead of the header budget. The HTTP/2 case is sneakier still: enabling
h2 is a one-line change, and a half-finished implementation gets negotiated and
then fails to read frames. There is a
switch to disable HTTP/2 to origin for that reason.
How to debug it
Bisect the edge out of the path first. Point curl at the origin IP while keeping SNI and the Host header intact:
curl -sv --resolve www.example.com:443:203.0.113.10 \
https://www.example.com/ 2>&1 | grep -E '^[<>*] '
Clean HTTP means the response is mangled between your server and the edge, or only under Cloudflare’s request shape. Garbage, an empty reply, or a hangup after the headers means you reproduced the fault without Cloudflare. Then measure header size, since 128 KB is checkable rather than a guess, and capture the cf-ray value — it is the only handle Cloudflare support has on your request. The header inspector makes the comparison in a browser.
Working examples
curl
# Compare what Cloudflare sees with what the origin actually sends.
# --resolve keeps SNI and Host correct while bypassing the edge.
curl -sv --resolve www.example.com:443:203.0.113.10 \
https://www.example.com/ 2>&1 | grep -E '^[<>*] '
# Header budget check: 520 fires above 128 KB of response headers.
curl -sD headers.txt -o /dev/null https://www.example.com/ && wc -c headers.txt
Python (requests)
import requests
# Hit the edge, then the origin, and diff the two.
edge = requests.get("https://www.example.com/", timeout=30)
print("edge:", edge.status_code, edge.headers.get("cf-ray"))
# Same request straight at the origin IP. TLS verification is relaxed
# because the certificate will not match a bare IP address.
origin = requests.get(
"https://203.0.113.10/",
headers={"Host": "www.example.com"},
verify=False,
timeout=30,
)
print("origin:", origin.status_code)
print("header bytes:", sum(len(k) + len(v) + 4 for k, v in origin.headers.items()))
Node (fetch)
// A 520 arrives as an ordinary response object; nothing throws.
const res = await fetch("https://www.example.com/");
console.log(res.status, res.headers.get("cf-ray"));
if (res.status === 520) {
// The body is a Cloudflare-generated page, not your application's output.
const html = await res.text();
console.log("edge page:", html.includes("Web server returns an unknown error"));
console.log("check origin logs for a matching request — there may not be one");
}Reference and tooling
The authoritative list of assigned status codes is theIANA HTTP Status Code Registry. For a searchable copy with the semantics summarised, use theHTTP status code reference on this site. To see exactly what a server is sending, theheader inspector shows the raw status line and response headers, and thecurl builder assembles the request flags for you.
Other 5xx codes
- 500 Internal Server Error
- 501 Not Implemented
- 502 Bad Gateway
- 503 Service Unavailable
- 504 Gateway Timeout
- 505 HTTP Version Not Supported
- 506 Variant Also Negotiates
- 507 Insufficient Storage
- 508 Loop Detected
- 510 Not Extended
- 511 Network Authentication Required
- 521 Web Server Is Down
- 522 Connection Timed Out
- 523 Origin Is Unreachable
- 524 A Timeout Occurred
- 525 SSL Handshake Failed
- 526 Invalid SSL Certificate
- 530 Unable to Resolve Origin Hostname