Moving to HTTPS on WordPress

Avatar of Chris Coyier
Chris Coyier on (Updated on )

📣 Freelancers, Developers, and Part-Time Agency Owners: Kickstart Your Own Digital Agency with UACADEMY Launch by UGURUS 📣

I just recently took CSS-Tricks “HTTPS everywhere”. That is, every URL on this site enforces the HTTPS (SSL) protocol. Non-secure HTTP requests get redirected to HTTPS. Here are some notes on that journey.

Why do it?

  • General security. When you enforce HTTPS, you’re guaranteeing no information passed between the server and client can be intercepted and stolen or messed with in any way. That’s great for a site like this that has a login system and accepts credit cards in some places and things like that.
  • The site as it is intended. I’ve heard of examples like hotel WiFi system and even ISPs that mess with HTTP traffic and do things like insert their own advertising code. Can’t do that over HTTPS.
  • SEO. Google says you’ll rank higher. Horse’s mouth.
  • Prereq. I can’t seem to find any good links on this, but I’m under the assumption that HTTPS is required for some/all of the stuff for SPDY / HTTP/2 – which everyone agrees is awesome and super fast. I want to make sure I’m ready so I can start moving forward on that.
  • Geek cred. Duh.

1. Get an SSL certificate

Not optional. This is how it works. I’ve done this a bunch of times in my life and it’s never overly comfortable. You just need to follow instructions closely. I’ve bought them in the past from discount providers and actually had it work fine, but it’s a more manual process.

CSS-Tricks is happily on Media Temple, and they provide SSL as a service, and I’m more than happy to pay for that to have it installed painlessly by professionals.

When the SSL certificate is installed properly, you should be able to visit your site (any URL) at either HTTP or HTTPS and have it come up fine. There may be errors on HTTPS though, and we’ll get to that.

2. Start with the Admin

In WordPress-land, you might as well get HTTPS going in the admin area first. It’s set up to handle it and there probably won’t be any errors. (I keep saying “errors”, I mostly mean “mixed content warnings” which I promise we’ll get to.)

To force HTTPS in the admin area, put this line in your wp-config.php file at the root of your WordPress install:

define('FORCE_SSL_ADMIN', true);

Make sure you test that HTTPS is working properly first! Go to https://yoursite.com/wp-admin/ to check. Otherwise you’ll be forcing URLs that don’t work and that’s bad. If you have trouble, remove that line right away.

All goes well, you’ll get a secure connection:

3. Try to get one page working on the front end

The next step is to get your front end on HTTPS. Forcing it all right away is probably going to be tough, so just start with one target page. For me, it was the signup page for The Lodge. That page can take credit cards, so really, it had to be HTTPS. This was the motivator for me early on to get this set up.

There is a plugin that can help with this: WordPress HTTPS (SSL). With that plugin, you get a checkbox on Posts/Pages to force it to be SSL.

4. Mop up Mixed Content Warnings

What you’re really trying to avoid is this:

Mixed Content Warning

That’s like: “Hey nice trying being HTTPS but you aren’t fully so NO GREEN LOCK FOR YOU!”

If you open the console, you’ll likely get messaging like this:

In this case, it was some images being used in a CodePen embed with an HTTP src.

But it could be anything. HTTP <script>s, HTTP CSS <link>s, HTTP <iframe>s. Anything that ends up making an HTTP request that isn’t HTTPS will trigger the error.

You just need to fix them. All.

5. Protocol Relative URLs! (or just relative URLs)

You know, those ones that start with //, like this:

<img src="//example.com/image.jpg" alt="image">

Those are your friend. They will load that resource with whatever protocol the current page is. And links that are just relative to begin with will be fine, like:

<img src="/images/image.jpg" alt="image">

I ended up finding them all over the place. I even had Custom Fields to fix:

6. Harder problem: images within legacy content

There are thousands and thousands of pages on this site, and lots and lots of images within those pages. Right in the content itself. There are a bunch on this very page you’re looking at. The problem is those images had fully qualified HTTP links on them.

I didn’t relish the idea of using some WordPress filter on content to kinda hotswap those URL’s out – I just wanted to fix the issue. I hired Jason Witt to help me with this. The first thing we did was run some SQL on the database to fix the URL’s. Essentially fixing the src of images to be protocol relative.

After backing up the database, and testing it locally, this worked great:

UPDATE wp_posts 
SET    post_content = ( Replace (post_content, 'src="http://', 'src="//') )
WHERE  Instr(post_content, 'jpeg') > 0 
        OR Instr(post_content, 'jpg') > 0 
        OR Instr(post_content, 'gif') > 0 
        OR Instr(post_content, 'png') > 0;

And another to catch single-quoted ones, just in case:

UPDATE wp_posts 
SET   post_content = ( Replace (post_content, "src='http://", "src='//") )
WHERE  Instr(post_content, 'jpeg') > 0 
        OR Instr(post_content, 'jpg') > 0 
        OR Instr(post_content, 'gif') > 0 
        OR Instr(post_content, 'png') > 0;

We even fixed the Custom Fields in much the same way:

UPDATE wp_postmeta 
SET meta_value=(REPLACE (meta_value, 'iframe src="http://','iframe src="//'));

7. Make sure new images are protocol relative

With the legacy content mopped up, we need to make sure new content stays OK. That means fixing the media uploader/inserter thingy so it inserts images with protocol relative URLs. Fortunately I already customize that to insert with <figure> tags, so it was just an adjustment of that. Jason ultimately helped me move a lot of my custom functions.php code into a plugin, so this is a little out of context, but I think you’ll get the picture and see the relevant filters and stuff:

class CTF_Insert_Figure {

  /**
   * Initialize the class
   */
  public function __construct() {
    add_filter( 'image_send_to_editor', array( $this, 'insert_figure' ), 10, 9 );
  }
  
  /**
     * Insert the figure tag to attched images in posts
     *
     * @since  1.0.0
     * @access public
     * @return string return custom output for inserted images in posts
     */
  public function insert_figure($html, $id, $caption, $title, $align, $url) {
    // remove protocol
    $url = str_replace(array('http://','https://'), '//', $url);
    $html5 = "<figure id='post-$id' class='align-$align media-$id'>";
    $html5 .= "<img src='$url' alt='$title' />";
    if ($caption) {
        $html5 .= "<figcaption>$caption</figcaption>";
    }
    $html5 .= "</figure>";
    return $html5;
  }
}

8. Your CDN needs SSL too

If you have a CDN set up (I use MaxCDN through W3 Total Cache) that means the CDN is totally different server and URL and all that and it needs to be able to serve over HTTPS as well. Fortunately MaxCDN handles it.

9. Start forcing HTTPS everywhere

This is what I do in the .htaccess file at the root:

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

Once that is in place, you can turn off the plugin and remove the bit from the wp-config.php file, as those are redundant now.

You’ll also want to change the URL settings in General Settings to be https:// – so all the links in WordPress are constructed properly and redirects don’t need to come into play:

10. Keep mopping up

Inevitably after going HTTPS everywhere, you’ll find pages with mixed content warnings. You just need to keep investigating and fixing.

Good luck! (and let me know if I’ve fouled anything up)


Update! Speaking of mopping up… Content Security Policy (CSP) is a technology that might be incredibly helpful for you here. There is a directive for it, upgrade-insecure-requests, that can force the browser to use HTTPS for any HTTP resources it finds. A bit of a sledgehammer, but could be super useful.