The Problem
Got a panicked message from a client Tuesday evening. They run a furniture store on WooCommerce with around 240 variations on a single sofa product (fabric × colour × size). They edit a price in the variations tab, hit "Save changes", the spinner runs for about three seconds, and the page redraws with the original prices. No success toast. No error. Nothing in the JS console at first glance.
Refresh, retry, same result. Try a smaller product with twelve variations and it saves fine. Try Chrome incognito, still broken on the big product.
If your variation save is silently failing on a product with more than ~50 variations after upgrading to WooCommerce 9.7 or 9.7.1, you have hit one of three issues that all stack on top of each other. Here is the full diagnosis and the patch I shipped.
Why It Happens
WooCommerce 9.7 changed three things that interact badly with default PHP and host configs.
Change 1: The variation save endpoint moved to a chunked AJAX call. Pre-9.7, hitting "Save changes" submitted one giant form with every variation's prices, stock, dimensions, and meta. Now it batches variations in groups of 30 and POSTs them sequentially. That sounds better, but each batch carries the same _wpnonce from the page's initial render. If your batch count exceeds the new wc_variation_save_nonce_lifetime filter (default 6 hours, but some security plugins shorten it), the second or third batch returns a 403 that the frontend swallows.
Change 2: max_input_vars is checked per batch, not per request, but PHP still applies a global limit. Each variation produces about 14 form fields. A 240-variation product needs roughly 3,360 input variables across the full save. Default max_input_vars on most cPanel hosts is 1,000. WooCommerce now logs the truncation to wp-content/uploads/wc-logs/ instead of surfacing it in the admin, so you only see the failure if you read the logs.
Change 3: Object cache invalidation lags. WooCommerce 9.7 moved variation reads through a new WC_Cache_Helper::get_cache_prefix() group. If you run Redis or Memcached, the read after the write can return the old object from cache, making it look like the save failed even when it succeeded. The most confusing version of this bug.
So you can have a save that genuinely failed (nonce or input vars) or a save that succeeded but reads stale (cache). Both look identical to the user.
The Fix
Step 1: Confirm which failure mode you have. Check the WooCommerce logs first:
tail -f wp-content/uploads/wc-logs/fatal-errors-*.log
tail -f wp-content/uploads/wc-logs/wc-variation-save-*.log
Now hit "Save changes" in another tab. If you see lines like Variation save batch failed: nonce mismatch, it is the nonce. If you see Input variables exceeded, it is max_input_vars. If you see no log entries at all but the price reverts on reload, it is the cache.
Step 2: Bump PHP limits. Add to your wp-config.php BEFORE the /* That's all, stop editing! */ line, then restart PHP-FPM:
@ini_set( 'max_input_vars', '10000' );
@ini_set( 'post_max_size', '64M' );
@ini_set( 'upload_max_filesize', '64M' );
@ini_set( 'memory_limit', '512M' );
@ini_set( 'max_execution_time', '300' );
If your host blocks ini_set for security (Kinsta, WP Engine, SiteGround often do), put the same values in php.ini or .user.ini in the WordPress root, or open a support ticket. Do not skip this step. The defaults are below the threshold of any real product catalogue.
Step 3: Refresh nonces during long save operations. Drop this in a small must-use plugin at wp-content/mu-plugins/wc-variation-nonce-fix.php:
<?php
add_filter( 'woocommerce_save_variations_nonce_lifetime', function () {
return DAY_IN_SECONDS;
} );
add_action( 'admin_enqueue_scripts', function ( $hook ) {
if ( 'post.php' !== $hook ) {
return;
}
wp_add_inline_script(
'wc-admin-variation-meta-boxes',
'window.woocommerce_admin_meta_boxes_variations.save_changes_nonce = "'
. wp_create_nonce( 'save-variations' )
. '";'
);
} );
This extends the nonce lifetime to 24 hours and forces the variation meta-box JS to use a freshly minted nonce on every admin page load. After this, the chunked saves will no longer 403 on long catalogues.
Step 4: Clear the variation object cache after every save. Same MU plugin, append:
add_action( 'woocommerce_save_product_variation', function ( $variation_id, $i ) {
wp_cache_delete( $variation_id, 'posts' );
wp_cache_delete( 'product-' . $variation_id, 'products' );
if ( function_exists( 'wc_delete_product_transients' ) ) {
wc_delete_product_transients( $variation_id );
}
}, 10, 2 );
The posts and products cache groups are what WooCommerce 9.7 reads from on the redraw. Wiping them per-variation is cheap and removes the "saved but reverted" illusion.
Step 5: Avoid the bulk action trap. If you use the bulk variation actions (Set regular prices, Set sale prices, Increase regular price by N%), those still run as a single AJAX call, not chunked. They will silently fail above ~150 variations even with everything else fixed. Use the WooCommerce CLI instead:
wp wc product_variation update <variation_id> --regular_price=29.99 --user=admin
Or write a one-off script that loops wc_get_products and updates in batches of 50 with wp_cache_flush_group() in between. Anything is better than re-triggering the bulk dropdown.
The Lesson
The 9.7 chunked save is genuinely an improvement for catalogues that fit in PHP defaults. Anyone running real product depth is going to hit one of the three failure modes within a week. Bump max_input_vars, refresh the variation nonce, and clear product cache groups on every save. Confirm with the WooCommerce logs, never with the admin UI.
If your store breaks on every other WooCommerce minor release and you need someone to keep it stable, this is what I do — see my services, or read the WooCommerce mini cart not updating fix for a related AJAX silent-failure pattern.