I am using Semrush and I have this one notice that is really bothering me and want to get rid of. 1 subdomain doesn't support HSTS for the subdomain: www.domain.com , here is my .htdocs file:
Options -Indexes
<IfModule mod_rewrite.c>
Options FollowSymLinks
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php/$1 [L]
RewriteCond %{HTTPS} !=on
RewriteCond %{HTTP_USER_AGENT} ^(. )$
RewriteCond %{SERVER_NAME} ^example\.com$
RewriteRule .* https://www.%{SERVER_NAME}%{REQUEST_URI} [R=301,L]
Header add Strict-Transport-Security "max-age=300"
</IfModule>
<IfModule mod_headers.c>
<If "%{REQUEST_SCHEME} == 'https' || %{HTTP:X-Forwarded-Proto} == 'https'">
Header always set Strict-Transport-Security "max-age=31536000"
</If>
</IfModule>
I add this part at the end:
<IfModule mod_headers.c>
<If "%{REQUEST_SCHEME} == 'https' || %{HTTP:X-Forwarded-Proto} == 'https'">
Header always set Strict-Transport-Security "max-age=31536000"
</If>
</IfModule>
But still nothing, what am I doing wrong?
CodePudding user response:
Have you tried to fix this via DNS record and .vconfig file? For my websites, I have:
- DNS A records for main domain (
A domain.com 123.123.123.123
) - Second DNS A record for www (
A www 123.123.123.123
)
and then in domain.com.vhost:
<VirtualHost *:80>
DocumentRoot /var/www/path_to_root
ServerName domain.com
ServerAlias domain.com
RewriteEngine on
RewriteCond %{HTTPS} off
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L,NE]
</VirtualHost>
The rewrite rule forces HTTPS.
If you want to force HTTPS for specific domain, both in .vhost or .htaccess you can use:
RewriteEngine On
RewriteCond %{HTTP_HOST} ^yourdomain1.com [NC]
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
and for specific folder:
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(folder1|folder2|folder3) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
Make sure to change the folder references to the actual directory names.
CodePudding user response:
If you're able to route your DNS via Cloudflare (even on their free plans), you'll be able to enable HSTS at the click of a button (including for subdomains). Loads of other benefits for speed and security too which might also help with your SEMRush report
CodePudding user response:
Options -Indexes
<IfModule mod_rewrite.c>
Options FollowSymLinks
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php/$1 [L]
RewriteCond %{HTTPS} !=on
RewriteCond %{HTTP_USER_AGENT} ^(. )$
RewriteCond %{SERVER_NAME} ^example\.com$
RewriteRule .* https://www.%{SERVER_NAME}%{REQUEST_URI} [R=301,L]
Header add Strict-Transport-Security "max-age=300"
</IfModule>
<IfModule mod_headers.c>
<If "%{REQUEST_SCHEME} == 'https' || %{HTTP:X-Forwarded-Proto} == 'https'">
Header always set Strict-Transport-Security "max-age=31536000"
</If>
</IfModule>
There are a number of issues here that will prevent HSTS from working properly and some additional optimisations that should made.
You are not redirecting
http://www.example.com
(ie. the www subdomain) to HTTPS. You need to redirect HTTP WWW to HTTPS WWW in order to be HSTS compliant.You are redirecting
http://example.com/
(ie. HTTP non-www) to HTTPS WWW. In order to be HSTS compliant, you need to redirect to HTTPS on the same host first. ie.http://example.com/
tohttps://example.com/
tohttps://www.example.com
(yes, that's two redirects, but it's also rare and will only occur at most once per user-agent, because of the STS header that is returned).Your directives are in the wrong order. Your canonical redirects (ie. HTTP to HTTPS and non-www to www) need to be before your front-controller rewrite (the first rule). By placing them after the front-controller they are never going to be processed for anything other than the homepage, directories and static resources. eg.
http://example.com/<some-url>
will never be redirected by the rules as written. Not sure how you are testing HSTS compliance with SEMrush, but with the directives as written thenhttp://example.com/<some-url>
would fail, since it's not redirected.You should remove the first
Header
directive - this is in direct conflict with the 2nd (correct)Header
directive. Thealways
condition (as used in the second instance) is required in order to set the header on the HTTPS 301 redirect (ie. non-2xx response) from non-www to www.You should include the
includeSubDomains
directive on theStrict-Transport-Security
header in order to cover the www subdomain when the domain apex is requested. Although, all subdomains will now be implicitly included. Without this, the www subdomain will need to be explicitly requested. (Ok, you are redirecting to www anyway in the next request.)You don't need the
<IfModule>
wrappers. These directives are mandatory, they are not optional (which is implied by the presence of these wrappers). Your site will presumably fail if mod_rewrite is not available and you will not be HSTS compliant.I assume from the earlier HTTP non-www redirect that you are not behind a front-end proxy (or load balancer) that manages your SSL, in which case the check for
%{HTTP:X-Forwarded-Proto}
in the later<If>
expression should be removed. If you are not behind a "proxy" then it would be possible to fake a request to prevent theSTS
header being sent back (a user should not be able to do this).Conversely, if you are behind a "proxy" then the check for
%{REQUEST_SCHEME} == 'https'
is redundant - but causes no harm. Incidentally, the use ofREQUEST_SCHEME
assumes you are on Apache 2.4 (which I expect you are), however, if you are still on Apache 2.2 then this check will fail and the STS header will not be set.
So, taking the above points into consideration, your .htaccess
file should be written like this:
Options FollowSymLinks -Indexes
RewriteEngine On
# Redirect HTTP to HTTPS on the same host (requirement of HSTS)
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
# Redirect non-www to www (HTTPS only)
RewriteCond %{HTTP_HOST} ^example\.com$ [NC]
RewriteRule ^ https://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
# Front-controller
RewriteRule ^index\.php/ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule (.*) index.php/$1 [L]
<If "%{REQUEST_SCHEME} == 'https'>
# The "always" condition is required to set the header on the HTTPS redirect
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
</If>
Additional notes:
I've used
HTTP_HOST
instead ofSERVER_NAME
since you need the hostname present on the request. By defaultSERVER_NAME
is the same asHTTP_HOST
, but this can vary based on the server config.There is no reason to only apply the canonical redirect when a non-empty User-Agent string is present on the request (a malicious request could suppress the User-Agent string) as you had originally, so I've removed this condition. If anything, you would block such requests.
The regex
^
in theRewriteRule
pattern is more optimal than.*
since it only needs to be successful - it doesn't need to actually match anything since it is not being used later.The additional
RewriteRule ^index\.php/ - [L]
directive before the "front-controller" is simply an optimisation to prevent rewritten URLs from being unnecessarily passed through the filesystem checks that follow.