How To Enable SSL For WordPress

Humans seem to fear and/or praise encryption in cycles. Sometimes nobody cares, yet other times it's all news outlets, governments, and hairdressers seem to talk about. Right now, we appear to be deep into the latter. Browser vendors like Google and Mozilla are starting to move from carrot to stick in their quests to see the whole Internet encrypted. And with new technologies like Let's Encrypt, there aren't very many excuses not to.

To Encrypt or Not to Encrypt?

Encryption ensures that communications between two parties (e.g. your web server and your visitor) are unaltered. Without that assurance, anything a visitor sends to your web site, or anything you send to a visitor, might be intercepted and/or altered along the way. Obviously, this means that sensitive information like logins and banking information can go astray, but the reverse is important too. Without encryption, Internet Service Providers can (and do) alter your page content before it reaches the visitor, usually to inject advertising or tracking scripts.

Encryption also offers benefits beyond security. Modern technologies like HTTP/2 and Brotli compression can shrink content and deliver it more quickly to users. Search engines like Google will also give you a nice little SEO bump for your trouble.

The only problem with SSL encryption is that to work, all assets on the page have to use it. If your site pulls in any tracking scripts, web fonts, advertising, etc., from third-parties, those connections must also use SSL or the protection is broken. Unfortunately some third-party services are shitty and don't support encryption on their end, in which case you either have to dump them or compromise security on your end.

The only other reason SSL might give you grief is if WordPress is running behind a proxy and does not have access to the environmental information necessary for it to determine whether or not a given request was encrypted. It boils down to the function is_ssl(), which relies on the superglobal $_SERVER['HTTPS']. If that variable isn't there, WordPress will end up stuck in a redirect loop. But if your server populates information in a different way, you can workaround this by manually setting $_SERVER['HTTPS'] to "on" or "off" in your wp-config.php file.

Do It!

So you've decided to upgrade your WordPress site to SSL. Congratulations! You're so modern! Before you get started, make sure you have a complete backup of everything (files and database, server configurations, etc.). Store that somewhere safe.

0. Obtain and Install an SSL Certificate

These days, most hosting providers offer free SSL protection via Let's Encrypt, but if your hosting provider is a stingy bastard, you might have to purchase one from a provider like Namecheap, or if they're really stingy, you might have to purchase one through them.

If your web hosting comes with a control panel of some sort, chances are you can do everything from there. Otherwise if you're going it manually, take a look at how to generate a certificate with Let's Encrypt and optimize its configuration for maximum security.

1. Update the WordPress URLs

WordPress will natively handle redirects, so it is important that WordPress thinks it belongs at https://domain.com rather than http://domain.com. Unfortunately, updating the URLs for an existing site is not a straightforward task. It cannot be done through WordPress as WordPress will explode somewhere in the middle when it can no longer find itself. And despite what a lot of tutorials on the internet suggest, you cannot simply run a search/replace over the database as many URL references will be stored in a serialized format.

The only fool-proof approach is to perform this operation from the command line using the delightful WP-CLI tool. If you have that installed, cd to your site root and run the following two commands:

# This assumes you are a decent human being and have your primary
# domain set to "domain.com" rather than a subdomain like
# "www.domain.com". If you are on the wrong side of history, just
# append a "www" to the second argument in each line. :)
wp search-replace 'http://domain.com' 'https://domain.com' --all-tables-with-prefix
wp search-replace 'http://www.domain.com' 'https://domain.com' --all-tables-with-prefix

If you do not have command line access, or aren't comfortable with it, some migration plugins support URL updates, however they might not be able to catch everything, and might not be able to faithfully run on a slow server.

If you can't manage either of those options, there are also plugins that will attempt to force SSL on-the-fly, however that comes with additional overhead and will not catch everything. This approach should only be considered as a last resort.

2. Update the Server

WordPress will handle connection upgrades for content that passes through the software, but that comes with unnecessary overhead and will also miss anything beyond WordPress' purview, such as static assets or additional content. To make sure your server is actually protected, it is necessary to upgrade requests at a server level.

For Apache users, this can usually be done by adding the following to a .htaccess file in the site root:

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

In the above example, you can also replace https://%{HTTP_HOST} with a static reference like https://domain.com, which will avoid subsequent redirects from www to no-www, etc.

For Nginx users, this is something you can bake into the virtual host record. That would look something like the following:

server {
	listen 80;
	listen [::]:80;

	server_name domain.com www.domain.com;
	return 301 https://domain.com$request_uri;
}
server {
	listen 443 ssl http2;
	listen [::]:443 ssl http2;

	server_name domain.com www.domain.com

	...
}

Just remember to restart Nginx after the change to make it official.

3. Bonus Points: Enable HSTS

When someone visits a site with HTTP Strict Transport Security enabled, their web browser will be instructed to only ever serve content from that domain securely. Sure, your server is already redirecting HTTP->HTTPS at this point, but in cases where someone is executing a Man-in-the-Middle attack, the user will be reaching some malicious, pretender server. With HSTS in place, their browser will itself require HTTPS, in which case the attack will fail (as it won't have a valid certificate).

For details about configuring this for Nginx, refer here. For Apache users, you just need to add the relevant header to your virtual host record:

<VirtualHost *:443>
	Header always set Strict-Transport-Security "max-age=31536000"
</VirtualHost>

4. Fix Third-Party Includes

As mentioned earlier, SSL is an all-or-nothing proposition. If your WordPress theme includes any hard-coded references to third-party assets likes fonts, tracking codes, etc., those have to use SSL connections too. Run a search over the code for "http://" and tweak URLs as necessary. If any themes or plugins store code chunks in the database, you might also need to search for references there as well. Anchors, like <a href="http://foobar.com"> can be left as-are, but other types of references like <iframe>, <form>, <link>, <script> and <img> tags will need to be updated.

5. Profit!

At this point, your WordPress site should be serving all content over SSL! It is recommended you take some time to browse your site, keeping an eye on the browser's console log, just to make sure nothing has broken or screaming at you. Other than that, just remember to renew the certificate before it expires, and life is good.

Josh Stoik
20 July 2017
Previous Nginx Static Compression, Gzip and Brotli
Next The Art of Database Partitioning