504 Gateway Timeout means Nginx passed a request to PHP-FPM or your app, waited, and gave up. By default it waits 60 seconds. The backend may still be working, but the visitor gets an error. Sometimes the right fix is a longer timeout for one slow admin task; more often it is a slow database query or an outside API that needs fixing. This guide covers both.
Before you start
- A Linux VPS running Nginx in front of PHP-FPM or an app server.
- SSH access with sudo.
- An idea of which page or action times out: an import, a report, a checkout, or every page.
Confirm it is a timeout
Check the error log while you repeat the slow action:
sudo tail -f /var/log/nginx/error.logThe message you are looking for:
upstream timed out (110: Connection timed out) while reading response header from upstream, client: 198.51.100.20, server: example.com, request: "POST /wp-admin/admin-ajax.php HTTP/2.0", upstream: "fastcgi://unix:/run/php/php8.3-fpm.sock"fastcgi://means PHP-FPM. You needfastcgi_read_timeout.http://127.0.0.1:3000means a proxied app. You needproxy_read_timeout.
If you see 502 and "connection refused" instead, the backend is down, not slow. See Fix 502 Bad Gateway in Nginx.
Note: If your site is behind Cloudflare, a timeout page branded by Cloudflare (error 524) comes from Cloudflare's own limit of about 100 seconds on standard plans. Raising Nginx timeouts beyond that will not help for proxied requests.
Find what is slow
Before raising limits, find out why the request takes so long.
Check server load while the action runs:
topA PHP or database process at 100% CPU, or a high wa (I/O wait) value, points to heavy work or slow disk.
For PHP, turn on the slow log in your pool file (/etc/php/8.3/fpm/pool.d/www.conf on Ubuntu/Debian, /etc/php-fpm.d/www.conf on AlmaLinux/Rocky):
request_slowlog_timeout = 10s
slowlog = /var/log/php-fpm-slow.logRestart PHP-FPM. Any request running over 10 seconds now writes a stack trace to the slow log, naming the function that is stuck: often a database call, curl_exec to an outside API, or a plugin loop.
For MySQL or MariaDB, check running queries:
sudo mysql -e "SHOW FULL PROCESSLIST;"Long-running queries in that list are your likely cause. The MySQL slow query log helps track them down over time.
Raise the Nginx timeout
If the work is genuinely long, such as a large import, raise the timeout for that path only where possible.
For PHP, add the timeout to the PHP location in the site's server block:
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_read_timeout 300s;
}Keep your own include and fastcgi_pass lines; only add fastcgi_read_timeout.
For a proxied app:
location /reports/ {
proxy_pass http://127.0.0.1:3000;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}proxy_read_timeout is the time allowed between two reads from the backend, not for the whole response. An app that streams output regularly can run longer.
Match PHP's own limits
If Nginx waits 300 seconds but PHP stops the script at 30, you get a different error. Line them up.
In PHP-FPM's php.ini:
max_execution_time = 300In the PHP-FPM pool file, request_terminate_timeout (if set) kills workers after that time regardless of php.ini. Leave it unset or make it equal to or longer than max_execution_time:
request_terminate_timeout = 300sRestart PHP-FPM:
sudo systemctl restart php8.3-fpmOn AlmaLinux and Rocky, use php-fpm. The order to aim for: PHP's limit equal to or slightly below Nginx's timeout, so PHP fails cleanly with a logged error instead of Nginx cutting it off.
Apply the change
sudo nginx -t && sudo systemctl reload nginxWarning: Do not set huge timeouts on every page. Each slow request holds a PHP worker. If many visitors hit slow pages at once, every worker is busy and the whole site stops responding.
Better fixes than longer timeouts
- Run long jobs in the background. Imports, exports and email sends belong in a queue or cron job, not a web request. WordPress has WP-CLI, Laravel has queues.
- Cache expensive pages. See Speed up PHP sites with Nginx FastCGI cache.
- Add database indexes for the slow queries you found.
- Set short timeouts on outside API calls in your code, so one slow service does not hang your page.
Check it works
Time the slow request from the command line:
curl -s -o /dev/null -w 'status %{http_code}, %{time_total}s\n' https://example.com/slow-pageYou should get a 200 and a total time under your new timeout. Repeat the real action in the browser and watch the error log for new upstream timed out lines.
Troubleshooting
- Still 504 at exactly 60 seconds: the new timeout is in a block that does not handle the request. Check with
sudo nginx -T | grep -n read_timeout. - Now a 502 instead of 504: PHP killed the worker. Raise
request_terminate_timeoutormax_execution_time. - Every page is slow, not just one: the server is overloaded. Check CPU, RAM and swap with Monitor Linux server resources.
- Cloudflare error 524: Cloudflare's own limit. Move the long task to a background job.
Next steps
- Tune PHP worker counts: Tune PHP-FPM pools.
- Find slow MySQL queries: MySQL slow query log.
- More CPU and RAM help heavy sites.
