301 Redirects in Apache: HTTPS, WWW, and URL switching

Computer programming
Computer programming by Goran Ivos/Unsplash

A few months ago, I tried to move this site, then Rawkblog.net, over to HTTPS encryption. Moving to HTTPS makes your visits to websites and any data you enter on them more secure: it’s important enough that it will also help a site’s rank in Google. But at the time, I couldn’t quite get it to work, thanks to a tangle of redirects between www and non-www versions of the site plus another domain name to wrangle, Rawkblog.com. So I switched to another problem, transferring Rawkblog from its old (slow) home at Bluehost over to a Digital Ocean VPS server (fast!) via Cloudways. After years at Rawkblog.net, this site finally became Rawkblog.com.

With that successfully accomplished, I turned back to the HTTPS problem yesterday. It took all day, again, but somehow, I got it to work with a chain of code I somehow haven’t seen anywhere else.

The problem

A second domain, Rawkblog.net, needed to redirect in a permanent, 301 way to Rawkblog.com to make sure over a decade of links land correctly over here.

Both the www and non-www versions of Rawkblog.net needed to point over, and the non-www version of Rawkblog.com needed to point to www.rawkblog.com to offer a single canonical site URL instead of duplicative versions.

And then, everything also needed to point to HTTPS.

I made these changes manually with the hidden .htaccess file in my site root, after turning off all my caching (the Breeze WordPress plugin and on the server level, Varnish), and used the incredible website HTTPSStatus.io to test multiple link paths simultaneously and clearly see the chain of redirects in action. With the cache off, the site responded instantly and didn’t need any cookie clearing or browser hijinks.

What didn’t work

It was easy enough to get parts of this to work: everything but HTTPS, or HTTPS for everything except the www.rawkblog.com site.

For instance, this If statement:

<If "%{HTTP_HOST} !='www.rawkblog.com'">
Redirect permanent / https://www.rawkblog.com/
</If>

This redirects everything except http://www.rawkblog.com over to the correct address by ordering: if this isn’t www.rawkblog.com, point it in the right direction. But it won’t change the www to https.

I looked through the Apache documentation and thought this would work:

<If "%{SERVER_PROTOCOL} != 'HTTPS'">
Redirect permanent / https://www.rawkblog.com/
</If>

But it turns out this is a documentation error! The way to check on HTTP status is REQUEST_SCHEME. But that didn’t work for me, either.

I tried to do an old-fashioned mod_rewrite, like this:

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule .* https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</IfModule>

But this broke the site by creating an endless redirect loop. (HTTP Status said it made it 11 loops before cracking.)

The solution

Then I found two solutions that worked:

<IfModule mod_rewrite.c>
Options +FollowSymLinks
RewriteEngine On
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteRule .* https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

#WordPress standard redirect
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>

<If "%{HTTP_HOST} ='www.rawkblog.net'">
Redirect permanent / https://www.rawkblog.com/
</If>
<ElseIf "%{HTTP_HOST} ='rawkblog.net'">
Redirect permanent / https://www.rawkblog.com/
</ElseIf>

The first mod_rewrite checked for HTTPS without causing a redirect infinity loop. The standard WordPress code looks at the site URL you’ve set in the admin menu and makes sure rawkblog.com moves over to www.rawkblog.com. And finally, the If statement moves the .net sites over to .com properly. It works!

But according to HTTP Status, it did so in 2 redirects, moving everything to https, then doing everything else. There had to be a better way. After another hour of tests and grimaces, I found it:

<IfModule mod_rewrite.c>
Options +FollowSymLinks
RewriteEngine On
RewriteCond %{HTTP:X-Forwarded-Proto} !https [NC,OR]
RewriteCond %{HTTP_HOST} ^rawkblog.net [NC,OR]
RewriteCond %{HTTP_HOST} ^www.rawkblog.net [NC]
RewriteRule ^(.*)$ https://www.rawkblog.com/$1 [R=301,L]

RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>

Just a simple mod_rewrite gets the job done: the first condition checks for HTTPS, the next two look at .net, and if any of these are satisfied, the whole thing moves over to the proper site. Finally, the WordPress code makes sure there are no issues with rawkblog.com vs. www.rawkblog.com. It all happens in one level of 301 redirects, which is great for site speed and also my sanity.

This will even move a URL from https://www.rawkblog.net/example to https://www.rawkblog.com/example, a (probably unnecessary) redirect the earlier method didn’t handle. (I think. I… tried a lot of methods.)

I hope this code is helpful to you if you, like me, spent half your night staring at the Apache documentation and got nowhere. Got a better way? Email me: david at davidgreenwald dot com.

Bug spotting

I found one: this technique works but leaves a double-redirect on WordPress date and archive pages (like rawkblog.com/page/2/). It works properly on tag, category, and other taxonomy pages. Why? I’ll find out.