Search code examples
phpamazon-web-services.htaccess

AWS- subdomain doesn't support HSTS


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?


Solution

  • 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 be made.

    1. 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.

    2. 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/ to https://example.com/ to https://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).

    3. 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 then http://example.com/<some-url> would fail, since it's not redirected.

    4. You should remove the first Header directive - this is in direct conflict with the 2nd (correct) Header directive. The always 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.

    5. You should include the includeSubDomains directive on the Strict-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.)

    6. You should remove the <IfModule> wrappers; they are not required. 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.

      See my answer to the following question on the Webmasters stack for more info on this:
      Is Checking for mod_rewrite Really Necessary?

    7. 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 the STS 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 of REQUEST_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 of SERVER_NAME since you need the hostname present on the request. By default SERVER_NAME is the same as HTTP_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 the RewriteRule 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.

    Reference / See also:


    UPDATE: Try the following instead. Since you are on AWS, you'll probably need to check the X-Forwarded-Proto HTTP request header instead of the HTTPS Apache server variable in order to determine whether the request to the client is secure or not.

    The changes required are to the HTTP to HTTPS redirect (first rule) and the <If> expression at the end.

    Options +FollowSymLinks -Indexes
    
    RewriteEngine On
    
    # Redirect HTTP to HTTPS on the same host (requirement of HSTS)
    RewriteCond %{HTTP:X-Forwarded-Proto} =http
    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 "%{HTTP:X-Forwarded-Proto} == '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>