How to fix 504 Gateway Timeout

The backend is alive but too slow. Find the slow request, raise the right timeout only if the work is genuinely long, and fix the cause.

4–6 minutes
Aisle between rows of server racks in a data center

504 Gateway Timeout means the go-between (NginxNginx A fast, popular program that sends web pages to visitors and can pass requests on to apps behind it. More about Nginx →, Apache, a load balancer or a CDN) waited for the program behind it and gave up before it answered. Unlike a 502, the backend is usually running. It is too slow, stuck waiting on something, or overloaded.

If you are visiting the site

Wait a minute and reload. If the page does something heavy, such as an export, a report or a large search, it may keep timing out until the site owner fixes it. Other pages on the same site often still work.

Which timeout ran out?

Every layer between the visitor and your code has its own clock. The 504 comes from the first one to give up.

LayerDefault limitSetting
Cloudflare (free and Pro plans)100 secondsFixed; Enterprise plans can raise it
AWS Application Load Balancer60 secondsIdle timeout on the load balancer
Nginx to PHP-FPM60 secondsfastcgi_read_timeout
Nginx to an app60 secondsproxy_read_timeout
Apache to PHP-FPM60 seconds on most setupsProxyTimeout or Timeout
PHP-FPMoff unless setrequest_terminate_timeout
PHP30 secondsmax_execution_time

A request that fails at almost exactly 60 seconds is hitting a 60-second limit. One that fails at 100 seconds behind Cloudflare is Cloudflare’s limit. Time it:

curl -o /dev/null -s -w "%{http_code} after %{time_total}s\n" https://example.com/slow-page/

If you run the site

1. Find which requests time out

sudo grep "upstream timed out" /var/log/nginx/error.log | tail -n 20

A typical line:

upstream timed out (110: Connection timed out) while reading response header from upstream, client: 203.0.113.7, server: example.com, request: "GET /report/ HTTP/2.0", upstream: "fastcgi://unix:/run/php/php8.3-fpm.sock"

The request part names the page. Is it every page, or one? One slow page points to slow code or a slow query. Every page points to an overloaded server or a shared dependency, such as the database, being down or swamped.

2. Check whether the server is overloaded

uptime
top -o %CPU
free -h

uptime shows load averages for the last 1, 5 and 15 minutes. Compare them with your CPU count (nproc). A load well above that number means requests are queueing for CPU. If memory is nearly full and swap is busy, everything slows down at once.

If the server is simply too small for its traffic, the lasting fixes are page caching, a CDN for static files, or more resources. See how much RAM a website needs.

3. Look for slow database queries

On MySQL or MariaDB, list what is running right now:

sudo mysql -e "SHOW FULL PROCESSLIST;"

Look at the Time column. Queries running for many seconds, or many queries stuck in Locked or Waiting for table metadata lock, are the usual culprit. To catch slow queries over time, turn on the slow query log:

sudo mysql -e "SET GLOBAL slow_query_log = 1; SET GLOBAL long_query_time = 2;"
sudo tail -f /var/lib/mysql/$(hostname)-slow.log

Common causes are a missing index on a large table, a plugin running a report on every page load, or a WordPress wp_options table bloated with autoloaded data.

4. Find where the PHP time goes

PHP-FPMPHP-FPM The part of the server that runs PHP code, the language WordPress and many sites are written in, and hands the finished page to the web server. More about PHP-FPM → can log the stack trace of any request that runs too long. Add this to the pool file and restart PHP-FPM:

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

Each entry shows the function the script was stuck in, which usually names the plugin, theme or library at fault.

5. Check calls to other services

Pages that call another service, such as a payment API, a licence server, a feed or a remote font, wait for it. If that service is slow or unreachable, your page waits too, and a firewallFirewall A gatekeeper that decides which connections are allowed into a server and blocks the rest. More about Firewall → that silently drops outgoing traffic makes it wait until the timeout. Test from the server:

curl -o /dev/null -s -w "%{time_total}s\n" https://api.example-service.com/

In your code, give every outgoing call a short timeout of its own, so one slow service cannot hold a page for a minute.

6. Raise timeouts only for genuinely long work

If a task really needs longer, such as a large import or a report, raise the limits together, for that location only where you can:

# Nginx, in the location block for the slow URL
fastcgi_read_timeout 300;

# PHP-FPM pool
request_terminate_timeout = 300

# php.ini or the pool (php_admin_value[max_execution_time])
max_execution_time = 300

Test and reload Nginx, then restart PHP-FPM:

sudo nginx -t && sudo systemctl reload nginx
sudo systemctl restart php8.3-fpm

For apps behind proxy_pass, the Nginx setting is proxy_read_timeout. Behind Cloudflare, anything longer than 100 seconds still fails, so move long jobs out of the web request: run them in the background with a queue or a cron jobCron job A task the server runs by itself on a timetable, like an alarm clock for jobs. For example, making a backup every night at 3 a.m. More about Cron job →, and show the user a “we’ll email you when it’s ready” message instead.

Do not raise timeouts across the whole site to hide a slow page. Visitors still wait, and each stuck request holds a PHP worker that other visitors need.

On shared hosting

You usually cannot change server timeouts. A 504 there means the page is too slow for the host’s limits, or the account is hitting its CPU cap. Check the control panelControl panel A website you log into to manage your hosting with buttons and forms, instead of typing commands. cPanel and Plesk are the common ones. More about Control panel →’s resource usage page, disable heavy plugins (backup, statistics and broken-link checkers are common), and add a caching plugin.

Confirm it is fixed

Time the page again with curl as above. A healthy dynamic page answers in well under a second; anything over a few seconds will time out again under load. Then watch the log while you browse:

sudo tail -f /var/log/nginx/error.log | grep --line-buffered "timed out"

502, 503 or 504?

CodeWhat happened
502 Bad GatewayThe backend gave no answer or a broken one
503 Service UnavailableThe server is up but refusing work: maintenance or overload
504 Gateway TimeoutThe backend was too slow to answer

Something out of date? Software changes. If a step no longer works, tell us and we will check it and update the page.