As with many flexible programming systems, there are often many different ways to do essentially the same thing. One way isn't necessarily any "better" than another. However, different methods may be required depending on specific requirements or different server configs.
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</IfModule>
There is nothing wrong with this. If it works for you then go with it. However, you should be aware of what it actually does. Specifically, it redirects to the same URL on the same host. By itself this does not canonicalise the www subdomain - so you might be seeing two redirects for certain non-canonical requests (eg. if WordPress, or another part of your config, canonicalises the www subdomain). Now, this isn't necessarily a bad thing - if you are implementing HSTS then this is actually a requirement. But, if you are not implementing HSTS then you might consider incorporating your canonical www redirect so you only ever get a single redirect.
However, these directives could be "tidied" a bit:
You don't need the <IfModule>
wrapper, unless you are using these directives on other servers where mod_rewrite might not be available and you have "backup" server-code that redirects HTTP to HTTPS when mod_rewrite is not available. Otherwise, these directives are considered mandatory and the <IfModule>
wrapper should be removed.
Since you are using WordPress, which already has its own block of mod_rewrite directives, you don't strictly need to repeat the RewriteEngine On
directive - even though these directives (presumably) occur before the WP directives. (The last RewriteEngine
directive in the .htaccess
file wins and controls the entire file/context.)
Regex optimisation: ^(.*)$
- you don't need the capturing parentheses in the RewriteRule
pattern when you are not making use of the backreference in the substitution (or preceding conditions). In other words, you shopuld change this to: ^.*$
(or simply .*
since the anchors are not required either - the regex is greedy by default).
In summary:
RewriteCond %{HTTPS} off
RewriteRule .* https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
RewriteCond %{HTTPS} !on
RewriteCond %{SERVER_PORT} !^443$
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]
Yes, this does "the same job", but in a different way. As @Invariant suggests in his answer, the 3rd condition that checks the X-Forwarded-Proto
HTTP request header is required when behind a proxy server (or load balancer) - although this can vary (not all proxies work the same way).
However, these directives are "overkill" and not strictly correct. If you are behind a proxy then the first two conditions are most probably redundant as they likely always evaluate to true. (But you wouldn't need both of these conditions under any circumstances - they essentially do the same thing.) And if you aren't behind a proxy then the 3rd condition (that checks X-Forwarded-Proto
) shouldn't be used anyway since it would potentially make your server vulnerable to malicious requests that inject this header, tricking your server into thinking it is already communicating over HTTPS when it is not.
The reason why you see rules like this (and the supposed "advantage") is that it is more "generic" and is more likely to work on different server configs - it can be copy/pasted between sites and stand a better chance of "working". However, as with many "generic" solutions", it introduces redundancy, is less efficient and, in this case, potentially introduces a vulnerability.
When used in .htaccess
in the document root of the site then the following two directives result in the same response.
RewriteRule .* https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]
However, the later will not work if .htaccess
is located anywhere else, other than the document root. And nor will it work "properly" outside of a directory context (ie. in the server config).