The Problem
A B2B catalogue client of mine ships orders out of NetSuite through a nightly sync that hits the WooCommerce REST API. The morning after their managed host rolled WordPress 6.9, every cron job failed with the same response:
{
"code": "woocommerce_rest_authentication_error",
"message": "Consumer key is missing.",
"data": { "status": 401 }
}
Same consumer key, same consumer secret, same Authorization header. Nothing about the integration had changed. If your WooCommerce REST API started returning 401 Unauthorized with "Consumer key is missing" after WordPress 6.9, or after a host pushed the May 2026 PHP-FPM defaults, you are looking at the same problem I fixed last week on three different sites.
Why It Happens
WordPress 6.9 tightened the authorization header pipeline. The change in wp-includes/rest-api.php now relies on apache_request_headers() (or its FastCGI fallback) returning a populated Authorization header before the REST request is dispatched. WooCommerce's authentication class reads from the same source.
Three things break that chain after 6.9, and I see them in this order on real client sites:
- FastCGI strips the header. Most Apache and Nginx FPM configs do not pass
Authorizationto PHP by default. WordPress 6.8 had a silent fallback that re-readREDIRECT_HTTP_AUTHORIZATION. The 6.9 hardening removed that fallback in some code paths because it was leaking the header to logging plugins. - Application Passwords collide with WooCommerce auth. Application Passwords were the recommended replacement for Basic Auth on the WP REST API, but the WooCommerce auth class still expects
ck_andcs_prefixes. If your integration sends a 6.9 application password against a/wp-json/wc/v3endpoint, the WooCommerce class rejects it before WordPress can fall back. - Edge workers normalise the header. Hosts that proxy through Cloudflare APO, BunnyCDN, or Cloudways Breeze occasionally drop
Authorizationon routes that ship through their cache layer. WooCommerce's REST namespace is supposed to be uncacheable, but some configs normalise the path before checking.
The error string is identical for all three causes, which is why the search results for "Consumer key is missing 6.9" are so noisy.
The Fix
Step 1: Confirm the header is reaching PHP. Drop this into a mu-plugin and hit any REST endpoint with your normal credentials:
add_action( 'rest_api_init', function () {
error_log( 'AUTH: ' . ( $_SERVER['HTTP_AUTHORIZATION'] ?? 'MISSING' ) );
error_log( 'REDIRECT_AUTH: ' . ( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ?? 'MISSING' ) );
}, 1 );
If both log MISSING, the header is being stripped at the web-server layer. That is the most common case I see on managed hosting.
Step 2: Pass the header through Apache or Nginx. For Apache with mod_rewrite, add this to .htaccess above the WordPress block:
RewriteEngine On
RewriteCond %{HTTP:Authorization} ^(.*)
RewriteRule ^(.*) - [E=HTTP_AUTHORIZATION:%1]
For Nginx in your FastCGI block:
fastcgi_pass_header Authorization;
fastcgi_param HTTP_AUTHORIZATION $http_authorization;
Reload the web server and retry the REST call. The mu-plugin log from Step 1 should now show a Basic prefix on HTTP_AUTHORIZATION. If it does, the request itself is fine and you can move on.
Step 3: Restore the fallback inside WordPress. If you cannot edit the web-server config (shared hosts, some Kinsta plans), recreate the 6.8 fallback in a mu-plugin:
add_action( 'plugins_loaded', function () {
if ( ! empty( $_SERVER['HTTP_AUTHORIZATION'] ) ) {
return;
}
foreach ( [ 'REDIRECT_HTTP_AUTHORIZATION', 'HTTP_X_AUTHORIZATION' ] as $key ) {
if ( ! empty( $_SERVER[ $key ] ) ) {
$_SERVER['HTTP_AUTHORIZATION'] = $_SERVER[ $key ];
return;
}
}
}, 0 );
This runs before WooCommerce's REST auth boots, so by the time WC_REST_Authentication::perform_basic_authentication() reads the header, the value is back.
Step 4: Stop using Application Passwords against WooCommerce endpoints. If your integration was migrated to Application Passwords, switch the WooCommerce endpoints back to consumer_key/consumer_secret query string auth or to proper Basic Auth with the ck_/cs_ prefixes intact. The official WooCommerce REST API authentication docs cover the two supported flows. Application Passwords stay fine for core WP endpoints (/wp-json/wp/v2/...) but not for the wc/v3 namespace.
Step 5: Bypass the CDN on /wp-json/wc/. In Cloudflare, add a Bypass Cache rule for URI Path matches *wp-json*. In BunnyCDN, exclude the path from the pull zone. In Cloudways Breeze or other origin-side plugins, add the WooCommerce REST namespace to the cache exclusions list. WooCommerce sends correct cache-control headers but some CDNs still normalise the Authorization header into the cache key.
Verify and Lock In
Run the same integration call you started with:
curl -i -u ck_xxx:cs_xxx https://example.com/wp-json/wc/v3/orders?per_page=1
You should see HTTP/1.1 200 OK and a JSON array. If you still get 401, the mu-plugin log from Step 1 tells you which layer is still dropping the header. Most days that points straight at the web-server config, which is the only place an Authorization header can disappear silently.
This breaks a lot of integrations quietly because the credentials still look correct, there is no expired token to refresh, no scope mismatch, no permission error. It is purely a transport-layer regression introduced by the 6.9 hardening, and most monitoring tools just record the 401 without flagging the upstream cause.
If your WooCommerce store has integrations going dark after WordPress updates and you want someone who fixes the transport without rolling back the release, this is the kind of work I do — see my services. For a related WooCommerce silent-failure pattern, look at WooCommerce Stripe webhook signature mismatch.