In a .htaccess
the expiration headers are set (let's call it the primary htaccess file). Then in a specific folder of this I have another .htaccess
file because I would like to have an exception and have different expiration headers for files in that subfolder. This subfolder contains test.min.css
.
However, when testing it, the file test.min.css
still has the expiration header of max-age=9072000
, I assume coming from the .htaccess
file a folder up.
What am I doing wrong in the .htaccess
file in the subfolder?
The primary .htaccess
file is as follows (it's a Wordpress site):
#Expires headers configuration added by BREEZE WP CACHE plugin
<IfModule mod_env.c>
SetEnv BREEZE_BROWSER_CACHE_ON 1
</IfModule>
<IfModule mod_expires.c>
ExpiresActive On
ExpiresDefault "access plus 1 month"
# Assets
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
ExpiresByType application/x-javascript "access plus 1 month"
ExpiresByType text/javascript "access plus 1 month"
# Media assets
ExpiresByType audio/ogg "access plus 1 year"
ExpiresByType image/bmp "access plus 1 year"
ExpiresByType image/gif "access plus 1 year"
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/svg xml "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
ExpiresByType video/mp4 "access plus 1 year"
ExpiresByType video/ogg "access plus 1 year"
ExpiresByType video/webm "access plus 1 year"
# Font assets
ExpiresByType application/vnd.ms-fontobject "access plus 1 year"
ExpiresByType font/eot "access plus 1 year"
ExpiresByType font/opentype "access plus 1 year"
ExpiresByType application/x-font-ttf "access plus 1 year"
ExpiresByType application/font-woff "access plus 1 year"
ExpiresByType application/x-font-woff "access plus 1 year"
ExpiresByType font/woff "access plus 1 year"
ExpiresByType application/font-woff2 "access plus 1 year"
# Data interchange
ExpiresByType application/xml "access plus 0 seconds"
ExpiresByType application/json "access plus 0 seconds"
ExpiresByType application/ld json "access plus 0 seconds"
ExpiresByType application/schema json "access plus 0 seconds"
ExpiresByType application/vnd.geo json "access plus 0 seconds"
ExpiresByType text/xml "access plus 0 seconds"
ExpiresByType application/rss xml "access plus 1 hour"
ExpiresByType application/rdf xml "access plus 1 hour"
ExpiresByType application/atom xml "access plus 1 hour"
# Manifest files
ExpiresByType application/manifest json "access plus 1 week"
ExpiresByType application/x-web-app-manifest json "access plus 0 seconds"
ExpiresByType text/cache-manifest "access plus 0 seconds"
# Favicon
ExpiresByType image/vnd.microsoft.icon "access plus 1 week"
ExpiresByType image/x-icon "access plus 1 week"
# HTML no caching
ExpiresByType text/html "access plus 0 seconds"
# Other
ExpiresByType application/xhtml-xml "access plus 1 month"
ExpiresByType application/pdf "access plus 1 month"
ExpiresByType application/x-shockwave-flash "access plus 1 month"
ExpiresByType text/x-cross-domain-policy "access plus 1 week"
</IfModule>
#End of expires headers configuration
RewriteEngine On
RewriteRule ^.well-known/acme-challenge - [L]
# BEGIN Imagify: webp file type
<IfModule mod_mime.c>
AddType image/webp .webp
</IfModule>
# END Imagify: webp file type
# BEGIN Force https
RewriteEngine On
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [L,R=301,NE]
# END Force https
# BEGIN Protect other domains trying to steal content
RewriteCond %{HTTP_REFERER} !^https://(www\.)? [NC]
RewriteCond %{HTTP_REFERER} !^https://(www\.)?.*$ [NC]
# END Protect content (incl scorm) to be accessed from other domains
# BEGIN WordPress
# The directives (lines) between "BEGIN WordPress" and "END WordPress" are
# dynamically generated, and should only be modified via WordPress filters.
# Any changes to the directives between these markers will be overwritten.
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
# MalCare WAF
<Files ".user.ini">
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
Order deny,allow
Deny from all
</IfModule>
</Files>
# END MalCare WAF
# Wordfence WAF
<Files ".user.ini">
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
Order deny,allow
Deny from all
</IfModule>
</Files>
# END Wordfence WAF
The .htaccess
file that I added in the subfolder:
<IfModule mod_expires.c>
ExpiresActive On
ExpiresDefault "access plus 6 hours"
ExpiresByType text/css "access plus 6 hours"
</IfModule>
Update: I've removed the <IfModule mod_expires.c>
wrapper.
The mod_expires
module is enabled/installed.
The full header response is:
HTTP/2 200 OK
server: nginx
date: Fri, 14 Oct 2022 12:06:43 GMT
content-type: text/css
last-modified: Tue, 11 Oct 2022 19:41:25 GMT
vary: Accept-Encoding
etag: W/"63***425"
cache-control: public, max-age=9072000
content-encoding: gzip
X-Firefox-Spdy: h2
CodePudding user response:
still has the expiration header of
max-age=9072000
, I assume coming from the.htaccess
file a folder up
Except that 9072000
seconds is 3.5 months (105 days), which doesn't appear to correspond to anything in the parent .htaccess
file either.
server: nginx
From the HTTP response headers for this request to your .css
file it would seem the response is being served by an Nginx server, not your Apache back-end/application server.
This suggests that you are behind a front-end proxy that is perhaps managing your static assets. This would also be responsible for the caching headers you are seeing. It's quite probable that Apache (your back-end server) is bypassed entirely for such requests. This is a relatively common optimization, as Nginx is more performant for serving static assets.
In fact, after a closer look at your .htaccess
file I see you are checking the X-Forwarded-Proto
HTTP response header in your "Force https" redirect - this confirms that you are indeed behind a (Nginx) proxy server. (If you weren't then that rule would result in a redirect loop, since that header is never normally set, except by proxy servers.)
If this is the case then you would need to make an exception in the proxy server that allows these requests through to be proccessed by your back-end/Apache server.