Skip to content
Control Plane Labs

524 A Timeout Occurred

Server error response defined by developers.cloudflare.com.

Last updated July 27, 2026

Common causes at a glance

  • A synchronous request doing work that exceeds the proxy read timeout
  • An unindexed database query or report generation running inline
  • An origin under resource pressure that cannot schedule the request
  • A large export endpoint proxied through Cloudflare instead of backgrounded

Reproduce it

curl -sS -o /dev/null -w '%{http_code} %{time_total}\n' --max-time 130 https://www.example.com/slow-report

What 524 A Timeout Occurred means

This is the one you fix with architecture rather than firewall rules. Cloudflare’s documented default proxy read timeout is 125 seconds: the origin accepts the connection, acknowledges the request, and then has that long to produce a response. There is a shorter window on the write side too — a documented 30-second proxy write timeout that cannot be adjusted.

Do not confuse 524 with 522, where the handshake never finished. Here everything worked and your code is still running. It is the closest of these to a standard 504: an intermediary ran out of patience with an upstream that was still thinking.

There is no RFC

524 is unregistered. It does not appear in the IANA HTTP Status Code Registry; the only source is Cloudflare’s documentation. Per RFC 9110 §15, clients that do not recognise it fall back to 500. The origin-log consequence is subtler here than elsewhere in this set. Your server does see the request, because it accepted the connection and started work. What it never sees is a 524 — it logs whatever it eventually produced, often a 200 minutes later, written to a socket Cloudflare closed long before. That mismatch is the signature.

What actually causes it

Almost every 524 traces to work that should not be happening inside a request. Report generation, bulk exports, PDF rendering, and cache-warming endpoints all start fast enough on a small dataset and cross the threshold as data grows. Because the limit is time rather than volume, the failure arrives suddenly: the endpoint that took 90 seconds last quarter takes 130 this quarter and errors for everyone at once. Resource contention does the same with no code change at all.

How to debug it

Time the request against the origin directly, with a ceiling above the edge’s, so you learn how long the work takes rather than when Cloudflare stopped waiting:

curl -s -o /dev/null -w 'ttfb=%{time_starttransfer} total=%{time_total}\n' \
--max-time 300 --resolve www.example.com:443:203.0.113.10 \
https://www.example.com/slow-report

If the origin answers in 200 seconds you have your number, and no amount of firewall auditing will help. Log response times at the origin and watch the P95, not the mean — 524s come from the tail. The durable fix is to stop holding the connection open: return 202 Accepted with a job ID and let the client poll. Failing that, Cloudflare documents moving the endpoint to a DNS-only, unproxied record. Enterprise zones can raise the read timeout, but that extends the deadline rather than repairing it.

Working examples

curl

# Measure the real duration at the origin, above Cloudflare's ceiling.
curl -s -o /dev/null \
  -w 'ttfb=%{time_starttransfer} total=%{time_total}\n' \
  --max-time 300 \
  --resolve www.example.com:443:203.0.113.10 \
  https://www.example.com/slow-report

# Through the edge, for comparison: this returns 524 much sooner.
curl -s -o /dev/null -w '%{http_code} %{time_total}\n' \
  https://www.example.com/slow-report

Python (requests)

import requests

# Through Cloudflare: the edge gives up first and hands you a 524.
resp = requests.get("https://www.example.com/slow-report", timeout=300)
print(resp.status_code, resp.elapsed.total_seconds())

# Against the origin: how long the work genuinely takes.
direct = requests.get(
    "https://203.0.113.10/slow-report",
    headers={"Host": "www.example.com"},
    verify=False,
    timeout=300,
)
print(direct.status_code, direct.elapsed.total_seconds())
# If this is well past the proxy read timeout, background the job.

Node (fetch)

// The 524 arrives from the edge; your own timeout never fires.
const started = Date.now();
const res = await fetch("https://www.example.com/slow-report", {
  signal: AbortSignal.timeout(300_000),
});
console.log(res.status, (Date.now() - started) / 1000, "s");

// The shape that actually fixes this: accept, queue, poll.
if (res.status === 524) {
  const job = await fetch("https://www.example.com/reports", { method: "POST" });
  console.log("queued:", job.status, job.headers.get("location"));
}

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