Every time a repeat visitor opens your site, their browser re-downloads your logo, CSS, and JavaScript — even though nothing changed. That wastes bandwidth and slows pages for your most likely buyers.
The fix takes five minutes, costs nothing, and needs no plugin: you tell the browser how long to keep each file. Here is how to enable browser caching in WordPress without a plugin — copy-paste code for Apache and Nginx, plus a simple way to prove it worked.
What Is Browser Caching in WordPress?
Browser caching is a deal between your server and your visitor’s browser: on the first visit, the browser downloads your images, stylesheets, scripts, and fonts and saves them. Next time, it reuses those copies — so the page loads far faster.
The key takeaway: browser caching only helps repeat visitors — but they are your most valuable traffic. Two headers control it: Cache-Control, the modern standard using max-age in seconds (max-age=31536000 means one year), and Expires, the older method. When both are present, browsers follow Cache-Control first; when neither is sent, the browser guesses — usually a very short lifetime, which is why Google flags your site.
Why PageSpeed Flags Your Site for Caching
PageSpeed Insights may show “Serve static assets with an efficient cache policy” (newer Lighthouse: “Use efficient cache lifetimes”; older: “Leverage browser caching”). It means PageSpeed found static files your browser may keep for less than 30 days. Google wants at least 30 days; its standard is a year (max-age=31536000) for files that rarely change. DebugBear’s guide to the audit explains exactly how Lighthouse measures it.
One honest note: this will not change your PageSpeed score much on a first visit, since PageSpeed loads with a cold cache — the payoff is real visitors, whose repeat visits load faster. It pairs with a full speed overhaul — see our WordPress speed optimization guide.
Before You Touch Anything: Back Up .htaccess
Do not skip this step. A single typo in .htaccess can take your whole site down with a 500 error — restoring the backup fixes it. In your file manager or FTP client, open your WordPress root folder (the one holding wp-config.php) and download .htaccess — enable “Show Hidden Files” if you cannot see it.
How to Enable Browser Caching in WordPress Without a Plugin on Apache (.htaccess Code)
Most shared WordPress hosting runs on Apache (or LiteSpeed, which reads the same rules). To set expires headers in WordPress on Apache, paste this code near the top of .htaccess, above the # BEGIN WordPress section so updates never overwrite it:
## EXPIRES HEADER CACHING ##
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/jpg "access 1 year"
ExpiresByType image/jpeg "access 1 year"
ExpiresByType image/gif "access 1 year"
ExpiresByType image/png "access 1 year"
ExpiresByType image/webp "access 1 year"
ExpiresByType image/svg+xml "access 1 year"
ExpiresByType text/css "access 1 month"
ExpiresByType text/html "access 1 hour"
ExpiresByType application/javascript "access 1 month"
ExpiresByType font/woff2 "access 1 year"
ExpiresDefault "access 1 month"
</IfModule>
## EXPIRES HEADER CACHING ##
The <IfModule mod_expires.c> wrapper is your safety net: if your host has not enabled mod_expires, the block is quietly ignored instead of crashing your site — WPBeginner’s expires-headers guide explains the same method step by step. For tighter control, add a Cache-Control block below it:
## CACHE-CONTROL HEADERS ##
<IfModule mod_headers.c>
<FilesMatch "\.(jpg|jpeg|png|gif|webp|svg|ico|woff|woff2|ttf)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
<FilesMatch "\.(css|js)$">
Header set Cache-Control "public, max-age=2592000"
</FilesMatch>
</IfModule>
## CACHE-CONTROL HEADERS ##
Only use immutable for files whose names change when the content changes (like style.css?ver=6.8). WordPress already version-numbers enqueued scripts and styles, so this is safe for most sites. Save the file, then clear any cache.
How to Enable Browser Caching in WordPress Without a Plugin on Nginx
Nginx ignores .htaccess completely. If your host runs Nginx — common on VPS and managed hosting — pasting code into .htaccess does nothing; Kinsta’s guide to expires headers shows the Nginx approach in detail. Add this inside your site’s server { ... } block:
location ~* \.(jpg|jpeg|png|gif|webp|svg|ico|css|js|woff|woff2|ttf)$ {
expires 365d;
add_header Cache-Control "public, immutable";
}
location ~* /(wp-admin|wp-login\.php) {
expires -1;
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0";
}
One expires directive writes both headers for you. Test and reload with sudo nginx -t then sudo systemctl reload nginx. On managed hosting you probably cannot edit the Nginx config yourself — and that is normal. Ask support to extend the static asset cache lifetime to one year — most hosts will apply it for free.
How Long Should Browser Cache Last? Recommended Durations by File Type
| File type | Recommended cache time | Why |
|---|---|---|
| Images (JPG, PNG, WebP, SVG) | 1 year | Rarely change |
| Fonts (WOFF, WOFF2, TTF) | 1 year | Never change |
| CSS and JavaScript | 1 month | Versioned filenames |
| HTML pages | 1 hour or less | Must stay fresh |
| Admin pages and login | No cache | Always fresh and private |
The rule of thumb: the less often a file changes, the longer you cache it. After changing a logo or stylesheet, hard-refresh (Ctrl + Shift + R / Cmd + Shift + R) if you still see the old version — that is the cache working, not a bug.
How to Fix “Serve Static Assets With an Efficient Cache Policy” in PageSpeed
Once your headers are in place, the warning should clear on the next test — if not:
- Check which files are still listed. Files on your own domain are yours to fix; files from another host (Google Fonts, YouTube, analytics) carry that vendor’s headers — you cannot change them.
- Clear every cache layer — plugin, server cache, and CDN — then retest.
- Look for short lifetimes set by plugins — usually a file with a
max-ageof a few hours. - Run a fresh PageSpeed test — results are cached briefly, so wait a minute first.
If only third-party scripts remain, you have done your part — that warning cannot be fully cleared without removing those scripts. Also see our guides on reducing image size and fixing render-blocking resources.
How to Verify Browser Caching Is Working (Chrome DevTools)
Never trust that the code worked — verify it in under a minute. In Chrome, press F12, click the Network tab, and reload. Click any image, CSS, or JS file: under Response Headers, look for cache-control: public, max-age=31536000 and a future expires date. If you see those headers, browser caching is live. Reload and watch the Size column: cached files show as “(disk cache)” with near-zero load time.
What If Your Host Does Not Use .htaccess? (Nginx-Only Hosts and Cloudflare)
If your host runs Nginx and blocks server-config edits, you still have options. Ask host support first — one ticket line (“please set a one-year cache lifetime on static assets”) solves it on most managed hosts. Or use Cloudflare’s Browser Cache TTL: under Caching > Configuration, set it to Respect Existing Headers — or to 1 year to override short lifetimes. (Cloudflare’s default is just 4 hours when your server sends no lifetime.)
FAQ: Browser Caching in WordPress
Can I enable browser caching without a plugin?
Yes. On Apache or LiteSpeed, add the mod_expires and mod_headers blocks from this guide to .htaccess. On Nginx, add expires rules to your server block.
How do I fix the “leverage browser caching” warning?
Set Cache-Control and Expires headers with a lifetime of at least 30 days — one year (max-age=31536000) is the standard for images, fonts, CSS, and JavaScript. Clear all cache layers and retest. Third-party files cannot be fixed from your side.
What is browser caching in WordPress?
It is when a visitor’s browser saves copies of your site’s static files on their device, so repeat visits load them from local storage instead of downloading them again. Your web server controls it with Cache-Control and Expires headers.
How long should browser cache last?
Images and fonts: one year. CSS and JavaScript: about one month (WordPress version numbers push updates through). HTML: an hour or less. Never cache admin or login pages.
Conclusion
Learning how to enable browser caching in WordPress without a plugin is one of the highest-return speed fixes you will ever make: five minutes of code, zero cost, and every repeat visit gets faster. Images and fonts to a year, CSS and JavaScript to a month, HTML to an hour — back up .htaccess first.
Your next step takes two minutes: run your homepage through PageSpeed Insights and check whether the caching warning is gone. If it is, your repeat visitors are already loading faster — and if you want the rest of your speed audit handled for you, see our WordPress speed services or get in touch.