WooCommerce Tips

Why Your WooCommerce Sale Doesn’t Start When You Think It Does (And What to Actually Do About It)

WooCommerce Troubleshooting

You Scheduled the Sale. WooCommerce Didn’t Get the Memo.

Why WooCommerce scheduled sale prices start late, end late, or don’t activate at all β€” and what the problem actually is.

\n

You set your sale to start at midnight. You checked the product. The dates were right. You went to bed. At 7 AM someone messaged to say the prices were still full. You logged in, found the sale hadn’t activated, saved the product manually, and it kicked in immediately.

This is one of the most reported WooCommerce scheduling problems β€” and it almost always gets misdiagnosed. Store owners assume they configured something wrong. They didn’t. The issue is in WooCommerce’s scheduling architecture itself: a daily cron job with no precision guarantee is responsible for applying your sale prices, and that cron job often runs hours after you expected it to.

This post explains what’s actually happening at the system level, why common workarounds only partially help, and what a genuine fix looks like for stores that need sales to start and end reliably.

The scenario that sends store owners to the support forums

You’ll find dozens of threads on WordPress.org and Reddit that follow the same pattern. A store owner schedules a WooCommerce sale price with a specific start and end datetime. The sale doesn’t activate at the scheduled time. Or it activates hours later. Or it was supposed to end at 11:59 PM and was still running at noon the next day.

Some common variations:

  • Black Friday sale set for midnight β€” still showing regular prices at 8 AM
  • Flash sale set to run for 24 hours β€” still active 36 hours later
  • Weekend sale that should have ended Sunday night β€” still live on Monday morning
  • Sale activated immediately after manually re-saving the product, despite the scheduled date having passed

That last one β€” manual save triggering activation β€” is the key diagnostic clue. It tells you exactly what’s happening. The sale wasn’t broken; it just hadn’t been checked yet.

How WooCommerce actually applies scheduled sale prices

WooCommerce’s native sale price system works like this: you set a “Sale price dates from” and “Sale price dates to” on a product. These are stored as product meta β€” two fields called _sale_price_dates_from and _sale_price_dates_to.

Having those dates stored doesn’t automatically make the sale price active. WooCommerce has to look at those dates and then actually write the sale price into the product’s active price field. That write happens through a cron job called woocommerce_scheduled_sales.

The cron job runs through every product in your store that has scheduled sale dates, compares those dates to the current time, and updates the active sale price accordingly. Products whose start date has passed get their sale price applied. Products whose end date has passed get it removed.

This is a sensible design for a world where you have ten products and the cron job runs reliably every hour. The problem is that this cron job runs once per day by default β€” and there are no precision guarantees on when that day it runs.

The daily cron problem: precision you don’t have

The woocommerce_scheduled_sales cron event is scheduled to run daily. By default, that means roughly once every 24 hours. If it last ran at 2 AM and your sale was scheduled for midnight, you might wait up to 24 more hours before WooCommerce applies your prices. In practice, it tends to run sometime during the following morning β€” but there’s no guarantee.

This is actually documented behavior. WooCommerce’s own support forums have confirmed this limitation multiple times. The GitHub issue tracker has entries referencing this exact problem. The daily cron design made sense for simple use cases, but it means scheduled sale prices are not precise to the hour, let alone to the minute.

This is separate from the WP-Cron traffic problem. Even if you have a real server cron triggering WP-Cron every 5 minutes β€” which eliminates the traffic-dependency issue β€” your sale still won’t activate until woocommerce_scheduled_sales runs. That job is scheduled daily, not every five minutes. A real server cron helps, but it doesn’t fix the precision problem. More on this below.

The mechanism that applies your sale prices is only awake once a day. Schedule a sale for midnight on Monday, and if the cron ran at 3 AM on Sunday, you might not see those sale prices until 3 AM on Tuesday.

Why manual save fixes it instantly

When you save a product manually in the WordPress admin, WooCommerce runs its product save hooks, which include an immediate check and write of the active sale price based on the current date and time. It bypasses the scheduled cron entirely.

This is why your sale activates the moment you re-save the product. You’re not fixing a scheduling problem β€” you’re manually triggering the logic that the cron was supposed to run automatically. The cron hadn’t run yet. Your manual save ran it immediately for that one product.

This also explains why the problem scales with catalog size. Saving 5 products manually is annoying but doable. Saving 200 products at the start of every promotion is not a workflow.

Caching makes it worse

Even after the cron runs and applies your sale prices at the database level, customers may not see updated prices immediately. Object caching, page caching, and transient storage all hold the old prices for some additional window.

This is a separate problem from the scheduling delay, but they compound each other. The sequence looks like this:

  1. You schedule a sale for midnight
  2. The cron runs at 6 AM and applies the sale price to the database
  3. Your page cache holds the pre-sale price for another hour or two
  4. Customers see sale prices starting around 7-8 AM

If you’re running a genuine flash sale or coordinating with email campaigns that went out at midnight, this gap is a real problem. Customers who clicked your email at 12:15 AM landed on a product still showing full price. Some of those people didn’t come back.

The caching problem has its own solutions β€” cache purging on product update, shorter TTLs, or bypassing caching for sale price fields. But none of that helps if the underlying database price hasn’t been updated yet. You have to fix the scheduling layer first.

Does a real server cron actually fix it?

Partially. Setting up a real server cron to trigger WP-Cron more frequently eliminates the traffic-dependency problem. Your scheduled tasks run on a clock, not on visitor arrivals. That’s worth doing regardless of this issue β€” it improves the reliability of the entire site.

But it doesn’t solve the precision problem for WooCommerce scheduled sales, for a subtle reason: running WP-Cron more often means WP-Cron fires more often, but woocommerce_scheduled_sales is still scheduled as a daily event. More frequent WP-Cron execution doesn’t cause a daily event to run more than once a day. WP-Cron will simply see that the event isn’t due yet and skip it.

To actually get more frequent sale price checks, you’d need to either:

  • Change the woocommerce_scheduled_sales event to run hourly instead of daily (possible via code, but it adds load and isn’t officially supported)
  • Manually trigger a WooCommerce product update check via a custom cron event that fires more often
  • Move away from WooCommerce’s per-product scheduled sale system entirely and use a different scheduling layer

The third option is the most reliable β€” and it’s the architectural approach that avoids the problem altogether.

What a genuine architectural fix looks like

The root cause is that WooCommerce’s native scheduled sale system is product-centric: each product carries its own sale dates, and a daily sweep checks whether each product’s dates have been crossed. That design can never be fully reliable for precise scheduling because the sweep is periodic and coarse.

A more reliable architecture inverts this: instead of asking “has this product’s sale date passed?”, ask “which campaigns are currently active right now?” and apply prices based on that real-time answer. The schedule lives at the campaign level, not at the per-product level. Price activation isn’t a background job that runs later β€” it’s a real-time lookup that happens at the moment a customer views a product or adds it to their cart.

This is how campaign-based discount systems work, including Smart Cycle Discounts. Rather than relying on WooCommerce’s woocommerce_scheduled_sales cron to apply and remove sale prices, the plugin applies discounts dynamically: it checks whether an active campaign covers the product being viewed right now, and if so, applies the campaign’s discount at that moment. When a campaign’s end time passes, the next product view shows the regular price β€” because the campaign is no longer active according to the current timestamp, not because a cron job removed a database entry.

The practical difference for a store owner:

  • You schedule a campaign to start at midnight on Black Friday
  • A customer visits your store at 12:01 AM
  • The campaign is active, so the discounted prices are shown immediately
  • No cron job needed to “apply” the prices β€” they’re applied in real time as long as the campaign is within its scheduled window

Important caveat: This approach means sale prices are applied dynamically, so the product’s sale price in the database may not reflect the discounted amount the way native WooCommerce scheduled sales do. The discount is visible on the frontend, in the cart, and at checkout β€” but if you’re relying on the database sale price field for external integrations (like a feed or third-party sync), you’d want to verify how those integrate.

What to do depending on your situation

There isn’t one answer here β€” the right approach depends on how precise your timing requirements are and how much technical work you’re willing to do.

If your timing requirements are loose (within a few hours is fine)

Make sure you have a real server cron triggering WP-Cron on a regular interval β€” every 5 to 15 minutes is common. On most managed WordPress hosting (WP Engine, Kinsta, Cloudways), this is either already configured or available as a one-click setting. Check your hosting’s documentation. This won’t give you precision, but it reduces the worst-case delay significantly.

You can also schedule your sale to start an hour or two earlier than your announced start time. This is an ugly workaround that requires you to mentally offset all your dates, and it still doesn’t give you precise end times. But for a store running an occasional seasonal promotion, it may be good enough.

If your timing requirements are exact (midnight means midnight)

Running WP-Cron via real server cron and modifying the woocommerce_scheduled_sales recurrence to hourly will get you close β€” within an hour at worst. This requires a small code snippet in your theme’s functions.php or a custom plugin, and needs to be maintained carefully so the original schedule is removed when you deactivate the change.

But if you’re running flash sales, coordinating email campaigns with specific send times, or running time-sensitive promotions where a three-hour delay actually costs conversions, the per-product scheduled sale model is the wrong tool entirely. A campaign-based scheduling system that applies discounts at runtime is more appropriate for this level of operational precision.

If you’re running recurring or high-frequency promotions

Managing start/end dates at the per-product level doesn’t scale. A weekly flash sale touching 50 products means WooCommerce’s cron has to check and update 50 products twice per week β€” assuming it runs at the right time. Campaign-level scheduling, where you define the promotion once and apply it to as many products as you need, is more reliable and far less maintenance-intensive. This is the approach covered in more detail in our guide to running flash sales without manual intervention.

FAQ

Why does my WooCommerce scheduled sale not start on time?

WooCommerce applies scheduled sale prices through a cron job called woocommerce_scheduled_sales that runs once daily by default. If this job hasn’t run since your sale’s start time passed, the sale prices won’t be active yet. The cron job checks product-level sale date fields and writes the active price to the database β€” but only when it fires, which could be hours after your intended start time.

Why does my WooCommerce sale price activate immediately when I save the product manually?

When you save a product in the WordPress admin, WooCommerce immediately checks and applies the correct sale price based on the current date β€” bypassing the scheduled cron entirely. The cron hadn’t run yet for your product, so the prices weren’t active. Your manual save triggered the same logic that the cron was supposed to run automatically.

Will setting up a real server cron fix WooCommerce sale timing?

Partially. A real server cron eliminates the traffic-dependency problem β€” WP-Cron will fire on a schedule rather than waiting for site visitors. But woocommerce_scheduled_sales is still a daily event, so running WP-Cron every 5 minutes won’t cause it to run every 5 minutes. You’ll still have up to a day’s delay unless you also change the recurrence of that specific event.

How do I make WooCommerce scheduled sales check prices more often?

You can reschedule the woocommerce_scheduled_sales event to run hourly rather than daily using a code snippet. Clear the existing event with wp_clear_scheduled_hook() and re-register it with wp_schedule_event() using the ‘hourly’ interval. This adds some database load on large stores, so it’s worth testing first. Alternatively, a campaign-based system that applies discounts at runtime bypasses this cron layer entirely.

My WooCommerce sale ended but prices are still discounted β€” is this the same problem?

The same cron job is responsible for removing sale prices as well as applying them. If woocommerce_scheduled_sales hasn’t run since your end date passed, the sale price is still in the database and still visible. There’s also a separate caching layer that can hold stale prices even after the database has been updated. The two problems are related but distinct β€” a sale that doesn’t end on time often has both the cron delay and a caching component. Our post on WooCommerce sale prices that don’t go away covers the caching side in more detail.

Does this affect all WooCommerce sale scheduling, or just specific setups?

It affects any setup that relies on WooCommerce’s native per-product “Sale price dates from / to” fields, because all of those rely on the same woocommerce_scheduled_sales cron event. Hosting environment and traffic volume affect how severe the delay is, but the fundamental precision limit is inherent to the daily-cron design. Stores on high-traffic sites with correctly configured server crons will see shorter delays, but rarely zero delay for exact start times.

The bottom line

WooCommerce’s scheduled sale system is built around a daily cron sweep. That was a reasonable design choice when the feature was introduced β€” it works for simple store-wide sales where a few hours’ precision doesn’t matter. But it’s the wrong model for flash sales, coordinated email campaigns, or any promotion where timing precision is part of the value.

The immediate fix is making sure WP-Cron is backed by a real server cron so you’re not also fighting the traffic-dependency problem on top of the daily-cron problem. Beyond that, the choice is between accepting the imprecision, working around it with code, or moving to a campaign-based scheduling layer that applies discounts in real time rather than through a background sweep. Which of those makes sense depends entirely on what your store actually needs from its promotions.