Action Scheduler and WP-Cron: checking for stuck background tasks

WordPress 6 min read

An online shop’s database can double in size over the course of a few months, even though ‘nothing has changed’ and the front end of the site is fast. In such cases, the Action Scheduler tables are often to blame: hundreds of thousands of failed or pending tasks from a plugin that was removed long ago, which the system keeps trying to run over and over again.

WordPress has two background task systems running side by side, and if something goes wrong, it’s worth checking both:

  • WP-Cron: WordPress’s built-in scheduler. It does not synchronise with the server’s clock, but rather with visitor activity: every time a page is loaded, it checks whether there are any tasks due.
  • Action Scheduler: a task queue included with WooCommerce, which is also used by many other plugins (such as Yoast SEO, MailPoet, and subscription and email-sending plugins). Its tasks are stored in the actionscheduler_actions table, its log is stored in the actionscheduler_logs table, and it is run by WP-Cron by default.

We ran the following commands on a test website installed with WooCommerce, using the cPanel hosting Terminal; the basics of WP-CLI are described in the article on using WP-CLI.

Action Scheduler status

You can also view this on the WordPress admin interface without using SSH: on the WooCommerce › Status page, under the Scheduled Actions tab, you can filter by status (pending, failed, completed) and by task name (hook). It’s quicker from the command line:

cd ~/public_html
wp action-scheduler status
Data store: ActionScheduler_HybridStore
Runner: ActionScheduler_QueueRunner
Version: 4.1.0
status    count  oldest                      newest
complete  1      2026-10-27 19:56:51 +0000   2026-10-27 19:56:51 +0000
pending   2      2026-10-27 19:57:51 +0000   2026-10-28 03:00:00 +0000

On a healthy website, the number of pending tasks is small, and even the oldest has only been waiting for a few minutes; the number of failed tasks is low, and their causes are known. If pending tasks have been waiting for hours, the process has stalled; if there are several thousand failed tasks, a plugin is repeatedly failing.

How many instances of each task are pending or have failed?

wp action-scheduler action list --status=pending --fields=id,hook,group,scheduled_date --per_page=20
wp action-scheduler action list --status=failed --format=count
wp action-scheduler action logs 1234

The last command displays a task log, including the cause of the error. For larger datasets, aggregating by task name is faster in SQL. The wp db query command does not work on aWh’s cPanel servers, so use the mysql command (the configuration of ~/.my.cnf, which contains the password, is described in the WP-CLI article), or run the query in phpMyAdmin using your own table prefix:

P=$(wp db prefix)
mysql "$(wp config get DB_NAME)" -e "SELECT hook, status, COUNT(*) AS db FROM ${P}actionscheduler_actions GROUP BY hook, status ORDER BY db DESC LIMIT 20"

WP-Cron status

wp cron event list --fields=hook,next_run_relative,recurrence
wp cron event list --due-now

If there are many tasks due now (--due-now) but they are not running, WP-Cron is not starting correctly: for example, because there are too few visitors, or because DISABLE_WP_CRON is enabled but there is no server-side scheduling in its place.

Orphaned WP-Cron tasks (which belong to a removed plugin and whose hooks are no longer being listened to):

wp eval 'foreach ( _get_cron_array() as $ts => $events ) { foreach ( $events as $hook => $x ) { if ( ! has_action( $hook ) ) echo "gazdátlan: $hook\n"; } }'

You can delete such a task as follows: wp cron event delete <hook>.

Cleaning

Before doing so, please perform a backup of the database (using mysqldump; see the WP-CLI article), as deletion cannot be undone.

1. Deleting orphaned tasks

Find any pending or failed tasks that no longer have an extension associated with them:

wp eval 'global $wpdb; foreach ( $wpdb->get_col( "SELECT DISTINCT hook FROM {$wpdb->prefix}actionscheduler_actions WHERE status IN (\"pending\",\"failed\")" ) as $h ) { if ( ! has_action( $h ) ) echo $h, "\n"; }' > ~/gazdatlan-feladatok.txt
cat ~/gazdatlan-feladatok.txt

Go through the list; if a name looks familiar and you still need the extension, do not delete it. You can cancel pending tasks by their name:

while IFS= read -r hook; do wp action-scheduler action cancel "$hook" --all; done < ~/gazdatlan-feladatok.txt

canceled tasks and old failed tasks are cleared by the next step.

2. Deleting old tasks and logs

By default, Action Scheduler deletes completed and cancelled tasks after approximately one month. It does not delete failed tasks, and if the clean-up process does not run for any reason, everything is retained. Manually, in small batches:

wp action-scheduler clean --status=complete,failed,canceled --before='14 days ago' --batch-size=500 --pause=1

Thanks to the pause between runs (--pause), the clean-up does not overload the database, and the website remains fast throughout. You can shorten the retention period using a ‘must-use’ plugin (wp-content/mu-plugins/action-scheduler-megorzes.php):

<?php
/**
 * Plugin Name: Action Scheduler: 14 napos megőrzés
 */
add_filter( 'action_scheduler_retention_period', function () {
    return 14 * DAY_IN_SECONDS;
} );

3. Compressing the table

Deleting data frees up rows, but the table remains the same size on the disk. After a major clean-up, run this in phpMyAdmin (selecting the tables, then ‘Optimise Table’), or:

mysql "$(wp config get DB_NAME)" -e "OPTIMIZE TABLE ${P}actionscheduler_actions, ${P}actionscheduler_logs"

Ensuring reliable operation

WP-Cront is triggered by visitor traffic, so on a low-traffic online shop overnight, tasks may be delayed by hours, whilst at peak times, a resource-intensive task can slow down a visitor’s page load. Let the server handle the execution:

  • If your website is managed via WP Toolkit, enable the** ‘Fetch wp-cron.php’** option in the ‘Performance’ section of your website’s card.
  • Manually: disable WP-Cron in the wp-config.php file (define( 'DISABLE_WP_CRON', true );) and set up a cron job in cPanel instead. The steps are described in the article on replacing WP-Cron.

For high-traffic online shops, you can also run the Action Scheduler queue as a separate cron job, in limited batches, so that it does not tie up all PHP processes. On the cPanel cron jobs interface (Advanced group), for example, every five minutes:

/usr/local/bin/wp --path=/home/felhasznalonev/public_html action-scheduler run --batch-size=25 --batches=4 > /dev/null 2>&1

cPanel Cron jobs: adding a new job

A background task that runs for more than a minute and gets stuck at the same point every time is performing an infinite loop: this must be fixed by the plugin’s developer. A large number of orders arriving suddenly (for example, during a promotion) can also schedule several thousand tasks at once; if the website slows down at such times, CloudLinux’s resource usage will show which limit the account is hitting.

Regular checks

Check the output of the wp action-scheduler status command monthly. If the number of pending tasks consistently runs into the thousands, or if failed tasks are piling up, this is a sign of a plugin error, and it is better to spot this within a few weeks than a year later, when the database has grown to gigabytes in size. The other tables in the database are covered in the article on cleaning the wp_options and wp_postmeta tables.

Back: WordPress articles (43)