.htaccess 301 vs. 302 Enforcing HTTPS

The different .htaccess/mod_rewrite directives you see around are usually because of differences in the server configuration (nothing to do with WordPress) and where they are being used. There’s no such thing as a one size fits all. (For instance, the above redirect may not work if you are behind an SSL proxy.) However, as with most things, there are often different ways of doing the same thing. So, you don’t need to do it one specific way for it to work perfectly.

Option#1

RewriteCond %{SERVER_PORT} 80 
RewriteRule ^(.*)$ https://www.example.com/$1 [R,L]

By default, HTTP works on port 80, which is what this checks for. However, you could configure HTTP to work on a different port. In which case, this would fail.

As you’ve stated, this is a 302 (temporary) redirect. (The default status when you don’t explicitly include the status code with the R flag.) Unless you are testing then an HTTP to HTTPS redirect is expected to be permanent. So, you would normally expect this to be a 301, ie. R=301. 301 redirects are cached by browsers and seen as a permanent change by search engines.

^(.*)$ – strictly speaking, the start and end anchors, ie. ^ and $, are entirely superfluous. ^(.*)$ is the same as (.*). However, some users find the former easier to read. So whatever you prefer. This simply grabs the entire URL-path and saves it in the $1 backreference, used later in the substitution.

https://www.example.com/$1 – Hardcoding the canonical hostname in the substitution is arguably more reliable. When passing this code to someone to use on their site, there is less margin for error. Server admins often argue that this is the way you should always do it (although I don’t necessarily agree – I’m not a server admin). (Particularly if you have multiple domains and www vs non-www hostname differences.) However, hardcoding is… well… hardcoding. So, it’s less portable.

This directive should only be used in .htaccess (or a directory context). If you used this directly in the server config (not that you would) you would get a double slash (//) in the redirected URL.

Option#2

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

HTTPS is a server variable set by Apache. It is either off or on and works regardless of what port HTTP (and/or HTTPS) is being served on. It’s also arguably easier to read than checking SERVER_PORT. However, HTTPS is not available on Apache 1.3 (although that really should not be a problem!). On most servers there will be no difference between checking %{SERVER_PORT} 80 and %{HTTPS} off.

R=301 – Good, it’s a 301 (permanent) redirect. Which is usually what you want.

(.*) – No unnecessary anchors (see above). However, the $1 backreference is not being used in the substitution (%{REQUEST_URI} is used instead), so the parentheses are an unnecessary (minor) overhead and should be removed. This should be written .*, or simply ^.

https://%{HTTP_HOST}%{REQUEST_URI} – The HTTP_HOST server variable holds the hostname that is present in the request. If your site is accessible via multiple domains then this will hold whatever domain is being requested. If your SSL cert only covers one of these domains then this will break. This also does not canonicalise any www vs non-www differences – which would need to be done before this directive to avoid a potential double redirect (which is why you often see www and HTTPS redirects done together).

Because of the use of %{REQUEST_URI} in the substitution this rule can be used in either the server config or .htaccess file without modification.

Option#3

RewriteCond %{HTTPS} off
RewriteRule ^ https://www.example.com%{REQUEST_URI} [R=301,L]

For either solution, is it correct that I should add the RewriteCond and RewriteRule entries immediately after RewriteBase /

Yes, that is a logical place to put it. (However, it could go before it, even before the RewriteEngine directive – technically, it doesn’t matter.)