Introduction

PHP's max_execution_time directive limits how long a script can run before being terminated. The default is 30 seconds, which is fine for web requests but insufficient for cron jobs processing thousands of records. When the time limit is exceeded, PHP throws a fatal error and the script dies mid-processing, potentially leaving data in an inconsistent state.

Symptoms

  • Fatal error: Maximum execution time of 30 seconds exceeded
  • Cron job completes partially - some records processed, others not
  • Script dies at different points each run depending on processing speed
  • Works for small data sets but fails for full imports
  • set_time_limit() calls not working as expected

``` PHP Fatal error: Maximum execution time of 30 seconds exceeded in /var/www/html/cron/process-queue.php on line 87

# Line 87 is just a regular function call - the timer expires during execution # No try/catch can catch this - it is a fatal error ```

Common Causes

  • max_execution_time set globally in php.ini applies to CLI
  • set_time_limit(0) not called or called too late
  • PHP-FPM pool config overrides php.ini setting
  • Processing large datasets without batching
  • Network calls to slow external APIs within the cron job

Step-by-Step Fix

  1. 1.Set unlimited execution time for CLI scripts:
  2. 2.```php
  3. 3.#!/usr/bin/env php
  4. 4.<?php

// At the VERY top of the script, before any processing set_time_limit(0); // No time limit ini_set('memory_limit', '512M');

// Or check current limits echo "Time limit: " . ini_get('max_execution_time') . "\n"; echo "Memory limit: " . ini_get('memory_limit') . "\n"; ```

  1. 1.Configure CLI-specific php.ini:
  2. 2.```bash
  3. 3.# Find the CLI php.ini
  4. 4.php --ini
  5. 5.# Loaded Configuration File: /etc/php/8.2/cli/php.ini

# Edit CLI-specific settings (does not affect web) # /etc/php/8.2/cli/php.ini max_execution_time = 0 memory_limit = 512M

# Verify php -i | grep max_execution_time # max_execution_time => 0 => 0 ```

  1. 1.Process in resumable batches:
  2. 2.```php
  3. 3.<?php
  4. 4.set_time_limit(0);

function processQueueInBatches(int $batchSize = 100): void { $offset = 0; $startTime = time(); $maxRuntime = 25 * 60; // 25 minutes (leave 5 min buffer)

while (true) { $items = getPendingItems($offset, $batchSize); if (empty($items)) { break; // All done }

foreach ($items as $item) { processItem($item); markProcessed($item->id); }

$offset += $batchSize;

// Check if approaching time limit if ((time() - $startTime) > $maxRuntime) { echo "Time limit approaching. Processed $offset items. " . "Will resume on next cron run.\n"; break; // Exit cleanly, next cron run will continue }

// Brief pause to reduce server load usleep(100000); // 100ms } }

// Cron runs every 5 minutes - processes as much as possible each run ```

  1. 1.Handle PHP-FPM vs CLI execution time differences:
  2. 2.```php
  3. 3.// Detect SAPI and set appropriate limits
  4. 4.if (php_sapi_name() === 'cli') {
  5. 5.set_time_limit(0); // CLI: no limit
  6. 6.} else {
  7. 7.// Web: keep default 30s or set specifically
  8. 8.set_time_limit(60);
  9. 9.}

// Note: set_time_limit() has NO EFFECT when PHP runs in safe mode // or when max_execution_time is set to 0 in php.ini ```

  1. 1.Use proper cron scheduling with lock files:
  2. 2.```php
  3. 3.<?php
  4. 4.// cron/process-queue.php
  5. 5.$lockFile = '/tmp/process-queue.lock';

if (file_exists($lockFile)) { $lockAge = time() - filemtime($lockFile); if ($lockAge < 3600) { // Less than 1 hour old echo "Already running (lock file is {$lockAge}s old). Skipping.\n"; exit(0); } echo "Stale lock file removed.\n"; }

file_put_contents($lockFile, (string)getmypid()); set_time_limit(0);

try { processQueueInBatches(); } finally { unlink($lockFile); // Always clean up } ```

Prevention

  • Always call set_time_limit(0) at the top of cron scripts
  • Use CLI-specific php.ini settings (/etc/php/*/cli/php.ini)
  • Implement resumable batch processing for large datasets
  • Use lock files to prevent overlapping cron executions
  • Log start/end times to track processing duration trends
  • Set max_execution_time in pool config for PHP-FPM separately from CLI
  • Consider using a job queue (RabbitMQ, Redis, Beanstalkd) instead of cron for reliable processing