Short answer: Cloudflare error 524 “A timeout occurred” means Cloudflare connected to your origin server and sent the request, but your server did not start answering within Cloudflare’s Proxy Read Timeout. That limit is 125 seconds by default. Older guides say 100 seconds, but Cloudflare’s documentation now gives 125. Your server is up. It is just too slow for that request: a long export or import, a slow database query, a hung call to an outside API, or so much load that requests wait in line. Raising nginx or PHP timeouts does not help. Outside Enterprise plans, you fix a 524 by making the request faster, moving the work out of the request, or serving it from a hostname Cloudflare does not proxy.
curl -sSk -m 300 -o /dev/null -w 'first byte after %{time_starttransfer}s, status %{http_code}\n' \
--resolve example.com:443:203.0.113.10 https://example.com/slow/pathsudo awk '$9 == 499' /var/log/nginx/access.log | tailReplace example.com/slow/path with the URL that fails and 203.0.113.10 with your server’s IP; -k accepts a Cloudflare Origin CA certificate for this test. If your firewall admits only Cloudflare’s ranges, run the curl line on the server itself with 127.0.0.1 as the address.
If the first byte arrives after more than 125 seconds, or the request never finishes, you have reproduced the 524 without Cloudflare. A 499 in the nginx log is nginx’s own code for “the client closed the connection before the response was ready”. Behind Cloudflare, the client that gave up is Cloudflare. A 499 at the moment of a 524 shows you the URL, and your server finished that work for nobody.
Which timeout fires first?
A request behind Cloudflare passes several clocks. The one that runs out first decides what the visitor sees:
| Clock | Default | What the visitor sees |
|---|---|---|
| Cloudflare: Proxy Read Timeout | 125 s; Enterprise can raise it to 6,000 s | 524 |
| Cloudflare: Proxy Write Timeout | 30 s, not adjustable | 524 |
nginx: proxy_read_timeout, fastcgi_read_timeout | 60 s, counted between two reads | 504 from nginx, passed through Cloudflare |
PHP: max_execution_time | 30 s, on Linux counting only the script’s own run time, not database waits, sleeps or network calls | A PHP fatal error, usually a 500 |
PHP-FPM: request_terminate_timeout | 0, which means off | Worker killed; nginx answers 502 |
With stock settings, nginx gives up at 60 seconds and sends its own 504, long before Cloudflare’s 125. So a 524 on an nginx server usually means someone raised proxy_read_timeout or fastcgi_read_timeout, often to cure earlier 504s. It can also mean the server is so busy it never picked up the request. Apache with mod_php has no wall-clock limit on the script itself. Node, Python and Go app servers that face Cloudflare directly often have none either. If you see a 504 instead, the origin timed out first. The nginx 504 guide covers that case.
Cause 1: the request really takes more than two minutes
Typical cases: CSV and PDF exports, reports over large tables, bulk imports, backups or updates started from a web admin, image and video processing, and sitemap or feed generation. In the Cloudflare dashboard, Speed → Origin Analytics plots your origin’s response time at the 95th and 99th percentile against the zone’s timeout, and its Top endpoints table ranks paths by that time. Its clock stops when your response headers arrive, the same moment that decides a 524. On the server, find the slow URLs by logging the time nginx spent on each request:
log_format timing '$remote_addr [$time_local] "$request" $status rt=$request_time uht=$upstream_header_time urt=$upstream_response_time';Then log to it from the site’s server block. If the block has no access_log line yet, add both lines below: a server block with an access_log line of its own stops inheriting the global one, so the first line keeps the normal access log going. If it already has one, keep it and add only the second.
access_log /var/log/nginx/access.log;
access_log /var/log/nginx/timing.log timing;sudo nginx -t && sudo systemctl reload nginxAfter the next slow request, list every request that took 60 seconds or more:
sudo grep -E ' rt=([6-9][0-9]|[1-9][0-9]{2,})\.' /var/log/nginx/timing.log | tail -n 20198.51.100.7 [27/Oct/2026:09:14:03 +0000] "GET /admin/export.csv HTTP/1.1" 499 rt=125.012 uht=- urt=125.012
203.0.113.50 [27/Oct/2026:09:20:41 +0000] "GET /reports/yearly HTTP/1.1" 200 rt=74.310 uht=74.297 urt=74.310uht is how long the backend took to send its response headers, which is what Cloudflare’s 125-second clock waits for; urt adds the body. The first line is a 524 seen from your side: Cloudflare gave up after 125 seconds, nginx logged a 499, and uht=- means no response headers had arrived from the backend yet. The second request was slow but answered in time.
For PHP, the FPM slow log shows where the time goes. Every request that runs longer than the threshold leaves a backtrace naming the script and the function it was stuck in:
request_slowlog_timeout = 20s
slowlog = /var/log/php8.3-fpm.slow.logsudo php-fpm8.3 -t && sudo systemctl reload php8.3-fpm
sudo tail -n 40 /var/log/php8.3-fpm.slow.log # after the next slow requestCause 2: the app waits on a database or an outside API
Often the code itself is fast, and it waits on something else. Look at the database while a slow request is running:
sudo mysql -e "SHOW FULL PROCESSLIST;" # long Time values, "Waiting for ... lock"
sudo mysql -e "SET GLOBAL slow_query_log = ON; SET GLOBAL long_query_time = 5;"
sudo mysql -e "SHOW VARIABLES LIKE 'slow_query_log_file';"sudo -u postgres psql -c "SELECT pid, now() - query_start AS runtime, state, query
FROM pg_stat_activity WHERE state <> 'idle' ORDER BY runtime DESC;"A query that runs for minutes needs an index, a LIMIT with pagination, or to run in the background. A query that waits on a lock needs you to find the transaction holding the lock. Outside APIs are the other classic: a payment, shipping or licence call with no timeout hangs for as long as the other side does. Remember that PHP’s max_execution_time does not count that waiting on Linux. To see which outside hosts your PHP workers are talking to at the moment:
sudo ss -tnp state established '( dport = :443 or dport = :80 )' | grep -E 'php|node|python'Give every outbound HTTP call an explicit connect and total timeout in your HTTP client, well under 125 seconds. Then decide what the page does when the other side is slow.
Cause 3: the server is overloaded and requests queue up
Here each request is quick on its own, but it waits a long time for a free worker. When every Apache worker is busy, the kernel still accepts new connections, and Cloudflare sends its request into the queue, so its clock is running. With PHP-FPM behind nginx the same happens once nginx’s timeouts are raised past 125 seconds. With the defaults you would see 504s first. Two warnings in the logs give it away:
sudo grep -E 'max_children' /var/log/php8.3-fpm.log | tail # "server reached pm.max_children setting"
sudo grep -E 'MaxRequestWorkers' /var/log/apache2/error.log | tail
uptime
vmstat 1 5 # high "wa" = waiting on disk, "si"/"so" = swapping, "st" = stealThis cause feeds on itself. When Cloudflare gives up, your server keeps working on the request. The visitor presses reload and starts another one, so the queue grows. Find out what holds the workers first (Cause 1 or 2 usually hides underneath), and see VPS high CPU usage if the CPU is pinned. If the server is simply too small for real traffic, give it room, sized to how the load looks: a Standard KVM carries more vCores and memory per plan for many parallel workers, and a Hi-CPU KVM is meant for workloads that lean on one or two cores rather than many.
Cause 4: a large upload stalls on the way in
The second clock in the table is Cloudflare’s Proxy Write Timeout. If Cloudflare cannot finish writing a request to your origin within 30 seconds, that also ends in 524, and you cannot change this limit. It mostly affects big uploads to an origin that reads them slowly, for example because the disk is saturated. Move big transfers to a non-proxied hostname (below) or to SFTP.
How to fix error 524 for good
- 1Make the request fast enough
Add the missing index, paginate, cache the expensive part, or split one huge request into several small ones. Anything that reliably finishes in a few seconds no longer needs a workaround.
- 2Move long work into a background job
This is the fix Cloudflare itself suggests, and the one that scales. The request only queues the job and returns at once. The browser then polls a status URL until the result is ready:
the pattern POST /exports -> 202 Accepted {"job": "8f3c"} GET /exports/8f3c -> 200 {"status": "running", "progress": 40} GET /exports/8f3c -> 200 {"status": "done", "file": "/downloads/8f3c.csv"}Laravel queues, Symfony Messenger, Celery, RQ, Sidekiq and BullMQ all do this. Run the worker as a service so it survives reboots and crashes. Here is a Laravel example; the other frameworks follow the same shape:
/etc/systemd/system/app-worker.service [Unit] Description=App queue worker After=network.target [Service] User=www-data WorkingDirectory=/var/www/app ExecStart=/usr/bin/php artisan queue:work --sleep=3 --tries=3 --max-time=3600 Restart=always [Install] WantedBy=multi-user.targetEnable it with
sudo systemctl enable --now app-worker. The details are in creating a systemd service. - 3Run admin jobs from the shell, not the browser
Database imports, search-and-replace across a site, and bulk updates do not need HTTP at all. Over SSH they have no 125-second limit:
Debian · Ubuntu mysql -u appuser -p appdb < dump.sql # instead of a web importer sudo -u www-data wp --path=/var/www/html plugin update --all sudo -u www-data wp --path=/var/www/html cron event run --due-nowOn WordPress, also set
define( 'DISABLE_WP_CRON', true );inwp-config.phpand run the last command from the system crontab every few minutes. Scheduled tasks then no longer run through web requests. - 4Serve the long requests from a DNS-only hostname
For requests that are long by design, such as an internal report or a large export, Cloudflare recommends a subdomain set to DNS only (grey cloud). Cloudflare does not proxy it, so its timeout does not apply:
- Create an A record such as
reports.example.compointing at the origin, with the proxy turned off. - Give it its own certificate on the server, for example with
sudo certbot --nginx -d reports.example.com. - If your firewall only admits Cloudflare’s ranges, also allow port 443 from the IPs that need the report. A firewall sees addresses, not hostnames, so protect the hostname itself too, with a password or an nginx
allow/denylist.
The cost is real. A DNS-only record publishes the server’s IP, which is the same IP that serves the proxied site. Requests to it skip Cloudflare’s cache and firewall. VMHeaven VPS plans include always-on DDoS protection in both Amsterdam and Frankfurt, but do not expose more than you need to.
- Create an A record such as
- 5Keep the origin’s timeouts below Cloudflare’s
Once long work runs elsewhere, make the web tier fail fast. A request that runs past the limit then gets killed and logged on your side, instead of holding a worker for a visitor Cloudflare has already given up on:
nginx · in the PHP location block fastcgi_read_timeout 120s;/etc/php/8.3/fpm/pool.d/www.conf request_terminate_timeout = 110sDebian · Ubuntu sudo nginx -t && sudo systemctl reload nginx sudo php-fpm8.3 -t && sudo systemctl reload php8.3-fpmFPM’s limit sits a little below nginx’s, so a runaway request is ended by PHP-FPM and logged there rather than racing nginx to the same second.
On an Enterprise plan you can instead raise the Proxy Read Timeout itself, up to 6,000 seconds. You do that with a Cache Rule, which only applies to requests that are eligible for caching (they do not have to be cached), or for the whole zone through the API.
524 compared with 520, 521, 522 and 504
- 522: Cloudflare could not even connect. That points at a firewall, DNS or a server that is down, not at slow code. See error 522.
- 521: the origin refused the connection outright, because the web server is stopped or a firewall rejects Cloudflare’s addresses. See error 521.
- 520: the origin answered, but with nothing usable: an empty reply, a reset or oversized headers. See error 520.
- 504 behind Cloudflare: your own proxy gave up before Cloudflare did.
If you are a visitor, a 524 is not your fault, and reloading only adds to the server’s queue. Wait a minute, try again, and tell the site owner if it keeps happening.
Frequently asked
How long does Cloudflare wait before showing error 524?
125 seconds by default, counted from when Cloudflare sends the request until your server starts its response. Older articles say 100 seconds, but Cloudflare's current documentation gives 125. Only Enterprise plans can raise it, up to 6,000 seconds.
Can I increase the Cloudflare 524 timeout on a Free or Pro plan?
No. The Proxy Read Timeout can only be changed on Enterprise plans. On other plans, make the request faster, move the work into a background job that the page polls, or serve long requests from a subdomain set to DNS only.
Does my server stop working when Cloudflare shows error 524?
No. Cloudflare stops waiting, but your server keeps processing the request, and nginx usually logs it as a 499. If visitors keep reloading, those abandoned requests pile up, so cap your own timeouts below 125 seconds and move long work out of web requests.