Recently having moved a site to SSL, I looked into enforcing HSTS for eventual preload. The syntax is approved and the Chrome List allows it to be OK. However, not being a coder at all, a slight problem arises.

I have:

php_value upload_tmp_dir "/tmp"

# Force SSL

RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

# Redirect to www
RewriteCond %{HTTP_HOST} ^example.com\.com [NC]
RewriteRule (.*) https://www.example.com/$1 [E=HTTPS,R=301,L]

# Security header
Header set Strict-Transport-Security "max-age=63072000; preload; includeSubdomains" env=HTTPS

# End Force SSL'

This seems OK, but some sites on the web --- I won't clutter this with cites --- advise RewriteCond %{HTTPS} off and others have RewriteCond %{HTTPS} on

Logically ON seems right, but I need to know what is correct. Neither give errors.


...advise RewriteCond %{HTTPS} off and others have RewriteCond %{HTTPS} on

That wouldn't make sense in the context given as these are obviously opposites (I would be interested to see the full examples you are quoting this from).

However, maybe you mean off vs !on? These are equivalent in this context. The ! prefix negates the regex, so that effectively means not "on" (ie. it must be "off").

So, in the context of your directives above, where you are testing whether HTTPS is not active, then the following are equivalent:

# Does the HTTPS server variable contain "off"?
RewriteCond %{HTTPS} off

# Does the HTTPS server variable not contain "on"?
RewriteCond %{HTTPS} !on

The HTTPS server variable is either set to "on" or "off". (Or, it's not set at all - but that is dependent on your server/SSL setup and you will have already discovered that by now.)

Which you use is really just a matter of preference.

Other notes on HSTS and .htaccess

# Redirect to www
RewriteCond %{HTTP_HOST} ^example.com\.com [NC]
RewriteRule (.*) https://www.example.com/$1 [E=HTTPS,R=301,L]
# Security header
Header set Strict-Transport-Security "max-age=63072000; preload; includeSubdomains" env=HTTPS

(I assume the extra .com in ^example.com\.com is just a typo? That should be just ^example\.com.)

E=HTTPS - The purpose of setting the environment variable HTTPS on the RewriteRule redirect is to be able to conditionally set the Strict-Transport-Security HTTP response header on the canonical (HTTPS only) non-www to www redirect[*1] (ie. https://example.com to https://www.example.com) based on the env=HTTPS check on the Header directive. However, for this header to be set on the redirect, you'll also need to use the always keyword on the Header directive, like so:

Header always set Strict-Transport-Security "max-age=63072000; preload; includeSubdomains" env=HTTPS

As noted in the Apache docs, regarding the use of always with the Header directive when setting headers on redirects:

  • You're adding a header to a locally generated non-success (non-2xx) response, such as a redirect, in which case only the table corresponding to always is used in the ultimate response.

[*1] This header must be set on the redirect in order to satisfy point 4.1 of the HSTS preload submission requirements:

If you are serving an additional redirect from your HTTPS site, that redirect must still have the HSTS header (rather than the page it redirects to).

Just an additional comment on the linked article in the comments below, that states:

The env=HTTPS environment variable wasn't working as expected. So I used the E=HTTPS flag on the www redirect to set the env=HTTPS environment variable on the next request.

The last bit about setting "the env=HTTPS environment variable on the next request" isn't quite correct. It's setting the HTTPS environment variable on the current (redirect) response. By the time the "next request" comes about (ie. the browser has responded to the redirect), this environment variable (that was set above) is long forgotten. But this does require the always keyword to be used on the Header directive (as mentioned above).

Header always set Strict-Transport-Security "max-age=63072000; preload; includeSubdomains" env=HTTPS

Just a minor point, and maybe this doesn't actually matter, but... I would include the preload directive at the end of the list of directives. For example:

 Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains; preload" env=HTTPS

The preload directive is not actually part of the HTTP Strict Transport Security (HSTS) specification. It is only required by the preload list. Other user-agents/browsers do not use this and may not even understand this, so it would be more logical to put this at the end of the list. Some parses might stop as soon as they reach an "invalid" directive?

UPDATE: However, this does not appear to be a complete solution. As it stands, the HSTS response header is only being set on the non-www to www (HTTPS) redirect. It's not being set on any other HTTPS responses, which it would need to be. It's quite possible that this header is being returned by the application itself, but there is no evidence of that here (except that the OP states that it does pass HSTS validation).

For a more complete .htaccess-only solution see my answer to the following related question: HSTS implementation when using www.TLD

  • Thank you. You are correct off vs !on. OFF = hostinger.com/tutorials/ssl/forcing-https ON= stackoverflow.com/questions/33268353/… – Claverhouse Jan 25 '18 at 1:55
  • Sorry, hitting enter just closes the comment. – Claverhouse Jan 25 '18 at 2:30
  • 1
    E=HTTPS came from here as far as I remember --- I looked at a lot of tedious examples ! --- forums.cpanel.net/threads/htaccess-header-set-doesnt-set.613111 – Claverhouse Jan 25 '18 at 2:32
  • @Claverhouse Thanks for the links. Incidentally, !=on is effectively the same as !on in this context (the result is the same here). However, there is a difference. The = prefix makes it an exact string comparison, whereas without =, it's a regex and so checks for "on" anywhere inside the TestString. It can only ever be "on" or "off", so the result is the same. Very interesting about the use of E=HTTPS on the redirect - my initial comment on that was wrong. However, you'll need to add the always keyword to the Header directive for that to work OK. I've updated my answer. – MrWhite Jan 25 '18 at 15:59
RewriteCond %{HTTPS} !=on
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

Check whether HTTPS ISN'T on

RewriteCond %{HTTPS} off
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

Check whether HTTPS IS off

RewriteCond %{SERVER_PORT} !^443$
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}

Check whether connection runs NOT on secure https port 443

But all of them do the same: check if no https and redirect to https.

<IfModule mod_headers.c>
Header set Strict-Transport-Security "max-age=10886400; includeSubDomains; preload"

You know already what it does.

  • The <IfModule> wrapper is not required here. In fact, if you are going for the "HSTS preload list" then the wrapper should be removed since this header is mandatory. You are also missing the recommended env=HTTPS condition. – MrWhite Feb 27 '18 at 11:16
  • IfModule wrapper is for check purpose, whether the addressed module is already installed. Regarding env=HTTPS i'm not sure - there are a bunch of working examples with and without it. – Evgeniy Feb 27 '18 at 13:02
  • "IfModule wrapper is for check purpose..." - Yes, but that's the problem. If mod_headers is not installed then the directive simply fails silently and the header is not set. This header must be set for HSTS to be successful. This should break if mod_headers is not available since this header is not optional. See this related question with regards to mod_rewrite (same idea): webmasters.stackexchange.com/questions/112600/… – MrWhite Feb 27 '18 at 17:43
  • Thank you both. The forum now incorporates your suggestions. Incidentally ( I am not asking for further advice ), on WordPress, the excellent hackrepair section has to go first on .htaccess. hackrepair.com/articles/website-security/… Just to note, the preload thing does not seem to like that ! – Claverhouse Feb 28 '18 at 19:48
  • Added above as comment rather than as before misplaced. Incidentally to reply, I left the hackrepair in and am not bothering with preloading the WordPress... – Claverhouse Feb 28 '18 at 19:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.