How To

HTTPS and WordPress

You can set up HTTPS on WordPress smartly or less smartly. Let’s be smart.

Really there’s a right way and a not-quite-as-right way to handle HTTPS on WordPress. It’s not that hard to do, and if your whole site is going to be HTTPS, then the easiest way is to change your home and site URLs to be and put define( 'FORCE_SSL_ADMIN', true ); in your wp-config.php file. Then you should (if this is an existing site) search your database for the old HTTP url and change that to HTTPS.

Seriously, that’s it. That tells WordPress to be HTTPS all the way and you’re done. Of course, that doesn’t actually work 100% for everyone, because there are some silly plugins and themes that do things like this:

add_action('wp_enqueue_scripts', 'enqueue_google_maps');
function enqueue_google_maps() {
  wp_enqueue_script('google-maps', '', array(), '3', true);

The problem there is they’ve defined the script as HTTP and if your site is HTTPS then you’re going to get mixed content messages. And the real issue here is that means your connection is only partially encrypted! That non-encrypted content is accessible to sniffers and can be modified by man-in-the-middle attackers. This, clearly, is not safe anymore. The right way to do your enqueues is with protocol relative URLs:

  wp_enqueue_script('google-maps', '//', array(), '3', true);

Alternately you can just use the HTTPS url, because that won’t break HTTP visits and it won’t make anything less secure.

But. Since you really can’t go in and edit all your themes and plugins, the plugin WordPress HTTPS is the way to go. That can force everything around. I know it’s not updated in a long time, but it still works. I keep thinking I’ll fork and clean it up… Well in my free time. The point of that plugin is that it lets you force everything to HTTPS, and will rewrite things on the fly. It’s a good idea.

Instead of using the plugin, I’ve seen a lot of people do this in their .htaccess:

RewriteEngine On 
RewriteCond %{SERVER_PORT} 80 
RewriteRule ^(.*)$$1 [R,L]

In and of itself, this isn’t wrong. This forces everything HTTP to redirect to HTTPS. The problem is you’re still actually sending data from WordPress over HTTP first, and you’re right back to opening up to man-in-the-middle attacks because the data from WordPress goes from HTTP first and that’s, say it with me kids, insecure!

Now that said. This should be okay for most things. The POST calls should be sent securely, and all you should see on the return end is everything after that 301 redirect, but we can’t be absolutely sure about this. My buddy Jan used mod_substitute to force HTTPS (back before he moved to nginx). His code looks like this:

<Location />
 AddOutputFilterByType SUBSTITUTE text/html
 Substitute "s|href="|href="|"
 Substitute "s|href='|href='|"
 Substitute "s|src=\'http:|src=\'|"
 Substitute "s|src=\"http:|src=\"|"

In doing this, he doesn’t need to worry about the HTTPS plugin I mentioned, because it forces everything with a src attribute to be protocol relative. He also doesn’t have to search/replace his content if he doesn’t want to, which makes switching back easier. If you wanted to do that. But as Jan pointed out to me, he switched to nginx because it’s easier and supports variable substitutions.

Should you use .htaccess or nginx to force https instead of a plugin? That’s totally up to you. I use the plugin since I trust it to only mess with WordPress and not anything else I may have lying around. Also since my domains are often more than just WordPress, it’s a little easier for me to segregate their control. The flip side to this is that WordPress doesn’t redirect http traffic.

By this I mean if you turn your whole site to HTTPS properly, you can still go to and WordPress will load it as HTTP. This is and is not a bug. WordPress is (properly) trusting your server to tell it what it should be. Your server is saying “Be HTTP or HTTPS! Whatever!” Now there is a trac ticket to have FORCE_SSL really force SSL but that’ll be a while because there are a lot of complications in that change.

So yes, for now, I would use .htaccess to add an extra later of SSL forcing, but with a bit of caution. If you’re proxying HTTPS (like you’re on a Varnish cache behind something like Pound or nginx) then you may need to use this code for your .htaccess redirect.

RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteRule .* https://%{HTTP_HOST}%{REQUEST_URI} [R,L]

The reason for this is Apache can’t always see SSL if it’s not in charge of it (because it’s proxied or handled by a load balancer), and to teach it where it really lives. The trick there is the code I just showed you may not be right because every server’s a little different. There’s a great StackOverflow post on the problems of redirect loops while forcing https that you should read.

Good luck, and safe HTTPSing!

7 replies on “HTTPS and WordPress”

Thanks for putting this out there. Getting SSL working properly can sometimes be a real pain, and it’s great to see some working options in writing, as I tend to wrap myself around the axle trying to figure out how to get things working (it can be problematic when you have more than one “solution” in place at a time; they can end up competing and cancelling each other out).

The one tiny fly in this ointment, though, comes when one of your themes or plugins references an external asset (img, JS, CSS, etc.) that, for whatever stupid reason, isn’t available over SSL. Sadly, I see this more often than I’d like.

Also, as far as using https for your assets even when the page is loaded over non-SSL, there is one instance where that can be somewhat problematic: AJAX. If the server isn’t configured to allow cross-domain access control origins (I’m not sure that’s the right way to phrase that), then AJAX requests to an SSL asset can get blocked pretty easily. I ran into this a few weeks ago while trying to work on some new AJAX requests on a site where FORCE_SSL_ADMIN is on, but the front-end site is normally loaded over non-SSL. Because I used admin_url( ‘admin-ajax.php’ ) to get the URL that the AJAX request should query, and because SSL was forced on the admin side, my AJAX requests were failing.

I’ve been told that that particular issue can be solved by running a JSONP request (which I’ll be trying in my “free time”), but for the time being, the simplest way for me to solve it was to strip the protocol from the AJAX URL.

@Curtiss Grymala: @Curtiss Grymala: Yeah, AJAX can make http/https a bit of a mess. My smilies that you can click to insert into comments was actually written in order to take care of that.

Right now, we’re not forcing plugin authors to use protocol relative URLs, though I often think about it. It’s too much overhead.

That Forward Proto rule took me HOURS to sort out too. What a pain in the ass.

great stuff, thanks =)

I like using
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
(because the void precedes all, and because wildcard matches are expensiver)

What I’m wondering about is for 100% HTTPS multisite netoworks, how might you prefer to approach setting subsite SITEURL and HOME to use https:// after new site creation?

I hope you have a thought or two on this… a thread to pull perhaps?

Honestly, this has been kinda an intimidating one… enough that I’m still generally doing it manually via the UI or very awkwardly elsewise =)

It’d be great if we could just use wp-config =)
define( ‘WP_HOME’, ‘https://’ . $_SERVER[‘HTTP_HOST’] );//working yet?
define( ‘WP_SITEURL’, ‘https://’ . $_SERVER[‘HTTP_HOST’] );//working yet?

again, thanks for the write-up – and for passing on the mod_substitute fun =)


What Iโ€™m wondering about is for 100% HTTPS multisite netoworks, how might you prefer to approach setting subsite SITEURL and HOME to use https:// after new site creation?

You really wanna know?

I use wp-cli or the interconnectitDB tool to search/replace http to https on the whole DB. If it’s all going to be https, once I’ve gotten the main site on HTTPS all my subsites will be built as HTTPS too ๐Ÿ™‚

This is actually easier than the mix and match I have now, because I have to edit the DB directly a little to make that work.

@Ipstenu (Mika Epstein):

I use wp-cli or the interconnectitDB tool to search/replace http to https on the whole DB.

I do the same… for staying in the wp-admin, I often recommend using the search/replace functionality built into UpdraftPlusPremium as an easy serialized-safe s/r approach… (of course, I also stress the importance of actually backing up/test restoring first =).

…and actually, my preferred approach is to get the certificate installed first and then run the install from the https URL… this results in SITEURL & HOME using https right ‘out-of-the-box’ and I figure is the best way to get/allow WP to recognize and work with the https environment…

If itโ€™s all going to be https, once Iโ€™ve gotten the main site on HTTPS all my subsites will be built as HTTPS too ๐Ÿ™‚

However, in every network I’ve set up the default behavior has been that despite all the above (and a few extra efforts as well), new subsites are created with thier SITEURL & HOME set to use http rather than https… so, currently I set these values to use https manually after subsite creation and am figuring that there must be an actually clever way to do this (via mu-plugins?)… I really like having the subsites’ set up this way for a variety of reasons, I just wish I had the time/skillz to figure out how to make this into a nice and automagical plugin =)

While I’m great with servers, seo and many other skills, I’m not a PHP/frontend person (yet!) and in many ways I have treated WP as a back box in working with it… if you’ve got a slick solution or some insight I’d really appreciate the input =)

Kind Regards, Max

An alternative to WordPress HTTPS, just for cleaning up mixed content errors, is the SSL Insecure Content Fixer. Its base mode is to only fix the really simple stuff like badly enqueued scripts and stylesheets, etc. so it can have a smaller hit on website performance. It also now handles HTTS detection when WordPress can’t detect HTTPS, e.g. when it’s behind a reverse proxy.

Comments are closed.