The Error
Your script hits this wall and dies:
Fatal error: Maximum execution time of 30 seconds exceeded in /var/www/html/process.php on line 142
PHP has a built-in execution time limit (max_execution_time) โ default 30 seconds. When a script runs longer, PHP kills it. No cleanup, no graceful exit. Just dead.
You'll typically see this during bulk CSV imports, calls to sluggish third-party APIs, PDF/report generation, image resizing pipelines, or any loop iterating over thousands of database rows.
Root Cause
The script is doing too much inside a single HTTP request. Either the logic itself is slow, or it's blocked waiting on something external โ a 10-second DB query, an unresponsive API, a 200 MB file loaded entirely into memory.
One important distinction: PHP CLI (php script.php) defaults to max_execution_time = 0 โ unlimited. This error almost exclusively happens in the web context (Apache or Nginx serving an HTTP request).
Fix 1: Increase the Limit in php.ini
The most permanent fix. It applies globally to your server. First, find your active php.ini:
php --ini | grep 'Loaded Configuration'
# or
php -r "echo php_ini_loaded_file();"
Edit it and raise the limit:
; php.ini
max_execution_time = 120
Then restart your web server:
# Apache
sudo systemctl restart apache2
# Nginx + PHP-FPM
sudo systemctl restart php8.2-fpm
sudo systemctl restart nginx
Avoid setting it to 0 on web-facing scripts. Unlimited execution time means a single slow request can hold a PHP-FPM worker hostage indefinitely โ that's a DoS waiting to happen.
Fix 2: Set It in the Script (Quick Override)
Can't touch php.ini? Shared hosting, someone else's server, emergency hotfix at 2 AM โ set_time_limit() works without any config access:
<?php
// Put this at the very top of your script
set_time_limit(120); // 120 seconds
// Or reset the clock on each loop iteration
foreach ($large_dataset as $item) {
set_time_limit(30); // Reset to 30s per item
process($item);
}
set_time_limit(0) disables the limit entirely for that script run. Fine for a CLI batch job processing 50,000 records. Not fine for a random public endpoint.
Note: set_time_limit() does nothing if PHP runs in safe mode โ rare in modern setups, but worth knowing if you're on legacy infrastructure.
Fix 3: .htaccess Override (Apache Only)
On shared Apache hosting with AllowOverride enabled, you can set this per-directory without touching anything global:
# .htaccess
php_value max_execution_time 120
No restart needed. Apache reads .htaccess on every request.
Fix 4: php.ini in Project Root (cPanel / Some Hosts)
Many shared hosts let you drop a php.ini or .user.ini directly in your project folder:
# public_html/php.ini OR public_html/.user.ini
max_execution_time = 120
Check your host's docs to see which file they honor. .user.ini is the PHP-FPM equivalent and is more common on hosts running modern stacks.
Fix 5: PHP-FPM Pool Config
Running PHP-FPM directly? Override the limit per pool instead of touching the global php.ini:
# /etc/php/8.2/fpm/pool.d/www.conf
php_admin_value[max_execution_time] = 120
Restart the FPM service to apply it:
sudo systemctl restart php8.2-fpm
Fix 6: For Laravel / Artisan Commands
For queued jobs or long Artisan commands, set the limit at the top of the handle() method:
<?php
public function handle()
{
set_time_limit(0); // CLI ignores this anyway, but explicit beats implicit
ini_set('max_execution_time', 0);
// your long-running logic
}
Also check your queue worker timeout โ PHP's limit and Laravel's worker timeout are two separate clocks:
# Run worker with explicit timeout (seconds)
php artisan queue:work --timeout=120
Fix 7: Diagnose and Fix the Slow Code
Raising the limit buys time. It doesn't fix anything. If your script genuinely needs 5 minutes, that's a red flag worth investigating.
Start by measuring where the time actually goes:
<?php
$start = microtime(true);
// ... your code block ...
$elapsed = microtime(true) - $start;
error_log("Block A took: {$elapsed}s");
The usual suspects:
- Slow DB query โ run
EXPLAINon it. A missing index on a 500,000-row table can turn a 10ms query into a 45-second table scan. - External API calls โ always set an explicit timeout:
curl_setopt($ch, CURLOPT_TIMEOUT, 10). Without it, PHP will wait indefinitely for a server that never responds. - Large file processing โ stream it instead of loading everything into memory at once. Reading a 1 GB file with
file_get_contents()will exhaust both memory and time. - Nested loops on big arrays โ O(nยฒ) logic hits a wall fast. Use chunking or rethink the algorithm.
For bulk operations, the real fix is to get them off the HTTP request entirely:
<?php
// Don't process 10,000 rows synchronously in a web request.
// Dispatch a job and return immediately.
ImportJob::dispatch($file_path);
return response()->json(['status' => 'processing']);
Verify the Fix
Don't trust what you set โ confirm what's actually active at runtime:
<?php
echo ini_get('max_execution_time');
Or use phpinfo() (never leave this on production) and search for max_execution_time. You'll see both the master value and the local value. If they differ, something is overriding your setting โ check .htaccess, pool config, or .user.ini in order.
Re-run the failing script and confirm it completes without the fatal error.
Quick Reference
- php.ini โ
max_execution_time = 120(global, requires restart) - Script top โ
set_time_limit(120);(per-script, immediate) - .htaccess (Apache) โ
php_value max_execution_time 120(no restart needed) - .user.ini โ
max_execution_time = 120(PHP-FPM per-directory) - CLI โ defaults to
0(unlimited), this error won't happen there
Prevention
The pattern that causes this error is predictable: synchronous, blocking work inside an HTTP request. Break that pattern early.
Add execution time logging to any process that touches external systems. Set up slow-request monitoring โ Apache's %D format variable logs request duration in microseconds; New Relic and Datadog can alert you when a route consistently hits 10+ seconds.
As a rule of thumb: a web request should return in under 3 seconds. If you need 30 seconds or more, the work belongs in a background job โ a queue worker, a cron task, or an async process. The HTTP response should just say "I've queued it" and return immediately.

