Maximum execution time exceeded in WordPress

A PHP script ran longer than the server allowed. Fix it by raising max_execution_time, calling set_time_limit(), or breaking the task into smaller batches.

You trigger an import, a plugin update, or a media regenerate, and WordPress stops with a block of red text that reads roughly:

Fatal error: Maximum execution time of 30 seconds exceeded in
/home/example/public_html/wp-includes/class-wpdb.php on line 2024

The path and line number change, but the shape is always the same. A PHP script ran longer than the server allowed, and PHP killed it.

What the error actually means

PHP enforces a per-request wall clock called max_execution_time. When that budget runs out, the PHP parser terminates the script, writes the fatal to the error log, and WordPress stops mid-request. The default in every PHP web SAPI is 30 seconds. The default in php-cli is 0, which is why the same import run over WP-CLI often succeeds where the wp-admin version fails.

One important detail: on Linux and macOS, time spent inside sleep(), database queries, file operations, or HTTP calls to external APIs does not count toward this budget. Only time inside PHP itself counts. On Windows the timer is real-time and counts everything. That asymmetry is documented in the PHP manual entry for set_time_limit() and explains why a script that hits the timeout on a Linux host is almost always doing real PHP work rather than waiting on a slow API.

Common causes, most likely first

  1. An importer or exporter processing too much at once. WP All Import, WP Import Export Lite, WooCommerce CSV exports, and "regenerate thumbnails" routines are the usual suspects. They loop over thousands of rows or media items inside a single HTTP request.
  2. A backup or migration plugin building a full archive in the browser. UpdraftPlus, All-in-One WP Migration, Duplicator, and similar tools can run out of wall time on larger sites when you trigger the backup from the dashboard.
  3. A slow or hung external API call inside a hook. A shipping calculator, a currency converter, or a third-party analytics call that stalls for 30 seconds inside an init or template_redirect hook. This is the Windows-vs-Linux caveat above: on Linux this only trips the timer if the call is actually doing PHP work in between.
  4. A shared host with the default 30 second limit and no way to override it. Some budget hosts disable set_time_limit() and reject .htaccess PHP overrides, so every long-running task dies at 30 seconds no matter what you do in WordPress.
  5. A stuck process caused by an infinite loop or recursion bug in a plugin, theme, or custom function. Rare, but it looks identical in the error log.

Diagnose which cause applies

Before you raise the limit, confirm what is actually running long. Raising the limit on a genuine infinite loop just extends how long your site hangs before failing.

Check the full error line. The file path in the error message tells you which component was on the stack when the timer fired. A path under wp-content/plugins/woocommerce-product-importer/ points at the importer. A path inside wp-includes/class-wpdb.php means PHP was inside a database call when it ran out of budget, which on Linux usually means a slow query in a much larger loop rather than a slow database per se.

Reproduce the action outside wp-admin if you can. Run the same import over WP-CLI:

wp import products.csv --user=admin

Expected behavior: WP-CLI uses the php-cli SAPI where max_execution_time defaults to 0. If the same task completes over CLI, you have confirmed that the work itself is fine and the problem is purely the wall clock on the web SAPI.

Check your current limit. Drop a tiny diagnostic file in your WordPress root:

<?php
// phpinfo-check.php, delete after use
echo 'max_execution_time: ' . ini_get('max_execution_time') . PHP_EOL;
echo 'set_time_limit disabled: ' . (in_array('set_time_limit', explode(',', ini_get('disable_functions'))) ? 'yes' : 'no') . PHP_EOL;

Visit https://yoursite.nl/phpinfo-check.php and read the two values. Expected output on a standard host: max_execution_time: 30 and set_time_limit disabled: no. If set_time_limit is disabled, methods 2 and 3 below will not work for you and you need method 1 or 4. Delete the file after you have read it.

Fix 1: raise the limit in your host control panel

This is the only fix that works on every setup, because it changes the underlying php.ini value that PHP itself reads at startup.

  1. Log into cPanel and open MultiPHP INI Editor (Plesk: PHP Settings; DirectAdmin: PHP Selector; hPanel: PHP Configuration).
  2. Select the domain whose WordPress site is hitting the error.
  3. Switch to Editor Mode if your panel offers it, so you can see max_execution_time as a direct value.
  4. Change it from 30 to 120. Do not jump straight to 600 or higher. A high ceiling hides real problems rather than fixing them.
  5. Save. Most panels apply the change to the next request with no restart.

Verify: re-open the diagnostic file from the previous section. You will know it worked when max_execution_time now reads 120. Then retry the original action (the import, update, or export). You will know the fix held when the task finishes without a fatal.

If the panel does not expose max_execution_time, or the value is locked, go to fix 4.

Fix 2: call set_time_limit() from wp-config.php

If you cannot change the panel value but your host has not disabled set_time_limit(), you can raise the limit in code. This has to go in wp-config.php so it runs before WordPress loads any plugin.

  1. Download wp-config.php over SFTP. Keep the original as wp-config.php.backup.

  2. Open it in a text editor (see the wp-config.php settings reference for the full constant context) and find the line that reads /* That's all, stop editing! Happy publishing. */.

  3. Directly above that line, add:

    // Give long-running imports up to 5 minutes.
    @set_time_limit( 300 );
  4. Save and upload the file back to the root of your WordPress install.

The @ suppresses a warning if the host has set_time_limit in disable_functions. Without it, the suppressed warning will itself crash your site.

One gotcha that catches people out: set_time_limit() resets the counter to zero from the point it is called, it does not add to the remaining budget. So calling it in wp-config.php gives the whole request a fresh 300 second budget starting from that line.

Verify: reload the diagnostic file and read max_execution_time. It will still show 30 in phpinfo() because you used set_time_limit() rather than ini_set(). That is expected. Retry the original action. You will know it worked when the task that previously died at 30 seconds now finishes.

If nothing changes, your host has disabled set_time_limit() or is running you behind a web server timeout that fires first. Go to fix 4.

Fix 3: raise the limit via .htaccess (Apache with mod_php only)

This only works on Apache with mod_php, and only if your host has not locked down php_value in .htaccess. It does not work on nginx, and it does not work on Apache with PHP-FPM. Do not combine this with fix 2.

  1. Download .htaccess from your WordPress root. Keep the original as .htaccess.backup.

  2. Open it and add this line at the very top of the file, before the # BEGIN WordPress block:

    php_value max_execution_time 120
  3. Save and upload the file back.

Verify: visit your site's homepage. You will know the directive was accepted when the page loads normally. You will know the host does not support php_value in .htaccess when you instead get a 500 Internal Server Error. If that happens, delete the line you added, restore .htaccess.backup, and use fix 1 or fix 4 instead.

Then re-check the diagnostic file. max_execution_time should now read 120. Retry the original action.

Fix 4: ask your host to raise the limit

If none of the above sticks, the host is enforcing a hard cap. That is common on managed shared hosting and on budget plans that run PHP-FPM with locked pool configs.

Open a support ticket and give them three concrete things:

  • The exact error, copied from the log: Fatal error: Maximum execution time of 30 seconds exceeded in /path/to/file.php on line N.
  • What you were doing when it fired ("running WP All Import on a 40,000 row CSV", "triggering a full UpdraftPlus backup", "regenerating thumbnails on 8,000 images").
  • A clear ask: "Please raise max_execution_time to 300 for my account, or confirm that my plan cannot exceed 30 and explain which plan allows a higher value."

A good host answers in one message. A bad host bounces you around. Either way, you have now got written confirmation of the ceiling, which is useful context for deciding whether to move the task off the web request entirely (see the prevention section below).

When to escalate

If you have tried fixes 1 and 4 and the task still times out, collect this before asking anyone for deeper help. It is the same pack a developer or host support will ask for on a second message:

  • The full error line from the PHP error log, including the file path and line number.
  • The output of the diagnostic file showing max_execution_time and whether set_time_limit is disabled.
  • Your WordPress version (Dashboard: Updates), PHP version (Tools: Site Health: Info: Server), and hosting plan name.
  • A list of the plugins active when the error fires, especially the one that triggered the long-running action.
  • Whether the same action succeeds over WP-CLI. That single data point tells anyone reading whether the problem is the work or the wall clock.
  • Any entries from wp-content/debug.log in the minute before the fatal.

If the task is a recurring one (a nightly import, a scheduled WooCommerce report, a backup), escalate before it next runs. These tasks almost always belong off the web SAPI entirely, and a host worth paying for will help you move them to a cron-driven WP-CLI command or a proper background queue.

Prevent the error from coming back

Raising the limit is a valid fix for a one-time task. It is not a strategy for a recurring one. Three habits keep this error rare:

  • Batch any import or export. WP All Import supports records_per_iteration. WooCommerce's built-in importer streams rows. Custom scripts should process in chunks of 100 to 500 rows, not 40,000 at once. A batched import also recovers cleanly from a timeout because each chunk is independent.
  • Move long-running tasks off the web request. Backups, thumbnail regenerations, and large imports belong behind WP-CLI or a scheduled cron job, not behind a dashboard button. The max_execution_time on php-cli is 0, so the wall clock disappears entirely. If you do not own the server, a managed host can configure this for you.
  • Give slow external API calls a timeout. A stuck external call will not usually trigger this specific error on Linux (see the earlier caveat), but it will hold a PHP worker open until the request dies. If you are seeing this error alongside related symptoms like slow front-end pages, read the article on PHP workers exhausted in WordPress to understand what happens to the rest of your site while a worker is stuck.

If raising max_execution_time fixes the symptom but you also see "allowed memory size" errors in the same workflow, you are probably looking at two faces of the same runaway task. The related article on allowed memory size exhausted covers the memory side of the same story. And if the long-running script crashes halfway and leaves the site showing a fatal to visitors, the article on the critical error on this website explains how WordPress surfaces that to the front-end and how to get the site back online.

Done chasing slowdowns?

Performance issues tend to come back after quick fixes. WordPress maintenance keeps updates, caching and limits consistent.

See WordPress maintenance

Search this site

Start typing to search, or browse the knowledge base and blog.