Skip to content

Performance · WordPress

How to set Cache-Control on static assets in WordPress

With no Cache-Control, ETag or Last-Modified, browsers apply a heuristic and CDNs usually decline to cache at all, so the asset is fetched from your origin far more often than it needs to be. One header turns a repeat request into a local read.

The fix for WordPress

13x detects your framework from the response and hands you this version rather than the generic one — below 50% confidence it hedges and gives you the generic one instead.

WordPress serves static files through your web server, so the policy belongs there rather than in PHP.

Apache — add to .htaccess:

apache code
<IfModule mod_expires.c>
  ExpiresActive On
  ExpiresByType text/css "access plus 1 year"
  ExpiresByType application/javascript "access plus 1 year"
  ExpiresByType image/webp "access plus 1 year"
</IfModule>

Nginx:

nginx code
location ~* \.(css|js|woff2|png|jpg|webp|avif)$ {
  add_header Cache-Control "public, max-age=31536000, immutable";
}

WordPress appends ?ver= to enqueued styles and scripts, which is what makes a long max-age safe: bumping the version in wp_enqueue_style() changes the URL and defeats the cache deliberately.

On a different stack? The general version of this fix explains what 13x checks and why it matters, without assuming a framework.

Check your WordPress site

113 deterministic checks against your live URL, in about 30 seconds. Framework detected from the response, so every fix comes back in the form your stack actually uses.

No signup. Results in 30 seconds.

The same fix, other stacks