Error 504 · server

504 Gateway Timeout

In one line

The proxy in front gave up waiting for the backend to answer — your code is running, it is just taking too long.

Ben Johnson · last verified 2026-08-04

Also seen as: Gateway Timeout · 504 error · request timed out

What it actually means

A 504 is a 502’s patient cousin. Same two-server setup: a proxy in front, an application behind. The difference is that with 502 the backend failed, and with 504 the backend simply never finished in time.

That is a useful distinction. Your code is probably not broken. It is slow, or it is stuck waiting for something else that is slow — a database query, an external API, a large file operation.

The timeout is usually 30 to 60 seconds. Cloudflare’s is 100 seconds on free plans.

Diagram illustrating server errors on shared web hosting.

Why it happens

A database query with no index

The single most common cause. A query that ran fine over 1,000 rows takes 40 seconds over 500,000. Nothing changed in your code — the table just grew past the point where a missing index mattered.

An external API that stopped responding

Your page calls a third-party service — a payment provider, a weather feed, a social widget. That service goes slow. Without a timeout, your page waits for it, and your visitors wait too.

One slow third party can take your whole site down. This is worth designing against before it happens.

Work that should not be in a web request

Bulk imports, report generation, sending hundreds of emails, resizing large images. These belong in a background job, not in the seconds a visitor is prepared to wait.

A loop that does not terminate

A while that never satisfies its condition, or recursion without a base case. The request runs until something kills it.

How to fix it

1. Find the slow query first

This is the cause most of the time, so start here.

On WordPress, add define('SAVEQUERIES', true); to wp-config.php and install Query Monitor — it lists every query with its duration. Anything over ~200ms deserves attention.

Then check what the query is actually doing:

EXPLAIN SELECT * FROM orders WHERE customer_email = '[email protected]';

If the type column says ALL, it is scanning the entire table. Add an index:

CREATE INDEX idx_customer_email ON orders (customer_email);

A single index routinely turns a 40-second query into a 5-millisecond one.

2. Put a timeout on every outbound call

Never let a third party decide how long your page takes:

$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 3,   // total
    CURLOPT_CONNECTTIMEOUT => 2,   // connection only
]);
$response = curl_exec($ch);
if ($response === false) {
    $response = get_cached_fallback();   // degrade, do not hang
}

Three seconds is generous for an API call. Cache the result so you are not calling it on every page load.

3. Move long work into the background

If something genuinely takes minutes, it cannot live in a request. Write the job to a table, return immediately, and process it from a scheduled task:

$db->insert('jobs', ['type' => 'import', 'payload' => $csv, 'status' => 'pending']);
header('Location: /import/queued');

On free hosting with no real cron, trigger a worker URL from an external scheduler — see the cron expression builder for the scheduling side.

4. Process large jobs in chunks

If you cannot use a queue, break the work up so each request finishes quickly:

$batch = 100;
$offset = (int) ($_GET['offset'] ?? 0);
$rows = $db->fetchAll("SELECT * FROM import LIMIT ? OFFSET ?", [$batch, $offset]);
foreach ($rows as $row) { process($row); }
if (count($rows) === $batch) {
    header('Location: ?offset=' . ($offset + $batch));
}

Each request handles 100 rows and redirects. Slower overall, but it never times out.

On CWP specifically

Our platform

CWP enforces a maximum execution time per request. A script that exceeds it is stopped and you get a 504, with the script and line recorded in your control panel error log.

Because free accounts have no cron, background processing needs an external scheduler hitting a worker URL. Protect that URL with a secret token, or you have built an endpoint anyone can hammer.

When this means you've outgrown free hosting

Be honest with yourself

If your work genuinely needs minutes of CPU per request, shared hosting is the wrong shape for it regardless of plan — the request/response model does not fit.

The answer is a background worker, which needs either a queue service or a VPS where you can run one. A bigger shared plan will not fix it.

Still stuck?

Post the exact error and your account name on the community forum — staff and other users answer there, and the thread helps the next person who hits this.