Error TIME · php
Maximum execution time exceeded
A PHP script ran longer than allowed and was stopped — almost always waiting on a slow query or a slow external service.
Also seen as: Fatal error: Maximum execution time of 30 seconds exceeded · PHP timeout · script timeout
What it actually means
PHP caps how long a single script may run. Exceed it and PHP kills the script mid-execution:
Fatal error: Maximum execution time of 30 seconds exceeded
in /home/u1234/htdocs/wp-content/plugins/importer/run.php on line 84
Unlike memory errors, the line named here is usually accurate — it’s where the script was when the clock ran out. That’s the slow operation.
One important detail: the timer counts PHP execution time, not wall-clock time. Time spent waiting on a database query or an HTTP request often doesn’t count on some configurations, which is why a script can appear to hang for far longer than the limit before dying.
If a proxy sits in front, you may see 504 Gateway Timeout instead, because the proxy gave up before PHP did.
Why it happens
A database query with no index
The most common cause by a wide margin. A query scanning a large table takes seconds. Add a few of those to one page load and the limit arrives.
The tell is that nothing in the code changed — the table simply grew past the point where the missing index mattered.
An external service that went slow
Your page calls a payment provider, a feed, a social widget. That service slows down and your page waits with it. Without an explicit timeout, PHP waits up to default_socket_timeout — 60 seconds by default — for each call.
Bulk work in a web request
Imports, exports, sending hundreds of emails, resizing a media library. These take minutes by nature and don’t belong in a request that a person is waiting on.
A loop that never ends
A while whose condition is never met, or recursion without a base case. If this appeared immediately after a code change, start here.
How to fix it
1. Time the operations to find the slow one
Don’t guess. Measure:
$start = microtime(true);
$result = $db->query($sql)->fetchAll();
error_log(sprintf('query took %.2fs', microtime(true) - $start));
On WordPress, define('SAVEQUERIES', true); plus Query Monitor lists every query with its duration. Anything over 200ms is worth attention; anything over a second is your problem.
2. Index the column you're filtering on
Once you know the slow query, check what it’s doing:
EXPLAIN SELECT * FROM orders WHERE customer_email = '[email protected]';
If the type column says ALL, it’s reading every row. Add an index:
CREATE INDEX idx_customer_email ON orders (customer_email);
This routinely turns a 30-second query into a few milliseconds. It is the single highest-value fix in this whole page.
3. 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,
CURLOPT_CONNECTTIMEOUT => 2,
]);
$body = curl_exec($ch);
if ($body === false) {
$body = get_cached_copy(); // degrade rather than hang
}
Then cache the response so you aren’t making the call on every page load.
4. Break long jobs into chunks
If the work genuinely takes minutes, make each request do a slice of it:
$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));
exit;
}
Slower overall, but it never times out and you can watch it progress.
5. Raise the limit only for the script that needs it
set_time_limit(300); // this script only, not site-wide
Use it for a genuine one-off — a migration, a bulk import you run manually. Do not raise it globally: a site-wide limit of 300 seconds means one stuck script holds a process slot for five minutes, and ten of those exhaust your plan.
Many shared hosts cap this above PHP, so the call may have no effect.
On CWP specifically
CWP enforces a maximum execution time per request above PHP, so set_time_limit() cannot raise it past the plan’s ceiling.
Because free accounts have no cron, chunked processing driven by redirects — or an external scheduler hitting a worker URL — is the practical way to run long jobs. The cron expression builder covers the scheduling side.
When this means you've outgrown free hosting
Work that legitimately needs minutes of CPU per request doesn’t fit the request/response model at all, on any plan.
The correct fix is a background worker, which needs a queue service or a VPS. A bigger shared plan buys you a slightly longer timeout and the same architectural problem.
Related errors
-
504
504 Gateway Timeout
The proxy in front gave up waiting for the backend to answer — your code is running, it is just taking too long. -
508
508 Resource Limit Is Reached
Your account hit its CPU, memory, or concurrent-process ceiling, so the server refused the request instead of letting your site destabilise the machine. -
MEM
Allowed memory size exhausted
A PHP script asked for more memory than your account allows, so it was killed part-way through. -
WSOD
White screen of death
PHP hit a fatal error and error display is turned off, so the server sent an empty page instead of a message. -
1040
MySQL: Too many connections
Your account is already holding as many database connections as it's allowed, so new requests are refused until one frees up. -
429
429 Too Many Requests
You sent more requests than the server allows in a given period, so it's refusing you until the window resets. -
413
413 Payload Too Large
You tried to upload something bigger than the server accepts, and it rejected the request before reading it all.
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.