| @@ -1,9 +1,10 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | /** |
| 3 | 3 | * Browser_Cache — writes/removes browser-cache directives in the site |
| 4 | 4 | * root .htaccess so static assets get long Cache-Control + Expires |
| 5 | - * headers, and serves an nginx snippet for non-Apache hosts. | |
| 5 | + * headers, and serves an nginx snippet for non-Apache hosts (that one | |
| 6 | + * sends Cache-Control only — see nginx_snippet()). | |
| 6 | 7 | * |
| 7 | 8 | * Same shape as Gzip: marker block, insert_with_markers, snippet |
| 8 | 9 | * fallback. Independent toggle so users can enable browser caching |
| 9 | 10 | * without compression and vice versa. |
| @@ -93,12 +94,8 @@ | ||
| 93 | 94 | ); |
| 94 | 95 | } |
| 95 | 96 | |
| 96 | 97 | /** |
| 97 | - * nginx snippet — uses `expires` directive (the canonical nginx way) | |
| 98 | - * plus an `add_header` line for the immutable flag. | |
| 99 | - */ | |
| 100 | - /** | |
| 101 | 98 | * Probe whether long-lived caching headers are actually reaching the |
| 102 | 99 | * browser. Picks a recognisable static asset (anything in |
| 103 | 100 | * `wp-includes/css/` is always served on a WP install) and HEADs it. |
| 104 | 101 | * Cached in a transient so this never adds latency to the dashboard. |
| @@ -208,20 +205,57 @@ | ||
| 208 | 205 | } |
| 209 | 206 | return false; |
| 210 | 207 | } |
| 211 | 208 | |
| 209 | + /** | |
| 210 | + * nginx snippet — one `add_header Cache-Control` per location, with | |
| 211 | + * `expires off;` pinned in front of it. | |
| 212 | + * | |
| 213 | + * nginx's `expires Ns;` emits its own `Cache-Control: max-age=N` (plus | |
| 214 | + * `Expires`), and `add_header` appends rather than replaces, so pairing | |
| 215 | + * the two — as this block used to — put two Cache-Control fields on | |
| 216 | + * every static asset it matched, WP core's own included (issue #259). | |
| 217 | + * `expires` can't say `immutable` and `immutable` is the point, so | |
| 218 | + * `add_header` is the one that stays. | |
| 219 | + * | |
| 220 | + * `off` rather than simply dropping the directive: `expires` is | |
| 221 | + * inherited from `server {}`, so a host template that sets one there | |
| 222 | + * would recreate the duplicate inside this location. | |
| 223 | + * | |
| 224 | + * Losing `Expires:` on nginx costs nothing — `max-age` outranks it in | |
| 225 | + * any HTTP/1.1 cache, and Apache still emits both via mod_expires. | |
| 226 | + * | |
| 227 | + * Deliberately no `always`. Without it nginx applies `add_header` only | |
| 228 | + * to the statuses its header filter treats as safe — 200, 201, 204, | |
| 229 | + * 206, 301, 302, 303, 304, 307, 308 — which is the coverage we want, | |
| 230 | + * and the same coverage Apache gives: `Header set` in apache_rules() | |
| 231 | + * runs under the default `onsuccess` condition, so it is off on error | |
| 232 | + * responses too. `always` would put `max-age=31536000, immutable` on a | |
| 233 | + * 404, and minified asset URLs are deterministic (Minifier keys them on | |
| 234 | + * path + mtime), so a file that 404s in the window after a purge comes | |
| 235 | + * back at the URL a client has already cached the 404 for — for a year, | |
| 236 | + * with no revalidation. | |
| 237 | + */ | |
| 212 | 238 | public static function nginx_snippet( array $opts = array() ): string { |
| 213 | 239 | $asset = (int) ( $opts['asset_ttl'] ?? self::DEFAULT_ASSET_TTL ); |
| 214 | 240 | $html = (int) ( $opts['html_ttl'] ?? self::DEFAULT_HTML_TTL ); |
| 241 | + // Same guard as apache_rules(): a negative TTL would otherwise | |
| 242 | + // reach the vhost as `max-age=-1`. | |
| 243 | + if ( $asset < 0 ) { | |
| 244 | + $asset = self::DEFAULT_ASSET_TTL; | |
| 245 | + } | |
| 246 | + if ( $html < 0 ) { | |
| 247 | + $html = self::DEFAULT_HTML_TTL; | |
| 248 | + } | |
| 215 | 249 | return implode( |
| 216 | 250 | "\n", |
| 217 | 251 | array( |
| 218 | 252 | 'location ~* \.(css|js|jpg|jpeg|png|gif|webp|avif|svg|ico|woff2|woff|ttf|otf|eot|mp4|webm|mp3|ogg)$ {', |
| 219 | - ' expires ' . $asset . 's;', | |
| 253 | + ' expires off;', | |
| 220 | 254 | ' add_header Cache-Control "public, max-age=' . $asset . ', immutable";', |
| 221 | 255 | '}', |
| 222 | 256 | 'location ~* \.html$ {', |
| 223 | - ' expires ' . $html . 's;', | |
| 257 | + ' expires off;', | |
| 224 | 258 | ' add_header Cache-Control "public, max-age=' . $html . '";', |
| 225 | 259 | '}', |
| 226 | 260 | ) |
| 227 | 261 | ); |