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
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
they’re just bounced straight over to
likewise if they go to
http://www.yourdomain.com/something they’re redirected
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.
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.)
Hopefully that was all pretty clear, but if you do have any questions, just drop us a line at firstname.lastname@example.org or leave a comment below.