WooCommerce Scheduled Sale Not Starting: Every Reason Why and How to Fix It
WooCommerce Troubleshooting
The Sale You Scheduled. The Store That Didn’t Notice.
Six reasons a WooCommerce scheduled sale fails to go live on time — and exactly what to check and fix for each one.
You set up the sale. You checked the start date. You went to bed, or sent the email, or posted on social — and then the store sat there serving full prices for hours after the scheduled start time. Or the sale activated but only some products showed the discounted price. Or it activated and then a different plugin quietly reversed it.
WooCommerce scheduled sales fail in six distinct ways, and each one needs a different fix. The forum answer that says “clear your cache” gets you part of the way there for one of the six. The rest have nothing to do with caching.
This guide works through all six causes in diagnostic order. Read the symptom, identify your situation, apply the fix. If you have already confirmed that WP-Cron is the root of your problem, the deep dive on WooCommerce WP-Cron scheduling covers the mechanism and setup steps in more detail than this guide does.
How to use this guide
The six causes are ordered from most common to least common. Check Cause 1 first. If your symptom matches, fix it and see if the problem resolves before moving to the next one. Most stores that have a stuck scheduled sale are dealing with Cause 1 or Cause 2. The others are real but less frequent.
If your sale activated but the price is still showing after the sale ended, that is a related but different problem. The full guide to sale prices that don’t go away covers that scenario in detail.
Cause 1: WP-Cron not firing on low-traffic sites
What you see
The sale was scheduled for midnight. It went live at 6am, or 9am, or not until someone visited the store the next day. Or it worked fine last month but failed this month during a quieter period. The sale does activate eventually — it just activates late, with no error messages anywhere.
What is actually happening
WooCommerce’s built-in scheduled sale system runs through a WordPress background process called WP-Cron. Unlike a real system-level scheduler, WP-Cron is not running on a clock. It only fires when a visitor loads a page on your site. If nobody visits your store between the scheduled time and hours later, the scheduled task simply waits in a queue until the next visitor arrives.
The specific cron job responsible for WooCommerce scheduled sales is called woocommerce_scheduled_sales. It runs daily by default — which means even with traffic at the right time, this job may have already run for the day and won’t check your new sale prices until the following run. A store with low overnight traffic and a midnight sale start time can easily see delays of 6–12 hours or more.
The daily cron problem is separate from the traffic problem
Even if you configure a real server cron job that triggers WP-Cron every 5 minutes, woocommerce_scheduled_sales is still scheduled as a daily event. More frequent WP-Cron execution will not make a daily job run more often. It will eliminate the traffic-dependency delay, but you can still have up to a 24-hour window before the sale activates if the daily event ran recently. Both layers of the problem need to be addressed.
How to confirm this is your cause
- Your sale activated late, not “not at all”
- Re-saving the product manually caused the sale to activate immediately
- Your store has low or no traffic at the scheduled start time (overnight, off-peak)
- You see a large queue of pending tasks under WooCommerce → Status → Scheduled Actions
How to fix it
Step 1: Confirm WP-Cron is actually running
Install the free WP Crontrol plugin (wordpress.org/plugins/wp-crontrol/). Go to Tools → Cron Events and run the loopback health check. If it fails, your server is blocking WP-Cron’s loopback request — a security plugin or firewall rule is the usual culprit. WP Crontrol will tell you specifically what failed.
Step 2: Replace WP-Cron’s page-load trigger with a real server cron
Open your wp-config.php file and add: define('DISABLE_WP_CRON', true); — then add a server cron job via your hosting control panel (usually under cPanel → Cron Jobs) to call wp-cron.php every 5 minutes: */5 * * * * wget -q -O - https://yoursite.com/wp-cron.php?doing_wp_cron >/dev/null 2>&1. This makes WP-Cron fire on a real clock, independent of traffic.
Step 3: Make woocommerce_scheduled_sales run more often
With a real server cron in place, add a snippet to your theme’s functions.php (or a custom plugin) to reschedule the daily event as hourly: first use wp_clear_scheduled_hook('woocommerce_scheduled_sales') to remove the existing schedule, then re-register it with wp_schedule_event(time(), 'hourly', 'woocommerce_scheduled_sales'). This reduces the maximum activation delay from 24 hours to one hour.
For a full explanation of why WP-Cron behaves this way and how Action Scheduler relates to it, the WooCommerce WP-Cron deep dive covers the mechanism in detail.
Cause 2: Server and site timezone are mismatched
What you see
You scheduled the sale for 8am. It went live at 3am, or 1pm, or some other time that is exactly a whole number of hours off from what you expected. The offset is consistent — always the same number of hours wrong.
What is actually happening
WooCommerce uses WordPress’s configured timezone (found under Settings → General → Timezone) to interpret the scheduled sale dates you enter in the product editor. If your WordPress timezone setting says “America/New_York” but your server’s PHP timezone is configured to UTC or a different zone, the timestamps stored in the database can end up shifted by the difference between the two.
This is one of the more confusing failures because it looks like a WP-Cron timing problem (the sale is late or early) but has nothing to do with traffic or cron frequency. The sale fires exactly when it was scheduled — the schedule itself is just offset from what you intended.
How to confirm this is your cause
- The timing offset is always the same number of hours, not random
- The offset roughly corresponds to the difference between your local timezone and UTC (e.g., UTC+5 site with UTC server = 5 hours off)
- Your WordPress timezone setting (Settings → General) is set to a named city or region, not UTC
How to fix it
Step 1: Verify your WordPress timezone setting
Go to Settings → General → Timezone. Make sure it is set to the actual geographic timezone where your store operates — not a UTC offset number. A named timezone like “America/Chicago” handles daylight saving time correctly; a raw UTC+X offset does not adjust for DST and will drift twice a year.
Step 2: Check your PHP timezone configuration
Create a temporary PHP info file (or use a plugin like “Display PHP Version” that shows phpinfo() output) and look for the date.timezone value in the PHP configuration section. If it is blank or set to something different from your WordPress timezone, ask your hosting provider to align it — or add date_default_timezone_set('America/Chicago'); (substitute your actual timezone) to your wp-config.php as a workaround. Note that this is a workaround, not a proper fix — the hosting-level configuration is the correct place for this setting.
Step 3: Re-enter the sale dates after fixing the timezone
Existing scheduled dates were stored using the old timezone offset. Once you correct the timezone settings, re-enter your sale start and end dates to ensure they are stored with the correct offset. Old dates will still be wrong even after you fix the timezone setting.
Cause 3: Caching is serving stale prices after the sale activates
What you see
The cron ran and updated the database — but customers are still seeing full prices on product pages. When you check the product in the WooCommerce admin, it correctly shows the sale price. The sale is active at the data layer, but cached pages are still serving the old version.
What is actually happening
Page caching plugins save a snapshot of rendered HTML and serve it to subsequent visitors without generating a fresh page from the database. If the cache took a snapshot before your sale activated, every visitor gets that stale snapshot until the cache expires or is cleared. On stores with long cache TTLs (24-hour page caches are common), this compounds the WP-Cron delay: the cron runs late, and then the cache adds more hours on top.
Object caching (Redis, Memcached) adds another layer. Even after you clear the page cache, WooCommerce’s pricing transients may still be cached in Redis with stale values. Clearing one layer and not the other leaves part of the problem in place.
How to confirm this is your cause
- The product in WooCommerce admin correctly shows the active sale price
- Customers see full prices on the product page
- Opening the product in a private/incognito window with a cache-bypass parameter (check your caching plugin’s docs for the exact parameter) shows the correct sale price
How to fix it
Step 1: Flush the page cache
In your caching plugin, do a full cache purge. For WP Rocket: Dashboard → Clear Cache. For LiteSpeed Cache: LSCache → Flush All. For W3 Total Cache: Performance → Purge All Caches. A full flush is faster than trying to target specific product pages, and the temporary performance hit is worth it for a sale launch.
Step 2: Clear WooCommerce transients
Go to WooCommerce → Status → Tools and run “Clear transients.” This flushes WooCommerce’s own cached pricing data, which is separate from your page cache. If you use Redis or Memcached for object caching, flush the object cache separately — in most hosting control panels this is a one-click operation.
Step 3: Enable WooCommerce integration in your caching plugin
WP Rocket, LiteSpeed Cache, and most major caching plugins have a dedicated WooCommerce mode that automatically invalidates cached product pages when product data changes. If this is off, your cache will never clear itself when a sale activates. Turn it on. Also consider reducing your product page TTL — a 2-hour cache TTL is a reasonable compromise for stores that run regular time-sensitive promotions.
Caching and WP-Cron delays compound each other
If the cron runs late AND caching adds more delay on top, your midnight sale could effectively start mid-morning. Fix the cron problem first, then address caching. Fixing the cache without fixing the cron just reduces one layer of delay while leaving the more fundamental timing problem in place.
Cause 4: Sale price not propagating to product variations
What you see
The sale activates on time. The main product page shows the sale price (or a range that includes the sale price). But when a customer selects a specific variation — a particular size, colour, or configuration — the variation shows the original price, or the sale price only appears on some variations but not others.
What is actually happening
WooCommerce variable products work differently from simple products when it comes to sale prices. A variable product is a parent product with multiple child products (the variations). Sale prices can be set at either the variation level or via a bulk edit that pushes the price to all variations.
When you set a scheduled sale price on the parent variable product, WooCommerce does not automatically push that price to all child variations. Each variation needs its own sale price set. If you set the sale at the parent level and only some variations have their own sale price configured, only those variations will show the discount. The rest show their individual regular prices.
The WooCommerce bulk edit for variations is also sometimes inconsistent about whether it schedules the dates along with the price — it may write the sale price without the date, which means the price is visible immediately (or never, depending on the cron timing) rather than at the scheduled start time.
How to confirm this is your cause
- The sale activates, but only on some product variations
- The product is a variable product (not a simple product)
- When you go to the product variations in admin, some variations have an empty sale price field
How to fix it
Step 1: Use WooCommerce’s bulk variation edit to set sale prices
Open the variable product in WooCommerce admin, go to the Variations tab, select “All Variations” from the bulk edit dropdown, then choose “Set sale price” or “Set scheduled sale dates.” Apply the sale price and dates to all variations at once. This is more reliable than editing each variation individually.
Step 2: Verify each variation’s sale price after the bulk edit
After the bulk edit, scroll through the individual variations and confirm each one has a sale price set. WooCommerce’s variation interface can be slow to save on large products — if you have more than 30 variations, save in batches of 10–15 at a time and wait for the save confirmation before proceeding.
Step 3: For large catalogs, consider a campaign-based approach
Managing sale prices on variable products with many variations at the per-variation level is genuinely tedious and error-prone. If you run frequent sales across variable products, a campaign-based discount plugin that applies discounts at the product level (covering all variations) rather than requiring individual variation edits is worth considering for the maintenance savings alone. See the prevention section below.
Cause 5: Another discount plugin is overriding your sale price
What you see
The sale activates correctly — WooCommerce admin shows the sale price, the cron ran on time — but customers see either the original price or a different price on the product page and in the cart. The price is not what you set. It may be lower or higher than your intended sale price, but it is not the correct discounted amount.
What is actually happening
WooCommerce’s pricing system uses PHP filters — specifically woocommerce_product_get_price, woocommerce_product_get_sale_price, and woocommerce_before_calculate_totals — to let plugins modify prices at display and cart time. If you have multiple plugins hooked into these filters (a general discount plugin, a dynamic pricing plugin, a role-based pricing plugin, or a campaign plugin), each one runs in hook priority order and can override what the previous one set.
The symptom looks like the sale “isn’t showing” — but what is actually happening is that another plugin’s discount calculation is running after your WooCommerce native sale price and replacing it with a different value, or resetting it to the regular price because the other plugin’s own logic says no discount applies.
How to confirm this is your cause
- The product’s sale price field in admin is correctly set
- The price displayed on the frontend is different from what you set
- You have more than one plugin that modifies WooCommerce prices installed and active
- Deactivating your other discount plugins temporarily causes your sale price to display correctly
How to fix it
Step 1: Identify which plugin is overriding the price
Deactivate your discount and pricing plugins one by one on a staging or local environment (never on production) while checking the affected product page. When the correct sale price appears after deactivating a specific plugin, that plugin is the conflict. Keep that one active and disable it last as your process of elimination.
Step 2: Check each plugin’s priority on price filters
WooCommerce price filters run in priority order (lower number = runs first). Plugins that hook at priority 10 run before plugins at priority 20. If two plugins both modify the sale price, whichever runs last wins. Check each plugin’s documentation or support pages for information on its filter priority — some have settings to adjust this.
Step 3: Consolidate to a single discount management system
Running multiple pricing plugins is the primary source of these conflicts. Each plugin was built assuming it has sole ownership of the price filters. Where possible, consolidate your discount strategy into a single plugin with the features you actually need, rather than running multiple systems simultaneously. For a detailed breakdown of how WooCommerce discount plugin conflicts work, see the guide to WooCommerce discount stacking and conflicts.
Cause 6: The schedule dates or times were entered incorrectly
What you see
The sale activates on a completely unexpected date — not late by a few hours, but off by a full day, or activated immediately when it shouldn’t have, or scheduled so far in the future it will never be reached in practice. Sometimes the sale appears to activate instantly (because the date was accidentally set in the past) or not at all (because it was set for the wrong year or month).
What is actually happening
WooCommerce’s date picker in the product editor is a straightforward calendar — but it is easy to make a small input error that has a big effect on timing. Common mistakes:
- AM/PM confusion: entering 12pm when you meant 12am (midnight), or 12am when you meant noon. 12:00 AM = midnight. 12:00 PM = noon. This trips up many people because “12am” sounds like it should be daytime.
- Wrong month/day order: if your browser’s locale uses MM/DD and you type DD/MM (or vice versa), the date picker can interpret January 5th as May 1st.
- Previous year left in the field: copying a product from last year and forgetting to update the year in the schedule dates. The cron checks whether the date has passed — and if the stored date is a year ago, WooCommerce may apply the price immediately or never, depending on how it handles expired schedules.
- Date picker dismissed without committing: some WooCommerce themes and page builders have date pickers that require clicking a separate “apply” or “confirm” button. If you select the date in the calendar but close the picker without confirming, the original date is saved, not the new one you selected.
How to confirm this is your cause
- Go to the product editor and look at the “Sale price dates from” field
- The stored date does not match what you intended to enter
- The offset is a full day, or several days, not just a few hours
How to fix it
Step 1: Verify the stored dates directly in the product editor
Open the product, look at the Sale price field and click “Schedule.” The current stored dates will appear. Verify that both the date and the time are exactly what you intended. Pay specific attention to AM/PM and confirm the year is correct.
Step 2: Re-enter the dates and save explicitly
If the dates are wrong, clear them and re-enter them carefully. After picking the date and time in the calendar, save the product and then re-open it to confirm the displayed dates match what you entered. This second open-and-check step catches cases where the date picker dismissed without committing.
Step 3: For bulk schedules, verify dates via a database check
If you set sale dates on many products at once via bulk edit, use WP-CLI to verify a sample of them: wp post meta get <product-id> _sale_price_dates_from returns the stored Unix timestamp. Convert it to a readable date to confirm it matches your intention. Catching a date entry error across 50 products early is much easier than manually correcting each one after the fact.
Prevention: a different scheduling approach
All six causes above are problems that exist because WooCommerce’s native sale scheduling was built around a specific, fairly simple architecture: store dates per product, run a daily background job to apply and remove prices, rely on WordPress’s pseudo-cron for the timing. That architecture is adequate for small, infrequent, manually-managed sales. It accumulates failure modes as stores scale.
There is a different approach worth understanding, because it solves several of these causes structurally rather than requiring ongoing workarounds.
A campaign-based discount system — where you define a promotion once at the campaign level, specify the products it covers, set the discount and schedule, and activate it — changes how several of these failure modes manifest:
- WP-Cron precision: Smart Cycle Discounts, for example, schedules precise per-campaign activation and deactivation events through WooCommerce’s Action Scheduler library (which itself builds on WP-Cron but adds retry logic, visibility, and dedicated per-event scheduling). Rather than a single daily sweep that checks every product, each campaign gets its own scheduled activation event, queued at the exact start timestamp. The precision is still subject to WP-Cron’s underlying traffic dependency — a real server cron job is still recommended — but the daily-sweep ceiling disappears. As of version 1.8.30, Smart Cycle Discounts migrated fully to Action Scheduler and actively clears any legacy WP-Cron entries from prior versions.
- Variable products: because Smart Cycle Discounts applies discounts at campaign time using WooCommerce’s price filters — not by writing sale prices to individual variation records — variable product coverage is handled at the campaign level. You specify which products the campaign covers, and all variations of those products receive the discount without needing per-variation sale price fields set.
- Timezone management: campaigns store a timezone per campaign and convert to UTC for storage, which keeps the scheduled time anchored to what you entered regardless of server timezone drift.
- Date entry errors: the campaign wizard validates start and end dates before saving, and the review step shows you the schedule in human-readable form before you launch — reducing the chance of a wrong-date mistake going unnoticed.
What a campaign-based approach does not change
Action Scheduler still depends on WP-Cron as its underlying trigger. A real server cron job is still recommended for any store that needs precise timing, regardless of which scheduling approach you use. The difference is that per-campaign events fire when their specific timestamp is reached (within the cron window), rather than waiting for a once-daily sweep to happen to check your product’s dates. For sites where WP-Cron is properly configured with a server cron, this means activation happens close to the scheduled time. For sites still on the default page-load trigger, both systems are equally subject to traffic gaps.
If you want to understand whether Smart Cycle Discounts’s approach to scheduling is the right fit for your store, the scheduling documentation covers how campaign dates, timezones, and activation work in practice.
Frequently asked questions
Why does my WooCommerce scheduled sale activate immediately when I re-save the product?
When you save a product in WooCommerce admin, the save hooks run an immediate check of the product’s sale dates against the current time — bypassing the scheduled cron entirely. If the start date has passed and the cron hasn’t run yet, re-saving triggers the activation for that one product on the spot. This is exactly what the woocommerce_scheduled_sales cron was supposed to do — your manual save just did it immediately for that product instead of waiting for the cron to get around to it.
My managed WordPress host (Kinsta, WP Engine) says they handle WP-Cron — why is my sale still late?
Managed hosts typically run WP-Cron every 15 minutes, which eliminates the traffic-dependency problem. But woocommerce_scheduled_sales is still a daily event — more frequent WP-Cron execution doesn’t change a daily job’s recurrence. You still face up to a 24-hour activation window unless you also reschedule woocommerce_scheduled_sales to run hourly. The host’s cron handling helps with everything else (subscription renewals, email sends, etc.) but doesn’t solve the WooCommerce sale precision problem specifically.
My sale price is correct in admin but wrong at checkout — is this a different problem?
Yes and no. If the product page shows the sale price but checkout charges a different amount, the most likely cause is a plugin conflict (Cause 5 above) — another plugin is modifying the cart price independently of the product display price. If the product page shows the full price but checkout is also showing the full price, check whether the cron has actually run (Cause 1) and whether the product’s sale price field is correctly set in admin. A price that is correct in admin but wrong at checkout specifically is almost always a plugin conflict.
How do I check whether DISABLE_WP_CRON is set on my site?
Open your wp-config.php file (in your WordPress root directory) and search for DISABLE_WP_CRON. If you find define('DISABLE_WP_CRON', true);, your page-load cron trigger is disabled. If you have not configured a real server cron to replace it, all scheduled tasks — including sale prices, subscription renewals, and email sends — have silently stopped running. Either set up the server cron replacement immediately or remove that line to re-enable the page-load trigger.
Does this problem affect all WooCommerce sale scheduling or just native sale prices?
It affects any workflow that depends on WooCommerce’s per-product “Sale price dates from / to” fields, because those all rely on the woocommerce_scheduled_sales cron event. Campaign-based plugins that schedule their own activation events (rather than relying on the WooCommerce daily sweep) have a different precision profile — their events fire when their specific timestamp is reached, rather than when the daily sweep happens to run. Both approaches still depend on WP-Cron’s underlying mechanism, but they fail differently.
What is the fastest way to get my stuck sale to activate right now?
Re-save each affected product manually in WooCommerce admin. This bypasses the cron and triggers an immediate sale price check. For a small number of products this is practical. For large product sets, use WooCommerce’s bulk edit to save multiple products at once — bulk saving also triggers the per-product save hooks. After the emergency activation, diagnose which of the six causes was responsible and fix the root issue before your next scheduled sale.
Six causes, six fixes — the short version
- WP-Cron timing: the
woocommerce_scheduled_salesjob runs daily by default and requires a visitor to trigger it. A real server cron (every 5 minutes) plus rescheduling the job to hourly solves both layers of this problem - Timezone mismatch: a consistent hours-off offset means your WordPress timezone setting and PHP server timezone are not aligned. Fix in Settings → General → Timezone, then re-enter all existing scheduled dates
- Caching: even when the database is updated, cached pages serve stale prices until cleared. Enable WooCommerce integration in your caching plugin, reduce product page TTL, and flush manually after any sale launch
- Variable products: sale prices must be set at the variation level, not just the parent product. Use WooCommerce’s bulk variation edit to push sale prices to all variations at once
- Plugin conflicts: multiple discount plugins hooking into the same WooCommerce price filters override each other. Identify the conflict by deactivating plugins one at a time, then consolidate to a single system
- Date entry errors: AM/PM confusion, wrong date format, and date picker not committed are the most common manual mistakes. Always re-open the product after saving to verify the stored dates match your intention
Run scheduled sales without juggling sale-price fields
Smart Cycle Discounts applies discounts at the campaign level — no per-product sale price editing, no per-variation bulk edits, and per-campaign Action Scheduler events instead of a once-daily sweep. Free to start.