← Back to blog

How to test a WordPress cron job before it fails quietly

The SandyWP team 8 min read

To test a WordPress cron job properly, check three separate things: the event is scheduled, WP-Cron can start, and the callback creates the result you expect. A green scheduled-event list proves only the first one.

This distinction matters because WP-Cron is not a system timer. WordPress checks its queue during a page load, so a quiet staging site can make a healthy job look broken, while a broken callback can make a working scheduler look guilty.

Why “cron is broken” is not a diagnosis

There are at least three failure points in a scheduled WordPress task. The plugin might never register its event, WordPress might be unable to spawn the cron request, or the event might run and fail inside its callback.

Those failures need different fixes. Adding a real server cron will not repair a callback that throws a fatal error, and reading the event list will not tell you whether the server can reach wp-cron.php.

Many guides start with DISABLE_WP_CRON, a cron-management plugin, or a reminder to visit the site. Those are useful pieces of advice, but they do not tell you which layer failed.

The shortest reliable workflow is to inspect the queue, test the spawn mechanism, run the callback on purpose, and verify the side effect. Do it on a disposable copy so the test cannot publish a real post, charge a customer, or send a production webhook.

Start with a real copy of the site

A fresh WordPress install tells you whether the plugin can be activated. It does not tell you whether its scheduled task works with your existing options, database records, content, PHP version, or other plugins.

Clone the site into a sandbox and test there. Keep outbound integrations pointed at test endpoints, and do not connect the copy to a production payment, CRM, or webhook account just because the URL is temporary.

SandyWP gives the copy a real WordPress server with WP-CLI available over SSH. The documented CLI path needs a ready, account-owned sandbox on a paid plan, but the same WP-CLI commands work on any staging server where you have shell access.

If you prefer to work from your terminal, the SandyWP CLI can run one remote command and return its output:

sandywp ssh my-site --cmd "wp cron event list --fields=hook,next_run --format=json"

Replace my-site with the sandbox slug. On another host, run the command from the WordPress directory.

1. Check that the event exists

Start with the queue, not the callback. The official wp cron event command lists, schedules, runs, and deletes WP-Cron events.

wp cron event list --fields=hook,next_run --format=json

Find the hook your plugin owns and read its next run time. If the hook is not there, WordPress has nothing to execute, no matter how many times you load the homepage.

An absent event usually points to an activation path that did not run, a condition that was false, a schedule name that is not registered, or a plugin that only schedules work after a separate admin action. Check the plugin's activation and setup code before changing the server scheduler.

Duplicates are a different bug. If activation adds the same hook repeatedly without checking wp_next_scheduled(), the task may run several times and create duplicate emails, records, or API requests.

Run the list after a fresh activation and again after deactivation and reactivation. A normal plugin should leave one intended recurring event, not a new copy every time the activation code runs.

For a plugin you are developing, make the hook name easy to find and keep the schedule and callback registration close enough that a reviewer can follow both. The WordPress testing guide also points to wp cron event list and wp cron event run as the basic inspection tools.

2. Test whether WP-Cron can start

Once the event exists, test the mechanism that starts it:

wp cron test

The command checks whether DISABLE_WP_CRON is set, warns about ALTERNATE_WP_CRON, and attempts the HTTP spawn that WordPress uses for normal WP-Cron execution. It is a test of the spawning system, not a test that your particular callback completed.

If it reports that WP-Cron is disabled, that may be intentional. A site that disables page-load cron must have a replacement trigger, such as a host scheduler or a system cron that calls wp-cron.php; otherwise every scheduled task waits forever.

If the spawn fails, inspect the loopback path before touching the plugin. Common causes include a password gate that blocks the site's own request, a bad HTTPS configuration, a firewall rule, or a host that prevents the site from calling itself.

This is also why a low-traffic staging site is a poor place to wait for a task to become due. WP-Cron runs when WordPress receives a request, not when the wall clock reaches the scheduled time.

3. Run the callback on purpose

You can isolate the callback from the normal spawn path by running the event yourself. Use the exact hook from the event list:

wp cron event run my_plugin_cron_hook

This runs the next scheduled event for that hook in the sandbox. If you need to process every event that is already due, use the documented --due-now form:

wp cron event run --due-now

Run these commands only on the copy. A cron callback may send mail, mutate orders, delete expired records, call an API, or publish content immediately when you invoke it.

The command output is a useful first signal. A fatal error, PHP warning, timeout, or explicit failure gives you a path to follow, while a successful execution tells you that WordPress reached the callback without an obvious process-level failure.

It still does not prove the job did the right work. Check the result the user would care about: a database row, a post status, a generated file, a changed option, a queued request, or an API response recorded by a test endpoint.

4. Read the log while you reproduce it

Background work often fails away from the browser response. Turn on SandyWP's debug mode, run the event again, and inspect wp-content/debug.log immediately after it finishes.

You can read the log from the dashboard or over SSH:

sandywp ssh my-site --cmd "tail -n 100 wp-content/debug.log"

For a live reproduction, leave the log streaming in one terminal and run the command in another:

sandywp ssh my-site --cmd "tail -f wp-content/debug.log"

Look for the hook name, the plugin file that registered the callback, and the first error rather than the last cascade of warnings. A missing function, an undefined option, a database error, or an HTTP timeout usually explains the symptom better than the generic message that the scheduled action failed.

If the log stays empty and the callback command says it ran, the code may be completing without the side effect you expected. Add a temporary, unmistakable log line or inspect the data it should have changed, then remove the instrumentation before the plugin is shipped.

5. Test the normal request path too

Manual execution is deliberately direct. It tells you whether the callback can run, but it does not prove that a normal web request can spawn it.

After wp cron test passes, create or wait for a due event on the copy and load a page. You can use a browser or request the sandbox URL with curl, then run the event list and inspect the log again.

The useful result is a chain you can explain: the event was present, the request could spawn WP-Cron, the callback ran, and the expected record or output appeared. If only the manual command works, the callback is probably fine and the trigger path is where you should look.

Do not “fix” that by setting DISABLE_WP_CRON to true unless you are also configuring and testing a replacement trigger. Disabling the built-in scheduler without replacing it turns a visible delay into a silent queue.

How to read the failure

The hook is missing. The plugin did not schedule it, the activation/setup condition was not met, or the schedule was rejected. Inspect registration and activation first.

The hook exists, but wp cron test fails. The queue is healthy enough to inspect, but WordPress cannot start its normal HTTP runner. Check DISABLE_WP_CRON, loopback access, HTTPS, authentication, and host restrictions.

The hook exists, spawn works, but a manual run fails. The scheduler is not the root cause. Read the first fatal or warning, then test the callback's database, filesystem, and outbound-service assumptions.

The manual run works, but waiting does nothing. The callback is probably fine. Recheck whether the event is due, whether page loads reach the site, and whether the normal spawn request is blocked or disabled.

The command says success, but the result is wrong. The callback ran, but its business assertion failed. Verify the side effect directly and test the data shape, permissions, API response, and retry path that the callback relies on.

What a sandbox cannot prove

A sandbox is excellent for separating WordPress registration, execution, and callback behavior. It is not a replica of the production host's system scheduler or traffic pattern.

It will not tell you how a production WAF, CDN, object cache, host-installed must-use plugin, firewall, PHP extension, or server-level cron behaves. If the failure only appears in that layer, reproduce it on a staging environment with the same host configuration or run a narrowly scoped production check with safe data.

It also cannot make a slow callback reliable. A job that processes 100,000 records may pass on a small copy and time out in production, so test batch size, locking, retries, and memory limits separately from the basic cron path.

The short version

Run these checks in order:

wp cron event list --fields=hook,next_run --format=json
wp cron test
wp cron event run my_plugin_cron_hook
tail -n 100 wp-content/debug.log

Then verify the side effect instead of trusting the command output. A scheduled event is only a promise that WordPress has something to try; your test is complete when you know it can start, the callback can finish, and the result is correct.