Troubleshooting

Fix 504 Gateway Timeout in Nginx

A 504 Gateway Timeout means PHP or your app took longer to answer than Nginx was willing to wait. Find the slow request, raise the right timeouts consistently, and fix the root cause.

IntermediateLinux server5 min read

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:

Bash
sudo tail -f /var/log/nginx/error.log

The 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 need fastcgi_read_timeout.
  • http://127.0.0.1:3000 means a proxied app. You need proxy_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:

Bash
top

A 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):

INI
request_slowlog_timeout = 10s
slowlog = /var/log/php-fpm-slow.log

Restart 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:

Bash
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:

Nginx
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:

Nginx
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:

INI
max_execution_time = 300

In 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:

INI
request_terminate_timeout = 300s

Restart PHP-FPM:

Bash
sudo systemctl restart php8.3-fpm

On 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

Bash
sudo nginx -t && sudo systemctl reload nginx

Warning: 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:

Bash
curl -s -o /dev/null -w 'status %{http_code}, %{time_total}s\n' https://example.com/slow-page

You 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_timeout or max_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.
Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.