| @@ -183,8 +183,14 @@ | ||
| 183 | 183 | // probe there entirely — no needless self-request. Health is the |
| 184 | 184 | // right place to pay for the probe when we DO run it (the admin |
| 185 | 185 | // bootstrap reads cache-only so it never blocks); the 5-minute |
| 186 | 186 | // transient still throttles repeat runs. (FBS-82142) |
| 187 | + // LiteSpeed never pays for the probe, even with the Static Fast | |
| 188 | + // Path opt-in on (#509): OLS can't stamp the HIT header, so the | |
| 189 | + // probe can't distinguish "static-served" from "PHP-served" | |
| 190 | + // there and no LiteSpeed card below consumes its verdict — the | |
| 191 | + // card reports the installed state instead. A probe nothing | |
| 192 | + // reads is just a needless loopback self-request per paint. | |
| 187 | 193 | $probe = ( Server::LITESPEED === $server_type ) |
| 188 | 194 | ? array( 'active' => false ) |
| 189 | 195 | : Cache::probe_static_rewrite( true ); |
| 190 | 196 | $is_active = (bool) ( $probe['active'] ?? false ); |
| @@ -229,27 +235,36 @@ | ||
| 229 | 235 | // about") instead of the WARN the block deserves. |
| 230 | 236 | $inconclusive = $inconclusive && ! $refused; |
| 231 | 237 | |
| 232 | 238 | if ( Server::NGINX === $server_type ) { |
| 233 | - if ( $is_active ) { | |
| 234 | - $nginx_detail = 'nginx is serving cache hits directly — PHP bypassed (~5-15ms TTFB).'; | |
| 235 | - } elseif ( 'mobile_separate' === $block_reason ) { | |
| 236 | - $nginx_detail = 'nginx detected, but the static rewrite is disabled because Separate Mobile Cache is on.' . $mobile_block; | |
| 237 | - } elseif ( 'skipped_nonce' === $block_reason ) { | |
| 238 | - $nginx_detail = self::nonce_skip_detail( $skip ); | |
| 239 | - } elseif ( $inconclusive ) { | |
| 240 | - $nginx_detail = sprintf( | |
| 241 | - 'Could not verify the static rewrite — the check itself did not complete, so this is not evidence that your config is wrong. If you have already pasted the snippet, it may well be working. Reason: %s', | |
| 242 | - $probe_reason | |
| 243 | - ); | |
| 244 | - } else { | |
| 245 | - $nginx_detail = 'nginx detected but not yet routing to the cache. Paste the snippet below into your site\'s server { } block, then reload nginx.'; | |
| 239 | + // One verdict for both surfaces: this panel and the Site | |
| 240 | + // Health test must answer from the SAME ordering, or they | |
| 241 | + // drift apart again — the whole point of #480. | |
| 242 | + $verdict = \XSpeed\Modules\Health\HealthModule::nginx_rewrite_verdict( $probe, $block_reason ); | |
| 243 | + switch ( $verdict ) { | |
| 244 | + case 'active': | |
| 245 | + $nginx_detail = 'nginx is serving cache hits directly — PHP bypassed (~5-15ms TTFB).'; | |
| 246 | + break; | |
| 247 | + case 'mobile_separate': | |
| 248 | + $nginx_detail = 'nginx detected, but the static rewrite is disabled because Separate Mobile Cache is on.' . $mobile_block; | |
| 249 | + break; | |
| 250 | + case 'skipped_nonce': | |
| 251 | + $nginx_detail = self::nonce_skip_detail( $skip ); | |
| 252 | + break; | |
| 253 | + case 'unverified': | |
| 254 | + $nginx_detail = sprintf( | |
| 255 | + 'Could not verify the static rewrite — the check itself did not complete, so this is not evidence that your config is wrong. If you have already pasted the snippet, it may well be working. Reason: %s', | |
| 256 | + $probe_reason | |
| 257 | + ); | |
| 258 | + break; | |
| 259 | + default: // 'required'. | |
| 260 | + $nginx_detail = 'nginx detected but not yet routing to the cache. Paste the snippet below into your site\'s server { } block, then reload nginx.'; | |
| 246 | 261 | } |
| 247 | 262 | |
| 248 | 263 | $out[] = array( |
| 249 | 264 | 'id' => 'static_rewrite_nginx', |
| 250 | 265 | // Inconclusive is INFO, not WARN — we have no finding to warn about. |
| 251 | - 'tone' => $is_active ? self::OK : ( $inconclusive ? self::INFO : self::WARN ), | |
| 266 | + 'tone' => 'active' === $verdict ? self::OK : ( 'unverified' === $verdict ? self::INFO : self::WARN ), | |
| 252 | 267 | 'label' => 'Static-file rewrite (nginx server config)', |
| 253 | 268 | 'detail' => $nginx_detail, |
| 254 | 269 | // Always ship the snippet — even when active, so the |
| 255 | 270 | // admin has it handy for re-pasting after a server |
| @@ -260,22 +275,66 @@ | ||
| 260 | 275 | // the Cache panel disagree on what to paste. |
| 261 | 276 | 'snippet' => Cache::full_nginx_server_block(), |
| 262 | 277 | ); |
| 263 | 278 | } elseif ( Server::LITESPEED === $server_type ) { |
| 264 | - // LiteSpeed intentionally does NOT use the .htaccess static | |
| 265 | - // rewrite: OpenLiteSpeed's .htaccess engine ignores | |
| 266 | - // mod_headers (so we can't stamp X-XSpeed-Cache: HIT) and has | |
| 267 | - // no per-rule access_log (so a static hit can't be counted). | |
| 268 | - // We route LiteSpeed hits through the PHP drop-in instead, so | |
| 269 | - // every hit is both visible (X-XSpeed-Cache: HIT) and counted | |
| 270 | - // in the hit-ratio — see Cache::static_rewrite_allowed(). This | |
| 271 | - // is the healthy, expected state on LiteSpeed, not a fallback. | |
| 272 | - $out[] = array( | |
| 273 | - 'id' => 'static_rewrite_litespeed', | |
| 274 | - 'tone' => self::OK, | |
| 275 | - 'label' => 'Cache serving (LiteSpeed)', | |
| 276 | - 'detail' => 'Cache hits are served by xSpeed\'s drop-in and tagged X-XSpeed-Cache: HIT — so every hit is visible and counted in your hit-ratio. (LiteSpeed\'s .htaccess can\'t add that header or log static hits, so xSpeed serves them itself for accurate reporting.)', | |
| 277 | - ); | |
| 279 | + // LiteSpeed defaults to the PHP drop-in — its .htaccess engine | |
| 280 | + // ignores mod_headers (no X-XSpeed-Cache stamp) and has no | |
| 281 | + // per-rule access_log, so a static hit would be invisible. The | |
| 282 | + // LiteSpeed Static Fast Path setting (#509) lets the user opt | |
| 283 | + // into web-server serving anyway; each state gets its own card | |
| 284 | + // so the trade the user made (or can make) is always stated. | |
| 285 | + if ( 'litespeed_dropin' === $block_reason ) { | |
| 286 | + // The intended default — healthy, not a fallback. | |
| 287 | + $out[] = array( | |
| 288 | + 'id' => 'static_rewrite_litespeed', | |
| 289 | + 'tone' => self::OK, | |
| 290 | + 'label' => 'Cache serving (LiteSpeed)', | |
| 291 | + 'detail' => 'Cache hits are served by xSpeed\'s drop-in and tagged X-XSpeed-Cache: HIT — so every hit is visible and counted in your hit-ratio. (LiteSpeed\'s .htaccess can\'t add that header or log static hits, so xSpeed serves them itself for accurate reporting.) Prefer raw speed over hit accounting? Turn on LiteSpeed Static Fast Path in Cache settings to serve hits straight from the web server with no PHP — how much that saves depends on how quickly PHP answers on this host.', | |
| 292 | + ); | |
| 293 | + } elseif ( 'mobile_separate' === $block_reason ) { | |
| 294 | + $out[] = array( | |
| 295 | + 'id' => 'static_rewrite_litespeed', | |
| 296 | + 'tone' => self::WARN, | |
| 297 | + 'label' => 'Static-file rewrite (LiteSpeed)', | |
| 298 | + 'detail' => 'LiteSpeed Static Fast Path is on, but the static rewrite is disabled because Separate Mobile Cache is on.' . $mobile_block, | |
| 299 | + ); | |
| 300 | + } elseif ( 'skipped_nonce' === $block_reason ) { | |
| 301 | + $out[] = array( | |
| 302 | + 'id' => 'static_rewrite_litespeed', | |
| 303 | + 'tone' => self::WARN, | |
| 304 | + 'label' => 'Static-file rewrite (LiteSpeed)', | |
| 305 | + 'detail' => self::nonce_skip_detail( $skip ), | |
| 306 | + ); | |
| 307 | + } elseif ( ! Cache::rewrite_installed() ) { | |
| 308 | + // Say WHY it is missing when we can tell. "Re-save to | |
| 309 | + // reinstall" on a read-only .htaccess is advice that | |
| 310 | + // cannot work — the same write that failed just fails | |
| 311 | + // again — and meanwhile the save itself succeeded | |
| 312 | + // silently, so this card is the only surface that can | |
| 313 | + // explain the state. (QA on #513) | |
| 314 | + $htaccess = ABSPATH . '.htaccess'; | |
| 315 | + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable -- Read-only diagnostic; mirrors install_rewrite()'s own pre-flight. | |
| 316 | + $writable = file_exists( $htaccess ) ? is_writable( $htaccess ) : is_writable( ABSPATH ); | |
| 317 | + $out[] = array( | |
| 318 | + 'id' => 'static_rewrite_litespeed', | |
| 319 | + 'tone' => self::WARN, | |
| 320 | + 'label' => 'Static-file rewrite (LiteSpeed)', | |
| 321 | + 'detail' => $writable | |
| 322 | + ? 'LiteSpeed Static Fast Path is on, but the block is missing from .htaccess. Re-save any Cache setting to reinstall it.' | |
| 323 | + : 'LiteSpeed Static Fast Path is on, but the block could not be written because .htaccess is not writable. Hits are still served (and counted) by the PHP drop-in. Make .htaccess writable and re-save any Cache setting, or turn the fast path off.', | |
| 324 | + ); | |
| 325 | + } else { | |
| 326 | + // Opt-in on and the block is installed. The probe can't | |
| 327 | + // confirm "active" the way it does elsewhere (LiteSpeed | |
| 328 | + // serves the probe file but stamps no header), so report | |
| 329 | + // the installed state and restate the accounting trade. | |
| 330 | + $out[] = array( | |
| 331 | + 'id' => 'static_rewrite_litespeed', | |
| 332 | + 'tone' => self::OK, | |
| 333 | + 'label' => 'Static-file rewrite (LiteSpeed)', | |
| 334 | + 'detail' => 'LiteSpeed Static Fast Path is on: the .htaccess block is installed and cache hits are served by the web server with no PHP. These responses carry no X-XSpeed-Cache header and are not counted in the hit ratio — that is the trade this setting makes. Turn it off in Cache settings to return every hit to the visible, counted PHP path.', | |
| 335 | + ); | |
| 336 | + } | |
| 278 | 337 | } elseif ( Server::APACHE === $server_type ) { |
| 279 | 338 | $installed = Cache::rewrite_installed(); |
| 280 | 339 | if ( $is_active ) { |
| 281 | 340 | $tone = self::OK; |
| @@ -343,10 +402,10 @@ | ||
| 343 | 402 | */ |
| 344 | 403 | $host_cache_path = Host_Page_Caches::nginx_helper_cache_path(); |
| 345 | 404 | if ( null !== $host_cache_path ) { |
| 346 | 405 | $detail = $cache_enabled |
| 347 | - ? 'Your server is running its own full-page cache in nginx (FastCGI), managed by the Nginx Helper plugin your host installed — so this site has TWO full-page caches stacked in front of it. xSpeed forwards every Purge All to the server layer, but the two expire on their own schedules (the server side is typically an hour), so a page can still be served from nginx after xSpeed has regenerated it. If edits keep looking stale, purge from your host\'s dashboard too, or turn xSpeed\'s page cache off and let the server layer do the work — it is the faster of the two, because it answers before PHP starts.' | |
| 348 | - : 'Your server is running a full-page cache in nginx (FastCGI), managed by the Nginx Helper plugin your host installed. xSpeed\'s own page cache is off, so this is the only full-page cache in front of the site — and it is the fastest kind, answering before PHP starts. Purge All in xSpeed still clears it.'; | |
| 406 | + ? 'Your server is running its own full-page cache in nginx (FastCGI), managed by the Nginx Helper plugin your host installed — so this site has TWO full-page caches stacked in front of it. xSpeed clears the server layer too on a Purge All, and on the changes that affect every page — settings, updates, themes, plugins, menus. It leaves the routine purge after a post or comment edit to Nginx Helper, which clears just the pages that changed, as long as Nginx Helper\'s own Enable Purge setting is on. With it off, xSpeed clears the server layer after those edits too. The two caches expire on their own schedules (the server side is typically an hour), so a page can still be served from nginx after xSpeed has regenerated it. If edits keep looking stale, purge from your host\'s dashboard too, or turn xSpeed\'s page cache off and let the server layer do the work — it is the faster of the two, because it answers before PHP starts.' | |
| 407 | + : 'Your server is running a full-page cache in nginx (FastCGI), managed by the Nginx Helper plugin your host installed. xSpeed\'s own page cache is off, so this is the only full-page cache in front of the site — and it is the fastest kind, answering before PHP starts. Purge All in xSpeed still clears it, as do settings changes and plugin, theme or core updates. The routine purge after editing a post or approving a comment is left to Nginx Helper, which clears just the pages that changed rather than all of them, as long as Nginx Helper\'s own Enable Purge setting is on. With it off, xSpeed clears the server layer after those edits too.'; | |
| 349 | 408 | |
| 350 | 409 | // Path prefix as a fingerprint for the WORDING only — never as a |
| 351 | 410 | // gate. Nginx Helper is not xCloud-only; other hosts and manual |
| 352 | 411 | // installs use it with a cache directory somewhere else entirely. |