Customers keep complaining that the subscription renewal email fires hours after the renewal, or that the coupon expiration did not kick in until someone actually visited the cart page. The culprit is almost never WooCommerce itself. It is WordPress wp-cron. Here is what to check, why it happens, and the fix that takes 5 minutes.
If you just want the short version: install WP Crontrol, confirm Action Scheduler is backlogged, disable wp-cron in wp-config.php, and point Crontap at wp-cron.php every 5 minutes. The renewal emails go back to firing on the right day.
Three symptoms your WooCommerce cron is not running
If any of these sound like your shop, "WooCommerce cron not running" is almost certainly a symptom of wp-cron, not Action Scheduler.
- Subscription renewal emails fire late. A subscription that renews on Monday at 03:00 sends the renewal email at 09:11 on Monday after the first staff member loads
/wp-admin. The renewal itself happened on Monday in the database; the email-send action sat in the queue, waiting for someone to wake the site up. - Coupon expirations are stale. A coupon set to expire at midnight is still accepting orders at 06:00 because the "expire coupons" action has not run yet. Customers screenshot the discount code, support handles the refund, you wonder how this kept happening.
- Action Scheduler past-due grows. Open WooCommerce, then Status, then Scheduled Actions. Filter by Past-due. If the count is in the dozens or hundreds and not going down, your queue is stuck.
If symptom 3 is true, it is unambiguously wp-cron. Action Scheduler itself is fine; nothing is poking it.
Why Action Scheduler still needs wp-cron to tick
Action Scheduler is the queue WooCommerce uses for everything async: order emails, subscription renewals, coupon expirations, abandoned cart triggers, refund processing, customer-facing report builds. Every WooCommerce extension uses it too (Subscriptions, Bookings, Memberships, AutomateWoo).
It is a queue, not a clock. It does not have its own scheduler. It piggybacks on wp-cron via the action_scheduler_run_queue event, which wp-cron schedules every minute. The flow is:
- WooCommerce or a plugin enqueues an action ("send renewal email for subscription #1234 at 03:00 on 2026-04-22").
- At the scheduled time, the action moves from "Pending" to "Past-due" until the queue runs.
- wp-cron is supposed to tick every minute and run
action_scheduler_run_queue, which drains as many actions as it can in one HTTP request. - The action runs, the email goes out, the action moves to "Complete".
The catch is step 3. wp-cron only ticks every minute if a request hits the site every minute. On a quiet 4am morning, no request, no tick, no queue drain. A subscription renewal scheduled for 03:00 sits in Past-due for hours until someone shows up and the queue catches up. (For the full story on why wp-cron itself misses, see Why your WordPress scheduled tasks are missing.)
The good news is that Action Scheduler is idempotent at the action level. An action runs exactly once even if multiple requests arrive at the same time, so external triggers do not double-fire emails or duplicate renewals. The failure mode is silent lateness, not duplication. That makes the fix safe to roll out without holding your breath.
The 5-minute fix with Crontap
The fix has two halves: stop the visitor-tied behaviour, and start an external trigger.
Step 1: Disable wp-cron
- Open
wp-config.phpin the root of your WordPress install. - Above the line that says
/* That's all, stop editing! */, add:
define('DISABLE_WP_CRON', true);
- Save and deploy.
WordPress will no longer fire wp-cron on visitor page loads. If you stop here without the next step, scheduled actions stop firing entirely. Do not stop here.
Step 2: Point Crontap at wp-cron.php
- Head to Crontap, sign up (free tier available, no credit card), and click Create schedule.
- URL. Paste
https://yourshop.com/wp-cron.php?doing_wp_cron=1. WordPress accepts this same query string for its own internal cron requests; we are mimicking the shape WordPress already understands. - Method.
GET. - Cadence. Every 5 minutes is the safe default. If you have time-sensitive subscriptions or coupons, go to every 1 minute on Crontap Pro. The floor is 1 minute.
- Timezone. Pick the IANA zone your store runs in. WooCommerce reads its own site timezone for human-facing dates (renewal emails, scheduled reports). Matching the Crontap schedule to the same timezone keeps the mental model clean across DST.
- Failure alerts. Add an integration: email / webhook (Slack / Discord / Telegram). Crontap fires on 4xx and 5xx with the response body and timing in the payload. A 500 from
wp-cron.phplands in your ops channel; you find out before the customer does. - Press Perform test. A healthy
wp-cron.phptick returns 200 with empty or near-empty body.
Fix this in 60 seconds with Crontap. Free tier available. No credit card. Schedule your first job →
If you would rather call Action Scheduler directly without going through wp-cron.php, you can also point Crontap at https://yourshop.com/wp-admin/admin-ajax.php?action=as_async_request_queue_runner. This skips the wp-cron event registry and asks Action Scheduler to drain the queue directly. The downside is the nonce: it expires, you have to maintain it, and most teams find pointing at wp-cron.php cleaner. Stick with wp-cron.php unless you have a specific reason not to.
When to move to WP-CLI + system cron instead (busy stores)
If your store has thousands of subscriptions or tens of thousands of orders per day, even disable-wp-cron + Crontap can run hot. The bottleneck is not Crontap; it is wp-cron.php running everything inline on a single PHP request.
For shops in that range, the next step up is WP-CLI on a server-side cron:
*/1 * * * * cd /var/www/yourshop && wp action-scheduler run --batch-size=100 >/dev/null 2>&1
This runs Action Scheduler directly via WP-CLI, with explicit batch sizes, on a real cron daemon. Action Scheduler exposes a CLI subcommand exactly for this case. The advantage is throughput; the disadvantage is you need shell access and the operational discipline of a real server.
A useful hybrid: use Crontap for the every-5-minute "general wp-cron tick" so all the non-WooCommerce events keep firing, and use WP-CLI on a server cron for the every-minute Action Scheduler queue run. Crontap handles the alerting (Slack on 4xx/5xx) for the wp-cron path, and the WP-CLI path is high-throughput. Most stores never need this; busy ones do.
Verifying the fix with WP Crontrol and Action Scheduler logs
Two views to keep open for the first 24 hours.
- WP Crontrol, under Tools, then Cron Events. All scheduled events should show "Next Run" timestamps that count down predictably. The
action_scheduler_run_queueevent should fire every minute. If it stays at "Now" without changing, your trigger is not landing; check Crontap's Activity tab for 4xx or 5xx responses. - WooCommerce, under Status, then Scheduled Actions. Filter by Past-due. The count should drop quickly after the first Crontap fire and stabilize near zero. Filter by Pending to see the next batch of actions queued up. Filter by Failed if anything threw an exception (separate problem; the queue is at least running).
A healthy WooCommerce store after the fix:
- Past-due: 0 to single digits at any moment.
- Pending: dozens to hundreds, depending on store volume, actively counting down.
- Complete: thousands per day for a busy store, all with sensible timestamps.
If Past-due is climbing instead of falling, your batch size is too small or your wp-cron tick is too infrequent. Increase Crontap cadence to every 1 minute on Pro, or drop to WP-CLI as described above.
FAQ
Will this fire renewal emails twice?
No. Action Scheduler stores each action with a unique identifier and a status. Once an action moves to Complete, it cannot run again. Even if Crontap and a stray visitor both hit wp-cron at the same moment, only one of them runs each pending action; the other sees the action is already Complete and exits.
Do I need to disable wp-cron, or can I just add Crontap on top?
You can technically leave wp-cron on and add Crontap as a redundant trigger. In practice, every team we have seen is happier with wp-cron disabled. The visitor-tied tick is the source of the slow-first-page-of-the-morning problem; removing it is a UX win independent of Action Scheduler.
What if my host runs its own 15-minute cron on wp-cron.php?
Most managed WordPress hosts (Kinsta, WP Engine, Hostinger Business) do this. It is fine to leave their 15-minute cron in place and add Crontap on top at every 5 or every 1 minute. The two requests will not collide (idempotent), and your effective cadence becomes whichever is faster.
What about Action Scheduler in non-WooCommerce contexts?
Action Scheduler ships with WooCommerce but it is a standalone library. Some plugins (Mailchimp for WooCommerce, AutomateWoo, FluentCRM) and theme builders use it directly. The fix is the same: drain the queue by ticking wp-cron on a real external clock.
How do I tell if my issue is wp-cron versus an actual WooCommerce bug?
Open WP Crontrol. If action_scheduler_run_queue is in the events list and its "Next Run" is in the past, wp-cron is the issue. If action_scheduler_run_queue is firing on time but specific actions are still failing, it is a WooCommerce or plugin bug; check Action Scheduler, then Failed, for the exception trace.
Does Action Scheduler need every-minute wp-cron?
Action Scheduler schedules its queue runner every minute by default. wp-cron does not have to fire that often, but the queue drains faster when it does. Every 5 minutes is the comfortable middle ground; every 1 minute is the floor on Crontap Pro and is the right choice for stores where action latency matters (live coupon expirations, time-sensitive promotions, near-real-time abandoned cart triggers).
What happens to actions that were past-due before I shipped the fix?
They run on the next Crontap tick. Action Scheduler does not throw away past-due actions; it runs them as soon as the queue gets a chance. If the past-due list was big, expect the first few Crontap requests to be slower than usual while the backlog clears. After that, response times settle to sub-second.
References
Related on Crontap
- Why your WordPress scheduled tasks are missing. The wp-cron deep dive that this post builds on, with the full why and the click-by-click setup.
- Cron jobs for WooCommerce, the platform spoke. The WooCommerce-specific guide.
- Cron jobs for WordPress, the platform spoke. The broader WordPress-platform guide for non-WooCommerce sites.
