wpsc_get_accept_header() in wp-cache-phase2.php uses a naive str_contains() check to detect JSON requests. It does not parse Accept header quality values (q=) as defined in RFC 7231 §5.3.2. As a result, any request where application/json appears anywhere in the Accept header — even at low priority — is misclassified as a JSON request, preventing the cached file from being served and triggering a cache rebuild.
This is reproducible with New Relic Synthetics, which sends:
Accept: text/html,application/xhtml+xml,application/json;q=0.9,application/javascript;q=0.9,text/javascript;q=0.9,application/xml;q=0.9,text/plain;q=0.8,*/*;q=0.7
This is a valid HTML-preferring Accept header — text/html has implicit q=1.0 (highest priority), and application/json is explicitly deprioritised at q=0.9. However, WPSC treats the entire request as a JSON request and refuses to serve the cached file.
In wp-cache-phase2.php, wpsc_get_accept_header() (around line 533):
$accept = isset( $_SERVER['HTTP_ACCEPT'] ) ? strtolower( filter_var( $_SERVER['HTTP_ACCEPT'] ) ) : '';
foreach ( $accept_headers as $header ) {
if ( str_contains( $accept, $header ) ) {
$accept = 'application/json';
}
}
if ( $accept !== 'application/json' ) {
$accept = 'text/html';
}
The str_contains() check finds application/json anywhere in the raw Accept string without considering q-values. A request from New Relic Synthetics with application/json;q=0.9 (lower priority than text/html) is classified as a JSON request.
This misclassification has two downstream effects:
wp_cache_serve_cache_file() returns false immediately because wpsc_get_accept_header() !== 'text/html', so the cached file is never served.
get_wp_cache_key() appends -application/json to the cache key, meaning NR Synthetics requests generate and populate a separate cache bucket, triggering a fresh page build on every synthetic check.
Steps to reproduce
- Enable WP Super Cache with Expert/SuperCache mode
- Enabled Debug logging
- Create a postman request that uses the above accept header
- Observe in the debug log:
wp_cache_serve_cache_file: visitor does not accept text/html. Not serving cached file. repeating on every synthetic check
- Observe cache files being rebuilt on every NR Synthetics request
Expected Bahviour
When text/html is the highest q-value type in the Accept header, WPSC should classify the request as text/html and serve the cached file normally. The function should parse q-values and determine the highest-priority accepted type before comparing against the known JSON types.
Workarounds
The wpsc_accept_headers filter cannot easily fix this because wpsc_get_accept_header() caches its result via static $accept = 'N/A' and is called very early in the PHP request lifecycle — before functions.php is loaded — so any filter hooked from a theme or standard plugin loads too late.
A short-term workaround for those using WP Super Cache in PHP mode (not .htaccess/Expert mode) is to add via an mu-plugin:
<?php
// mu-plugins/fix-nr-accept-header.php
add_filter( 'wpsc_accept_headers', function( $headers ) {
if ( ! empty( $_SERVER['HTTP_X_NEWRELIC_SYNTHETICS'] ) ) {
return [];
}
return $headers;
} );
However this is NR-specific and does not fix the underlying q-value parsing issue that would affect other monitoring tools (Datadog Synthetics, Pingdom, UptimeRobot, etc.) with similar broad Accept headers.
Suggested Fix
wpsc_get_accept_header() should parse the Accept header properly, sorting types by q-value before comparing.
wpsc_get_accept_header()inwp-cache-phase2.phpuses a naivestr_contains()check to detect JSON requests. It does not parse Accept header quality values (q=) as defined in RFC 7231 §5.3.2. As a result, any request whereapplication/jsonappears anywhere in the Accept header — even at low priority — is misclassified as a JSON request, preventing the cached file from being served and triggering a cache rebuild.This is reproducible with New Relic Synthetics, which sends:
This is a valid HTML-preferring Accept header —
text/htmlhas implicitq=1.0(highest priority), andapplication/jsonis explicitly deprioritised atq=0.9. However, WPSC treats the entire request as a JSON request and refuses to serve the cached file.In
wp-cache-phase2.php,wpsc_get_accept_header()(around line 533):The
str_contains()check findsapplication/jsonanywhere in the raw Accept string without considering q-values. A request from New Relic Synthetics withapplication/json;q=0.9(lower priority than text/html) is classified as a JSON request.This misclassification has two downstream effects:
wp_cache_serve_cache_file()returnsfalseimmediately becausewpsc_get_accept_header() !== 'text/html', so the cached file is never served.get_wp_cache_key()appends-application/jsonto the cache key, meaning NR Synthetics requests generate and populate a separate cache bucket, triggering a fresh page build on every synthetic check.Steps to reproduce
wp_cache_serve_cache_file: visitor does not accept text/html. Not serving cached file. repeating on every synthetic checkExpected Bahviour
When
text/htmlis the highest q-value type in the Accept header, WPSC should classify the request astext/htmland serve the cached file normally. The function should parse q-values and determine the highest-priority accepted type before comparing against the known JSON types.Workarounds
The
wpsc_accept_headersfilter cannot easily fix this becausewpsc_get_accept_header()caches its result viastatic $accept = 'N/A'and is called very early in the PHP request lifecycle — beforefunctions.phpis loaded — so any filter hooked from a theme or standard plugin loads too late.A short-term workaround for those using WP Super Cache in PHP mode (not .htaccess/Expert mode) is to add via an mu-plugin:
However this is NR-specific and does not fix the underlying q-value parsing issue that would affect other monitoring tools (Datadog Synthetics, Pingdom, UptimeRobot, etc.) with similar broad Accept headers.
Suggested Fix
wpsc_get_accept_header()should parse the Accept header properly, sorting types by q-value before comparing.