Skip to main content

Flask behind a reverse proxy: actual client IPs

Hi! At work I'm involved with a REST API based on Flask. For SSL, we decided to use nginx as a reverse proxy. As a result, client IPs are all reported to be 127.0.0.1:

 * Running on http://0.0.0.0:5000/
127.0.0.1 - - [15/Feb/2015 17:43:48] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [15/Feb/2015 17:43:48] "GET /favicon.ico HTTP/1.1" 404 -

Flask is based on Werkzeug. Werkzeug comes with a helper called ProxyFix to address this problem.

from flask import Flask
from werkzeug.contrib.fixers import ProxyFix

app = Flask(__name__)
app.wsgi_app = ProxyFix(app.wsgi_app)
[..]

To make nginx feed the headers needed by ProxyFix, these lines help:

proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Host your.project.domain.org;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:5000/;

Now one thing remains to fix: The debugging log on stderr still reports 127.0.0.1. To get the IP from header X-Forwarded-For in there, I made this patching function replacing method WSGIRequestHandler.address_string:

def fix_werkzeug_logging():
    from werkzeug.serving import WSGIRequestHandler

    def address_string(self):
        forwarded_for = self.headers.get(
            'X-Forwarded-For', '').split(',')

        if forwarded_for and forwarded_for[0]:
            return forwarded_for[0]
        else:
            return self.client_address[0]

    WSGIRequestHandler.address_string = address_string

With that applied, I get actual client IPs. Tested with python-flask 0.8-1 and python-werkzeug 0.8.3+dfsg-1 of Debian wheezy. All source code in this post is licensed under CC0.