Back to blog

Guides · Apr 17, 2026

WooCommerce cron not running: the traffic problem and the fix

Action Scheduler is the queue WooCommerce uses for renewal emails, coupon expirations and abandoned cart triggers. It is fine. The problem is that it depends on wp-cron to tick, and wp-cron depends on visitor traffic. Here is the 5-minute fix that puts WooCommerce on a real external clock.
crontap.com / blog
Subscription renewal emails firing hours late, coupon expirations not kicking in, Action Scheduler past-due piling up: WooCommerce cron problems are almost always wp-cron problems. Here is the 5-minute fix that disables wp-cron and points Crontap at the queue on a real external clock.

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.

  1. 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.
  2. 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.
  3. 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:

  1. WooCommerce or a plugin enqueues an action ("send renewal email for subscription #1234 at 03:00 on 2026-04-22").
  2. At the scheduled time, the action moves from "Pending" to "Past-due" until the queue runs.
  3. 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.
  4. 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

  1. Open wp-config.php in the root of your WordPress install.
  2. Above the line that says /* That's all, stop editing! */, add:
define('DISABLE_WP_CRON', true);
  1. 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

  1. Head to Crontap, sign up (free tier available, no credit card), and click Create schedule.
  2. 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.
  3. Method. GET.
  4. 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.
  5. 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.
  6. 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.php lands in your ops channel; you find out before the customer does.
  7. Press Perform test. A healthy wp-cron.php tick 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.

  1. WP Crontrol, under Tools, then Cron Events. All scheduled events should show "Next Run" timestamps that count down predictably. The action_scheduler_run_queue event 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.
  2. 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

From the blog

Read the blog

Guides, patterns and product updates.

Tutorials on scheduling API calls, webhooks and automations, plus deep dives into cron syntax, timezones and reliability.

Alternatives

Vercel Cron every minute: beating the Hobby hourly limit

Vercel Cron caps Hobby at hourly cadence and 5 jobs, and ties every change to a redeploy. Here is the external cron pattern teams use to ship per-minute schedules, per-IANA timezones, and one dashboard across projects without paying $20/mo per user for Pro.

Alternatives

Cloud Run cron without Cloud Scheduler

Cloud Scheduler costs $0.10 per job per month after the first 3 and asks for OIDC plus IAM bindings on every target. Here is the IAM-free pattern Cloud Run teams use to fire their .run.app URLs on a clock with one bearer token and one dashboard across every GCP project.

Alternatives

Heroku Scheduler alternative: any cron expression without the add-on

Heroku Scheduler caps you at three cadences and account-wide UTC, and spins a one-off dyno per run. Here is the external cron pattern that gives you any cron expression, per-schedule timezones, and zero per-execution dyno spin-up cost.

Guides

Running an OpenAI sentiment pipeline on a real scheduler

OpenAI batch work needs a clock, not a user session. Here is the scheduled HTTP-route pattern teams use to drain LLM batches at a sustainable rate inside OpenAI's rate limits, with per-task failure alerts.

Reference

Cron syntax cheat sheet with real-world examples

Cron syntax without the math. Every pattern you're likely to reach for (every 5 minutes, weekdays, business hours, first of the month), with a practical example and a link to a free debugger.