How to make python on Heroku https only?

Django 1.8 will have core support for non-HTTPS redirect (integrated from django-secure):

SECURE_SSL_REDIRECT = True # [1]
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

In order for SECURE_SSL_REDIRECT to be handled you have to use the SecurityMiddleware:

MIDDLEWARE = [
    ...
    'django.middleware.security.SecurityMiddleware',
]

[1] https://docs.djangoproject.com/en/1.8/ref/settings/#secure-ssl-redirect


What framework are you using for your application? If you're using Django you could simple use some middleware similar to:

import re

from django.conf import settings
from django.core import urlresolvers
from django.http import HttpResponse, HttpResponseRedirect


class SSLMiddleware(object):

    def process_request(self, request):
        if not any([settings.DEBUG, request.is_secure()]):
            url = request.build_absolute_uri(request.get_full_path())
            secure_url = url.replace("http://", "https://")
            return HttpResponseRedirect(secure_url)

Not sure if @CraigKerstiens's answer takes into account that request.is_secure() always returns False if behind Heroku's reverse proxy and not "fixed". If I remember correctly, this will cause a HTTP redirect loop.

If you are running Django with gunicorn, another way to do it is to add the following to gunicorn's config

secure_scheme_headers = {
    'X-FORWARDED-PROTO': 'https'
}

Run with some like this in your Procfile

web: python manage.py run_gunicorn -b 0.0.0.0:$PORT -c config/gunicorn.conf

By setting gunicorn's secure-scheme-header, request.is_secure() will properly return True on https requests. See Gunicorn Config.

Now @CraigKerstiens's middleware will work properly, including any calls to request.is_secure() in your app.

Note: Django also has the same config setting call SECURE_PROXY_SSL_HEADER, buts in the dev version.


Combining the answer from @CraigKerstiens and @allanlei into something I have tested, and verified to work. Heroku sets the HTTP_X_FORWARDED_PROTO to https when request is ssl, and we can use this to check:

from django.conf import settings
from django.http import HttpResponseRedirect


class SSLMiddleware(object):

    def process_request(self, request):
        if not any([settings.DEBUG, request.is_secure(), request.META.get("HTTP_X_FORWARDED_PROTO", "") == 'https']):
            url = request.build_absolute_uri(request.get_full_path())
            secure_url = url.replace("http://", "https://")
            return HttpResponseRedirect(secure_url)