Deploying a Flask app part II: using an Apache reverse proxy

I recently wrote about serving a Flask web application on localhost using gunicorn. This is sufficient to get an app up and running locally using a production-ready WSGI server, but we still need to add a HTTP proxy server in front to securely handle HTTP requests coming from external clients. Here we’ll cover configuring a simple reverse proxy using the Apache web server, though of course you could do the same with another HTTP server such as nginx.

Telling Flask it’s behind a reverse proxy

Before we go any further, we need to tell Flask that the app is running behind a reverse proxy. This is important as, from Flask’s point of view, requests will appear to be coming from the HTTP server to the WSGI server, rather than from a remote address to the HTTP server, which will cause problems when Flask tries to generate the correct response to a request. The HTTP server will forward the necessary headers to tell Flask where requests are coming from, but we need to tell Flask to trust these headers. Fortunately this is easy to fix – we simply include the following snippet of code in our Flask app’s main file:

from werkzeug.middleware.proxy_fix import ProxyFix

app.wsgi_app = ProxyFix(
    app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1
)

In this case, we have told Flask to trust one set of headers, i.e. that it is behind a single reverse proxy. It’s necessary to tell Flask not only that it’s behind a proxy, but how many proxies there are, as headers can be faked and telling Flask to trust an incorrect number of headers can compromise the security of your application.

Proxying an app running on localhost

Now that we’ve told Flask it’s behind a proxy, let’s first consider the simpler case: our app is running on localhost on a certain port and we simply wish to proxy it using Apache running on the same machine. For clarity, support we run our Flask app using unicorn with the following command:

gunicorn --bind 127.0.0.1:8000 --worker-class gevent

In this case, our app is running on 127.0.0.1:8000. If we want Apache to take requests to https://my.site.com/myapp and forward these to our app running on port 8000 (assuming we’ve already enabled or ideally enforced https of course), we can add the following reverse proxy to our server’s Apache configuration file (located in /etc/apache2/sites-available):

<Location /myapp>
	ProxyPass http://127.0.0.1:8000/
	ProxyPassReverse http://127.0.0.1:8000/
	RequestHeader set X-Forwarded-Proto http
	RequestHeader set X-Forwarded-Prefix /myapp
</Location>

This block tells Apache to forward all requests to /myapp to the app running on 127.0.0.1:5000 and to forward the necessary headers.

Proxying an app running on a different host

Now for something slightly more complicated. Suppose we want our WSGI server and our HTTP server running on two different hosts. This might be useful for resource allocation or simply to reduce the risk of an external attacker gaining access to the entire system if the HTTP server is compromised.

Currently our application is running on 127.0.0.1:5000 on one host, meaning it is only accessible from that host. To make it accessible from a different host on the network, we instead need to bind 0.0.0.0 when running gunicorn:

gunicorn --bind 0.0.0.0:8000 --worker-class gevent

Now, suppose the host of our application has private IP address 10.12.34.56 and is able to communicate with the host of our HTTP server. We can modify our reverse proxy to forward requests to port 8000 on our application server as follows:

<Location /myapp>
	ProxyPass http://10.12.34.56:8000/
	ProxyPassReverse http://10.12.34.56:8000/
	RequestHeader set X-Forwarded-Proto http
	RequestHeader set X-Forwarded-Prefix /myapp
</Location>

And that’s it! The HTTP server will now take requests to /myapp and forward them to our application running on port 8000 on a different host. From here, just configure Apache to suit your performance and security needs and you’re good to go.

Author