Configuring cache expiration in your .htaccess file tells the browser how long to store resources like images, CSS, and JavaScript before re-downloading them. The result is immediate: repeat visits load much faster and bandwidth usage drops.
This guide shows you exactly how to do it on an Apache server, with copy-paste ready examples.
Why Browser Cache Matters
When someone visits your site for the first time, the browser downloads everything: HTML, stylesheets, scripts, and images. On the second visit it could reuse those files from its local cache — if your server tells it that's safe to do.
Without cache headers, the browser assumes everything may have changed and re-downloads each resource. That's the behavior we're fixing here.
Analysis tools like GTmetrix and PageSpeed Insights penalize missing cache headers with warnings like "Serve static assets with an efficient cache policy." For a broader look at performance metrics, check out our web performance blog.
Prerequisites
- Access to your domain's
.htaccessfile (site root orpublic_htmlfolder). - An Apache server with mod_expires and mod_headers enabled. Most shared hosting environments have both active by default.
- Permission to edit files via cPanel File Manager, FTP, or SSH.
The .htaccess Block for Cache Expiration
Copy this block and paste it at the end of your existing .htaccess file (don't overwrite any directives you already have):
# ── Browser Cache Expiration ────────────────────────────────────
<IfModule mod_expires.c>
ExpiresActive On
# HTML documents
ExpiresByType text/html "access plus 0 seconds"
# Stylesheets
ExpiresByType text/css "access plus 1 year"
# JavaScript
ExpiresByType application/javascript "access plus 1 year"
ExpiresByType text/javascript "access plus 1 year"
# Images
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/gif "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
ExpiresByType image/svg+xml "access plus 1 year"
ExpiresByType image/x-icon "access plus 1 year"
# Web fonts
ExpiresByType font/woff2 "access plus 1 year"
ExpiresByType font/woff "access plus 1 year"
ExpiresByType application/font-woff "access plus 1 year"
# PDFs and documents
ExpiresByType application/pdf "access plus 1 month"
# JSON / XML (feeds, APIs)
ExpiresByType application/json "access plus 0 seconds"
ExpiresByType application/xml "access plus 0 seconds"
# Default for unlisted types
ExpiresDefault "access plus 1 month"
</IfModule>
# ── Cache-Control (complements mod_expires) ─────────────────────
<IfModule mod_headers.c>
<FilesMatch "\.(ico|jpg|jpeg|png|gif|webp|svg|css|js|woff|woff2)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
<FilesMatch "\.(html|htm|php)$">
Header set Cache-Control "no-cache, must-revalidate"
</FilesMatch>
</IfModule>
# ───────────────────────────────────────────────────────────────
Understanding the Key Directives
ExpiresActive On
Activates the mod_expires module. Without this line, everything else is ignored.
ExpiresByType
Assigns an expiration time per MIME type. The value "access plus 1 year" is the practical maximum recommended for static resources that don't change (or that change with a new file name).
Cache-Control: immutable
Tells the browser the file will not change during its cache lifetime. It prevents even conditional revalidation requests (If-None-Match). Only use it on versioned assets (CSS and JS with a hash in the filename).
no-cache vs no-store
| Value | Behavior | When to use it |
|---|---|---|
no-cache |
Stores in cache but validates with the server before using | HTML, dynamic pages |
no-store |
Never stores anything in cache | Sensitive data (cart, profile) |
public |
Cacheable by any intermediary (CDN, proxies) | Static resources |
private |
Only cacheable by the user's own browser | Personalized responses |
How to Verify the Cache Is Working
After saving your .htaccess, confirm that the headers are being sent:
- Open browser DevTools (F12).
- Go to the Network tab and hard-reload the page (Ctrl+Shift+R to bypass cache).
- Click on any static resource (image, CSS, JS).
- In the Response Headers section, verify that
Cache-Controland/orExpiresappear with the values you configured.
You can also run a new test in GTmetrix or PageSpeed Insights: the "Leverage browser caching" or "Serve static assets with an efficient cache policy" warning should disappear or shrink significantly.
If you need a full review of your Apache configuration and cache policy, the team at elenlace.com can audit and optimize it as part of a comprehensive performance plan.
Important Considerations
- Version your static files. If you cache CSS and JS for one year and then modify them, users will see the old version until the cache expires. Solution: add a version suffix to the filename (
style.v2.css) or a query string (style.css?v=2). - Don't cache HTML. HTML pages must always be fresh to reflect changes in content and prices. That's why the block above uses
"access plus 0 seconds"fortext/html. - Session and cart data. Use
no-storefor any response containing personal user information. - WordPress and other CMSs. Many cache plugins (WP Rocket, W3 Total Cache) already add these directives automatically. Avoid duplicating them to prevent conflicts.
Key Takeaways
- Setting cache expiration in
.htaccessis one of the simplest, highest-impact performance optimizations available. - Use
mod_expiresto assign times by MIME type andmod_headersfor theCache-Controlheader. - One year is the maximum recommended expiration for static assets; pair it with file versioning.
- Never cache dynamic HTML, session data, or real-time API responses.
- Verify the result with browser DevTools or a speed analysis tool.
Want someone to implement and validate this for you? The specialists at elenlace.com configure cache policy, asset versioning, and the rest of your Apache optimizations so your site passes the performance audit once and for all.
FAQ
Can I break my site by editing .htaccess?
Yes, a syntax error can trigger an HTTP 500 error. Always back up the file before editing it. If the site goes down, restore the backup and review the syntax line by line. The block above has been tested and is production-safe when pasted as-is.
What if mod_expires isn't available on my hosting?
You can use just the mod_headers block with Cache-Control, which is more widely supported. If neither module is available, contact your hosting provider to enable them.
Does browser caching affect SEO?
Yes, positively. Google includes page speed and Core Web Vitals as ranking factors. A correct cache policy reduces load time on repeat visits and improves metrics like LCP, which can benefit your search rankings.
How long does it take for the change to take effect?
The server-side change is immediate. However, users who already have resources cached under the previous policy will continue using that cache until it expires. New visitors and users who clear their cache will see the new headers right away.
Compare providers
Other providers and guides worth comparing: