Force HTTPS on your website


One smaller feature we added in our last system update was the ability to force HTTPS without needing to change your code. Here’s a bit more information about why you might want to do that, and how it works.

Why force HTTPS?

If you’re using your default yourusername.pythonanywhere.com website on PythonAnywhere, people can access it using a non-secure connection via http://yourusername.pythonanywhere.com, or using a secure connection via https://yourusername.pythonanywhere.com. Likewise, if you have a paid account and are using a custom domain, they can access it non-securely via http://www.yourdomain.com or – if you’ve set up an HTTPS certificate – they can access it securely via https://www.yourdomain.com.

Having two ways to access the site is fine, but these days there’s a general move across the Internet to using HTTPS for everything. Newer versions of Chrome explicitly put the words “Not secure” in the browser’s address bar when you access a non-HTTPS site, and Google also apparently rank HTTPS sites higher in search results.

So often, what you want to do is set things up so that people can only use HTTPS to access your site – never simple non-secure HTTP. This is called “forcing HTTPS”, and works by sending back a redirect when people try to access the non-secure URL. So if someone goes to http://www.yourdomain.com, they’re just bounced straight over to https://www.yourdomain.com, and likewise if they go to http://www.yourdomain.com/something they’re redirected to https://www.yourdomain.com/something.

How it used to work

Because forcing HTTPS is a common requirement, most of the main Python web frameworks have support for it build in – Django has a SECURE_SSL_REDIRECT setting, for example, and there’s a Flask extension called flask-sslify. In the past, we suggested that people add the appropriate configuration to their code to use those features, and it all worked fine.

There were just two problems:

  • If you use the static files system on the “Web” tab, it picks up any requests before they get anywhere near your code. So while any requests that went to your view code were redirected to HTTPS, your static assets would still be served over plain HTTP if that was the kind of request made by the browser.
  • Putting this kind of protocol-level stuff in the Python code just feels like it’s happening at the wrong layer of the application. In particular, if you’re testing locally then perhaps you don’t want HTTPS to be forced there – and while the framework plugins generally have support for only forcing it in non-debug environments, it can be a little fiddly.

How it works now

We made a bunch of changes to the way our loadbalancers work as part of our recent system update, primarily in order to improve the way we handle HTTPS certificates. Because we were working in that part of the code, it was easy for us to add the capability to force HTTPS to our own infrastructure. So now, in order to force HTTPS for your website, just go to the “Web” page inside PythonAnywhere, select the site on the left, scroll down to the “Security” section, and toggle it on:

You’ll need to reload the site using the button at the top of the page to activate it.

Caveats

There are a couple of things to keep in mind when using this feature.

  • Most importantly, if you have a custom domain, you’ll need to make sure you keep your HTTPS certificate updated and that it does not expire. This is because every person coming to your site will be sent to the secure version – and if the cert has expired, they’ll get a security warning, which won’t look good!
  • A redirect message from a server only includes the URL to redirect to – not, for example, any POSTed data. This means that we can’t do a force-HTTPS redirect in response to a POST request, because the data would get lost. (It also wouldn’t be super-valuable – if the user has POSTed data, then it’s already gone across the Internet unencrypted, so sending it again in encrypted form wouldn’t really help matters.)

Any questions?

Hopefully that was all pretty clear, but if you do have any questions, just drop us a line at support@pythonanywhere.com or leave a comment below.

comments powered by Disqus