Quick answer
Cloudflare 524 is a Cloudflare-generated timeout: your origin server held the connection for more than 100 seconds without sending response headers. The fix is either to make the request finish faster or to stop serving it synchronously — and for anything genuinely long-running, it should be a background job, not a web request.
What 524 actually means
Cloudflare has a hard 100-second cap on how long it'll wait for the origin to send the first byte of the response. Once that clock runs out, Cloudflare kills the connection and shows the visitor "A timeout occurred" with error code 524. This isn't the same as a 522 (TCP handshake failed) or a 504 (origin returned a timeout itself). 524 means the TCP connection was fine, the request reached your server, and then your server just... sat there.
I've seen this most often on WordPress sites running a slow WooCommerce order export, Magento admin reports, Laravel apps chewing through a bulk API sync, and any PHP endpoint that hits a third-party API with no timeout set. The classic trigger: someone clicks "Export all orders" for a customer with 40,000 line items, and the PHP process grinds for three minutes generating a CSV in memory. Cloudflare drops the connection at 100 seconds. The user sees 524 while PHP happily keeps working on the server, which is even worse — now you've got orphan processes stacking up.
Fix it properly — numbered steps
-
Confirm it's really an origin timing issue. Tail your web server logs while reproducing the error. On nginx:
tail -f /var/log/nginx/error.log /var/log/nginx/access.log | grep -E "(upstream|timeout|499|524)"If you see
upstream timed out (110: Connection timed out)and a request_uri that matches the failing action, you've found the culprit. If the logs show the request completing in 2 seconds, your problem is elsewhere — check for a firewall silently dropping packets from Cloudflare IPs. -
Measure how long the request actually takes. Bypass Cloudflare temporarily by hitting the origin IP directly with a Host header:
curl -o /dev/null -s -w "Total: %{time_total}s\n" -H "Host: example.com" https://ORIGIN_IP/your/slow/endpointIf it's 130 seconds, you know the origin is the bottleneck. If it's 30 seconds, Cloudflare isn't your problem — something between you and the origin is.
-
Raise your own timeouts first, not Cloudflare's. Nginx defaults are stingy for this kind of work. In your server block or http context:
proxy_connect_timeout 60s; proxy_send_timeout 180s; proxy_read_timeout 180s; fastcgi_read_timeout 180s; send_timeout 180s;Reload nginx (
nginx -t && systemctl reload nginx). For PHP-FPM, bumprequest_terminate_timeoutin your pool config andmax_execution_timein php.ini. Apache users:TimeoutandProxyTimeoutdirectives. Don't set these to 0 or infinity — you'll end up with hung workers and a dead pool. -
Bump Cloudflare's Enterprise timeout if you have it. Only Enterprise plans let you raise the 100-second proxy timeout, up to 6000 seconds, via the Timeout setting under Rules → Settings, or via the API:
curl -X PATCH "https://api.cloudflare.com/client/v4/zones/ZONE_ID/settings/proxy_read_timeout" \ -H "Authorization: Bearer API_TOKEN" \ -H "Content-Type: application/json" \ --data '{"value":"300"}'If you're on Free, Pro, or Business, you can't change this. Period. Which brings us to the real answer.
-
Move the work off the request thread. This is the fix. Long exports, big reports, bulk syncs — none of those belong in a synchronous HTTP request. Push them to a queue and return immediately. Laravel has Horizon, Rails has Sidekiq, WordPress has Action Scheduler, and anything Python-shaped has Celery or RQ. The endpoint's job becomes: validate input, enqueue a job, return a job ID and a 202. The UI polls for status or emails the result. This solves 524 permanently and stops the orphan-process problem too.
If that doesn't work — alternative fixes
- Enable caching for the slow endpoint. If it's a report that doesn't need to be real-time, stick Cloudflare in front of it with a Cache Rule and a 15-minute edge TTL. The first request is slow, everyone else gets a hit from the edge.
- Stream the response. If you must keep it synchronous, send headers early and stream the body. PHP has
ob_implicit_flush(true)plusflush(); Python has generators; Go and Node do this natively. Cloudflare will hold the connection as long as bytes keep flowing. - Use a bypass subdomain. If you need a truly long-running endpoint, put it on
jobs.example.comwith the orange cloud toggled off (grey cloud / DNS-only). You lose Cloudflare's proxy protection, so lock it down with auth and rate limits at the origin. - Chunk the work. Split the export into pages of 500 rows, and have the front-end fire sequential AJAX calls. Each stays under 100 seconds, progress is visible to the user, and you don't need any infrastructure changes.
Prevention
Set an alarm on p95 origin response time in your monitoring (Cloudflare Analytics, Datadog, or even a Prometheus blackbox exporter). Anything trending past 30 seconds is a warning shot — you've got 70 seconds of headroom before 524s start showing up in production. And audit third-party API calls: I've lost count of how many 524 incidents traced back to a curl call with no CURLOPT_TIMEOUT set, hanging on a vendor's dead endpoint. Always set explicit timeouts on outgoing calls. They're the most common silent killer of origin response times.