I have a .htaccess file like so:
#protect config files
<FilesMatch "\.(env|json|lock|xml|yml|htaccess|gitignore|gitattributes)$">
Order Allow,Deny
Deny from all
Require all denied
</FilesMatch>
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^([^\.]+)$ $1.php [NC,L]
</IfModule>
<filesMatch "\.(html|css|js)$">
AddDefaultCharset UTF-8
</filesMatch>
<filesMatch "\.(css|js)$">
FileETag MTime Size
</filesMatch>
ErrorDocument 304 /error_pages/304.php
ErrorDocument 400 /error_pages/400.php
ErrorDocument 401 /error_pages/401.php
ErrorDocument 403 /error_pages/403.php
ErrorDocument 404 /error_pages/404.php
ErrorDocument 409 /error_pages/409.php
ErrorDocument 410 /error_pages/410.php
ErrorDocument 500 /error_pages/500.php
<IfModule mod_expires.c>
ExpiresActive On
# Fonts
ExpiresByType font/ttf "access plus 1 year"
ExpiresByType font/otf "access plus 1 year"
ExpiresByType font/woff "access plus 1 year"
ExpiresByType font/woff2 "access plus 1 year"
# Images
ExpiresByType image/jpg "access plus 1 year"
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/gif "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/x-icon "access plus 6 year"
# CSS, JavaScript
ExpiresByType text/css "access plus 6 month"
ExpiresByType application/javascript "access plus 6 month"
ExpiresByType application/x-javascript "access plus 6 month"
ExpiresByType application/x-shockwave-flash "access plus 6 month"
# Other
ExpiresByType application/pdf "access plus 6 month"
ExpiresDefault "access plus 10 days"
</IfModule>
<IfModule mod_headers.c>
# Jaar Public
<FilesMatch "\.(jpg|jpeg|png|gif|svg|webp|ico|ttf|woff|woff2|JPG|JPEG|PNG|GIF|SVG|WEBP|ICO|TTF|WOFF|WOFF2)$">
Header set Cache-Control "max-age=31536000, public"
</FilesMatch>
# Jaar
<FilesMatch "\.(js|css|swf)$">
Header set Cache-Control "max-age=31536000"
</FilesMatch>
</IfModule>
# Disable directory browsing
Options All -Indexes
How can I still access json files in a subfolder? Some part of i18n is in a subfolder called assets and is now not loaded in because of the first directive. As I am a front-end person I don't know a lot about Apache. I also have problem with the JS routing from Vaadin (see: Vaadin/Router) On reload of a link within the page like so: /page/123 it doesn't load, instead returning a 404. When returning to /page it works as intended. I have found that /page.php/123 works as intended if configured so. Is it possible to hide the .php extension, but still request the page.php with the /123 URI?
I have a .htaccess file like so:
#protect config files
<FilesMatch "\.(env|json|lock|xml|yml|htaccess|gitignore|gitattributes)$">
Order Allow,Deny
Deny from all
Require all denied
</FilesMatch>
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^([^\.]+)$ $1.php [NC,L]
</IfModule>
<filesMatch "\.(html|css|js)$">
AddDefaultCharset UTF-8
</filesMatch>
<filesMatch "\.(css|js)$">
FileETag MTime Size
</filesMatch>
ErrorDocument 304 /error_pages/304.php
ErrorDocument 400 /error_pages/400.php
ErrorDocument 401 /error_pages/401.php
ErrorDocument 403 /error_pages/403.php
ErrorDocument 404 /error_pages/404.php
ErrorDocument 409 /error_pages/409.php
ErrorDocument 410 /error_pages/410.php
ErrorDocument 500 /error_pages/500.php
<IfModule mod_expires.c>
ExpiresActive On
# Fonts
ExpiresByType font/ttf "access plus 1 year"
ExpiresByType font/otf "access plus 1 year"
ExpiresByType font/woff "access plus 1 year"
ExpiresByType font/woff2 "access plus 1 year"
# Images
ExpiresByType image/jpg "access plus 1 year"
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/gif "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/x-icon "access plus 6 year"
# CSS, JavaScript
ExpiresByType text/css "access plus 6 month"
ExpiresByType application/javascript "access plus 6 month"
ExpiresByType application/x-javascript "access plus 6 month"
ExpiresByType application/x-shockwave-flash "access plus 6 month"
# Other
ExpiresByType application/pdf "access plus 6 month"
ExpiresDefault "access plus 10 days"
</IfModule>
<IfModule mod_headers.c>
# Jaar Public
<FilesMatch "\.(jpg|jpeg|png|gif|svg|webp|ico|ttf|woff|woff2|JPG|JPEG|PNG|GIF|SVG|WEBP|ICO|TTF|WOFF|WOFF2)$">
Header set Cache-Control "max-age=31536000, public"
</FilesMatch>
# Jaar
<FilesMatch "\.(js|css|swf)$">
Header set Cache-Control "max-age=31536000"
</FilesMatch>
</IfModule>
# Disable directory browsing
Options All -Indexes
How can I still access json files in a subfolder? Some part of i18n is in a subfolder called assets and is now not loaded in because of the first directive. As I am a front-end person I don't know a lot about Apache. I also have problem with the JS routing from Vaadin (see: Vaadin/Router) On reload of a link within the page like so: /page/123 it doesn't load, instead returning a 404. When returning to /page it works as intended. I have found that /page.php/123 works as intended if configured so. Is it possible to hide the .php extension, but still request the page.php with the /123 URI?
Share Improve this question edited Mar 24 at 9:31 Sean asked Mar 21 at 10:37 SeanSean 213 bronze badges 8 | Show 3 more comments1 Answer
Reset to default 0#protect config files <FilesMatch "\.(env|json|lock|xml|yml|htaccess|gitignore|gitattributes)$"> Order Allow,Deny Deny from all Require all denied </FilesMatch>
The fact that this started working after removing the <IfModule mod_headers.c>
container implies that mod_headers is not installed on your system*1. Which is a little unusual. (Although it made no sense to contain that block in such an <IfModule>
directive anyway. In fact, you should generally avoid <IfModule>
directives in your config, unless the contained rules are optional and the same config is intended to be used unaltered on many different systems. Otherwise, the <IfModule>
directive simply masks the error.)
You are also mixing both Apache 2.2 (Order
and Deny
) directives with Apache 2.4 (Require all denied
) directive. This is bad practice and can result in unexpected conflicts (not to mention that Order
and Deny
are formerly deprecated and not available by default on Apache 2.4). So, using this same config on other systems is likely to fail. Assuming you do not have other Order
, Deny
and/or Allow
directives in your config (anywhere) then you should remove the Order
and Deny
directives here.
(*1 If mod_headers is not installed then the later section that sets some Cache-Control
headers is also not processed. Although this is probably a good thing, since this section conflicts with the mod_expires section directly above it!?)
Json files are blocked, but then also in subfolders. I only want to block project configuration files in the root, for security reasons.
("for security reasons"?! Presumably it's because your application needs client-side access to .json
files in subdirectories for some reason? Not a security thing, as allowing access can only be less secure?)
The <FilesMatch>
directive matches against the filename only, not the file-path, so applies to all subdirectories (which is generally what you want in this scenario).
If you want to be able to access .json
files in subdirectories then you need to remove |json
from the above alternation section in the regex. And create a separate rule that blocks requests for .json
files in the root only. For example:
# Block requests for ".json" files in the root only
<If "%{REQUEST_URI} =~ m#^/[^/]+\.json$#">
Require all denied
</If>
This uses an Apache expression to examine the requested URL. Note that this blocks URLs, regardless of whether a .json
file actually exists. (Which is actually the same as the <FilesMatch>
directive.)
The regex ^/[^/]+\.json$
will only match /<something>.json
and not /<something>/<something>.json
etc.
RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^([^\.]+)$ $1.php [NC,L]
For example the route { path: "/tickets.php/:id", component: "ticket-component" } works when the hotlinks are updated to tickets.php/4509 instead of tickets/4509
This simply appends the .php
extension to any URL-path that does not contain a dot and that does not map to a file*2. (It should at least check that the target .php
file exists.)
(*2 Note that this does not necessarily test the full URL-path since REQUEST_FILENAME
is a calculated value based on your filesystem.)
With the above rule, if you request /tickets/4509
(and /tickets
is not a physical subdirectory) then the RewriteRule
directive first matches against tickets/4509
(which is saved in the $1
backreference). The preceding condition then tests if /tickets
(not /tickets/4509
) is not a physical file (which I assume it's not, so the condition is successful) so ends up rewriting the request to tickets/4509.php
- which naturally fails with a 404.
Assuming all your relevant .php
files exist in the document root only (not subdirectories) then you would need to write the rule something like the following instead to test for .php
files in root and append the remaining URL-path as path-info (which seems to be what you are expecting).
For example:
# Rewrite requests of the form "/page/id" to "/page.php/id"
RewriteCond %{DOCUMENT_ROOT}/$1.php -f
RewriteRule ^([^./]+)(/\d+)?$ $1.php$2 [L]
Note that I included a slash in the negated regex character class so it only matches the first URL-path segment, not the entire URL-path.
I'm assuming (based on your examples) that only numeric IDs are being passed. eg. /<page>/<numeric-id>
. But I've allowed for the /<numeric-id>
part to be entirely optional, so /tickets
only could be requested and is simply rewritten to /tickets.php
.
The preceding condition (RewriteCond
directive) now checks that the corresponding .php
file actually exists before attempting to rewrite the request.
No need to backslash-escape literal dots when used inside a regex character class. And the NC
flag is superfluous, since the regex itself is not case-specific.
Summary
So, bringing the above points together, your complete .htaccess
file would now look like this:
# Disable directory browsing
Options All -Indexes
# Block requests for ".json" files in the root only
<If "%{REQUEST_URI} =~ m#^/[^/]+\.json$#">
Require all denied
</If>
# Protect other config files (anywhere)
<FilesMatch "\.(env|lock|xml|yml|htaccess|gitignore|gitattributes)$">
Require all denied
</FilesMatch>
<FilesMatch "\.(html|css|js)$">
AddDefaultCharset UTF-8
</FilesMatch>
<FilesMatch "\.(css|js)$">
FileETag MTime Size
</FilesMatch>
ErrorDocument 304 /error_pages/304.php
ErrorDocument 400 /error_pages/400.php
ErrorDocument 401 /error_pages/401.php
ErrorDocument 403 /error_pages/403.php
ErrorDocument 404 /error_pages/404.php
ErrorDocument 409 /error_pages/409.php
ErrorDocument 410 /error_pages/410.php
ErrorDocument 500 /error_pages/500.php
<IfModule mod_expires.c>
ExpiresActive On
# Fonts
ExpiresByType font/ttf "access plus 1 year"
ExpiresByType font/otf "access plus 1 year"
ExpiresByType font/woff "access plus 1 year"
ExpiresByType font/woff2 "access plus 1 year"
# Images
ExpiresByType image/jpg "access plus 1 year"
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/gif "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/x-icon "access plus 6 year"
# CSS, JavaScript
ExpiresByType text/css "access plus 6 month"
ExpiresByType application/javascript "access plus 6 month"
ExpiresByType application/x-javascript "access plus 6 month"
ExpiresByType application/x-shockwave-flash "access plus 6 month"
# Other
ExpiresByType application/pdf "access plus 6 month"
ExpiresDefault "access plus 10 days"
</IfModule>
<IfModule mod_rewrite.c>
RewriteEngine On
# Rewrite requests of the form "/page/id" to "/page.php/id"
RewriteCond %{DOCUMENT_ROOT}/$1.php -f
RewriteRule ^([^./]+)(/\d+)?$ $1.php$2 [L]
</IfModule>
I also adjusted the logical order of the rule blocks, which helps readability and maintenance (although this does not strictly affect functionality).
Ordinarily, the <IfModule mod_rewrite.c>
wrapper should probably be removed, although your system is technically usable without this section, providing you use (the less pretty) URLs of the form /page.php/<id>
.
Although this is still strictly not complete as URLs of the form /page.php/<id>
are still accessible, potentially creating a duplicate content issue (whether this would become a real problem for your site is another matter). You could implement this as a canonical redirect in .htaccess
(or your PHP code) or simply block such requests with a 404. This is left as an exercise for the reader.
.htaccess
file. (Except for an "incorrect" rewrite to.php
files) How do you expect such a request to be handled (server-side)? You have other inconsistencies in your.htaccess
file to do with caching. Is mod_headers installed (normally it is)? – MrWhite Commented Mar 21 at 11:07FilesMatch
into<IfModule mod_headers.c>
though - that directive is not provided by mod_headers, but part of Apache core. So try and remove that If-Wrapper, and see what happens then. – C3roe Commented Mar 21 at 11:08/page/123
gets requested, doesn't match an existing file - so you append.php
to it, but I'm pretty sure/page/123.php
won't actually exist in your system either. – C3roe Commented Mar 21 at 11:09</FilesMatch>
directive, which would effectively disable the remainder of your.htaccess
file (if not an error). But did that resolve the issue of still being able to access.json
files or not? Do you have any other.htaccess
files (in subfolders)? "I have found that /page.php/123 works as intended" - But that's routing the request to a PHP script, I thought/page/123
was supposed to be handled by JS? – MrWhite Commented Mar 21 at 16:17