PHP Maximum execution time exceeded — Script ran too long
Fatal error: Maximum execution time of N seconds exceeded
Verified against PHP 8.3 documentation, PHP set_time_limit() docs, MySQL slow query log documentation · Updated June 2026
> quick_fix
For legitimate long-running scripts (imports, exports, batch processing), call `set_time_limit(0)` at the start to disable the limit. For unexpectedly slow scripts, profile to find the bottleneck — usually a slow database query, an N+1 loop, or an unindexed data set.
<?php
// Option 1: disable time limit for batch scripts
set_time_limit(0);
// Option 2: extend limit to 5 minutes for heavy exports
set_time_limit(300);
// Option 3: increase via ini_set (must be before the slow code)
ini_set('max_execution_time', '300');
// Best practice: break large loops into chunks + process one chunk per request
function processChunk(array $items, int $offset, int $limit): int {
$chunk = array_slice($items, $offset, $limit);
foreach ($chunk as $item) {
processItem($item);
}
return count($chunk);
}What causes this error
PHP enforces a maximum execution time to prevent runaway scripts from consuming server resources indefinitely. When a script exceeds `max_execution_time` (default: 30 seconds), PHP raises a fatal E_ERROR and terminates the script. Common causes: unoptimized database queries in a loop (N+1 problem), processing a large file all at once, calling an external API that is slow or unresponsive, infinite or very deep loops, or missing database indexes on columns used in WHERE clauses.
How to fix it
- 01
step 1
Diagnose what is slow before extending the timeout
Adding `set_time_limit(0)` fixes the symptom but not the cause. Profile first: add `microtime(true)` timing around suspected slow sections, log slow database queries with `SET GLOBAL slow_query_log = 'ON'` in MySQL, or use Xdebug/Blackfire profiling.
- 02
step 2
Fix N+1 database queries
The most common cause: a loop that executes a query on each iteration (N+1). Replace: `foreach ($users as $user) { $orders = getOrders($user->id); }` with a single JOIN or a single IN query that fetches all orders at once.
- 03
step 3
Add database indexes
A query without an index on the WHERE column does a full table scan. On a table with 100k rows, `SELECT * FROM orders WHERE user_id = 5` without an index on user_id scans every row. Add the index: `ALTER TABLE orders ADD INDEX idx_user_id (user_id);`
- 04
step 4
Use set_time_limit(0) for legitimate long-running CLI scripts
Batch imports, data migrations, and report generation legitimately take minutes. For these, call `set_time_limit(0)` at script start. Note: set_time_limit() does not count time spent in system calls or waiting on external services on most UNIX systems, only CPU time.
- 05
step 5
Move long tasks to background queues
Web requests should complete in under 30 seconds. For tasks that genuinely take longer, dispatch them to a background queue (Laravel Queues, Symfony Messenger, or a simple database-backed job table) and return a 202 Accepted response immediately.
How to verify the fix
- Measure execution time before and after the fix with microtime()
- Run the script with the production dataset size — not just small test data
- For queue-based solutions, confirm the job processes successfully in the background
Why Maximum execution time exceeded happens at the runtime level
PHP's execution time limit is a safety mechanism built into the PHP engine. At each opcode execution, the engine checks whether the configured `max_execution_time` has elapsed since the script start. When it has, the engine immediately raises a fatal error, unwinding the call stack without giving the script a chance to clean up. On POSIX systems, the timer measures wall-clock time for I/O-bound scripts and CPU time for CPU-bound ones, which is why a script blocked waiting for a database response may not time out as quickly as expected.
Common debug mistakes for Maximum execution time exceeded
- Running a bulk import via a web request instead of a CLI script or background queue
- An N+1 loop that executes hundreds of database queries, each individually fast but collectively slow
- Reading an entire large file into memory with file_get_contents() before processing it line by line
- Calling an external API in a loop without timeouts, blocking on a slow or unresponsive endpoint
When Maximum execution time exceeded signals a deeper problem
Maximum execution time exceeded is often the visible symptom of a missing architectural boundary between web request handling (must be fast) and background processing (can be slow). Web frameworks encourage putting all business logic in controllers, which works until the logic takes 10+ seconds. The solution is not to increase the timeout but to recognize that long-running work belongs in a queue: dispatch a job, return 202, and let the worker process it asynchronously. This also makes the system more resilient — a slow external API blocks one worker, not the entire web server.
Editor's take
Maximum execution time exceeded means PHP's safety timer killed your script because it ran longer than the configured `max_execution_time` (default: 30 seconds in most configurations). This limit exists to prevent runaway scripts from consuming server resources indefinitely, and it only counts CPU time — time spent waiting for database queries, HTTP requests, or file I/O doesn't count toward the limit on most platforms (though Windows counts wall-clock time). The most common production cause isn't inefficient code — it's a missing database index causing a full table scan that returns thousands of rows, which then takes 30+ seconds of CPU time to process in PHP. The quick fix of `set_time_limit(0)` or `ini_set('max_execution_time', 300)` is almost always wrong because it masks the performance problem instead of fixing it. For legitimate long-running tasks (CSV imports, report generation, image processing), the correct architecture is a background job queue using Laravel Queues, Symfony Messenger, or a standalone worker process that runs outside the web request lifecycle. CLI scripts (`php artisan` commands) have no execution time limit by default, which is why artisan commands are the right tool for batch processing. If you're hitting this on a web request, the real question is: why is a web request doing this much work? The answer is usually that the operation should be async — accept the request, queue the work, and return a 202 Accepted with a polling endpoint.
By Bikram Nath · Curator · Updated June 2026
Frequently asked questions
Does set_time_limit(0) work inside a PHP-FPM web request?
It depends on the server configuration. php.ini may have `max_execution_time` overridden by the web server (nginx/Apache). Also, PHP-FPM has its own `request_terminate_timeout` setting that can kill requests regardless of set_time_limit(). For web requests, the correct fix is to make the code faster or move it to a background job.
Why does Maximum execution time not trigger on CLI scripts?
By default, php-cli sets max_execution_time to 0 (unlimited). The limit is primarily a web request safeguard. When running scripts via `php script.php` on the command line, you will not hit this error unless you explicitly set a non-zero limit.
Can I catch this fatal error?
No, not in PHP 7. A Maximum execution time error is a hard fatal (E_ERROR), not an exception. It cannot be caught with try-catch. In PHP 8, you can intercept some errors via set_error_handler() but execution still terminates. The solution is to prevent it, not catch it.