Making HTTPS Redirects Work With a Reverse Proxy

The Problem

One common setup is to have a reverse proxy (like Pound, Lighttpd, or Apache) sit in front of CherryPy and handle requests. If you want to handle both http and https protocols, you set up your reverse proxy to deal with the secure communications, and then pass types of both types of requests (secure and insecure) to CherryPy as a normal http request. CherryPy processes the requests, returns them to the proxy, and the proxy passes them on to the client (secure or insecure, depending on the original request).

This causes a problem if you do a HTTPRedirect to a URL in your application. CherryPy thinks that this is an unencrypted request. So, the redirect URL provided by CherryPy will begin with http, regardless of whether or not the original URL scheme was http or https.

One Solution

One way to work around this is to have the proxy give a hint to CherryPy as to the original protocol. Most proxies have some way to set a custom header to the request before passing it on. So, the general solution is to add a special header to https requests, and then have a CherryPy tool look for that header and modify the base URL to something more appropriate.

Step One: Have the proxy set a custom header

Pound is a fairly easy to configure reverse proxy that we can use as an example. In the Pound configuration, we add a custom header called ‘X-Forwarded-Ssl’. The relevant part of the Pound configuration file looks like:

ListenHTTPS
  Address 127.0.0.1
  Port 443
  Cert "/Users/plewis/Desktop/Pound-2.0.2/mycert.pem"
  AddHeader "X-Forwarded-Proto: https"
  Service
      BackEnd
          Address 127.0.0.1
          Port 8080
      End
  End
End

Apache users can use mod_headers to add a custom header. For example, in your SSL virtualhost section, add the following line:

RequestHeader add X-Forwarded-Proto https

Lighttpd users can set a custom header via setenv.add-request-header.

Regardless of the proxy you are using, you don’t want to add this header on a global basis, but only for https requests.

Step Two: Set configuration file settings

CherryPy 3 comes with a builtin tool that already does exactly what we want. It is named tools.proxy and just needs to be activated.

In your configuration file (e.g. prod.cfg), set the following configuration variable:

[/]
tools.proxy.on = True

Step Three: Test things out

You should be able to add a method to your controller like:

@turbogears.expose()
def test_redirect(self):
    turbogears.redirect('/') # uses cherrypy.HTTPRedirect

Calling this URL should work over both http and https, and will properly redirect to the base of your application.