What is going on when I set app.wsgi_app = ProxyFix(app.wsgi_app) when running a Flask app on gun...
Categories:
Demystifying ProxyFix: Running Flask Apps Behind a Reverse Proxy with Gunicorn

Understand the role of app.wsgi_app = ProxyFix(app.wsgi_app)
when deploying Flask applications with Gunicorn behind a reverse proxy, and how it correctly handles client information.
When deploying Flask applications in a production environment, it's common practice to place them behind a reverse proxy server like Nginx or Apache. This setup offers numerous benefits, including load balancing, SSL termination, static file serving, and enhanced security. However, this architecture introduces a challenge: the Flask application, by default, sees the reverse proxy's IP address as the client's IP, not the actual end-user's IP. This can lead to incorrect logging, security vulnerabilities, and misbehavior in features that rely on client IP or protocol information. This article delves into how app.wsgi_app = ProxyFix(app.wsgi_app)
addresses this issue when running your Flask app with Gunicorn.
The Problem: Reverse Proxies and WSGI
In a typical web application stack, a client request first hits a reverse proxy. The reverse proxy then forwards this request to your application server (e.g., Gunicorn), which in turn serves your Flask application. When the reverse proxy forwards the request, it often adds special HTTP headers to convey information about the original client, such as X-Forwarded-For
(client IP), X-Forwarded-Proto
(original protocol, e.g., HTTPS), and X-Forwarded-Host
(original host).
However, the WSGI specification, which Flask and Gunicorn adhere to, doesn't inherently understand these proxy-specific headers. A standard WSGI application will simply process the request as if it originated directly from the Gunicorn server, using the proxy's IP and the protocol used between the proxy and Gunicorn. This means request.remote_addr
in Flask would show the proxy's IP, and request.url_root
might incorrectly show http://
instead of https://
.
sequenceDiagram actor Client participant ReverseProxy as "Reverse Proxy (Nginx/Apache)" participant Gunicorn participant FlaskApp as "Flask Application" Client->>ReverseProxy: HTTP/HTTPS Request ReverseProxy->>Gunicorn: Forwarded Request (adds X-Forwarded-* headers) Gunicorn->>FlaskApp: WSGI Environment (without interpreting X-Forwarded-*) FlaskApp->>Gunicorn: WSGI Response Gunicorn->>ReverseProxy: HTTP Response ReverseProxy->>Client: HTTP/HTTPS Response
Request flow without ProxyFix, showing how Flask receives proxy's information.
The Solution: Werkzeug's ProxyFix Middleware
Werkzeug, the WSGI utility library that Flask is built upon, provides a middleware called ProxyFix
. This middleware is designed to inspect the X-Forwarded-*
headers added by reverse proxies and adjust the WSGI environment accordingly. When you wrap your Flask application's WSGI app with ProxyFix
, it effectively 'fixes' the environment variables before Flask processes the request.
Specifically, ProxyFix
modifies the following WSGI environment variables:
REMOTE_ADDR
: Set to the value ofX-Forwarded-For
.SERVER_NAME
andSERVER_PORT
: Set based onX-Forwarded-Host
.wsgi.url_scheme
: Set tohttps
ifX-Forwarded-Proto
ishttps
.
This ensures that when your Flask application accesses request.remote_addr
, request.url_root
, or request.is_secure
, it receives the correct information about the original client and the original request context, not just the details of the connection from the reverse proxy.
from flask import Flask, request
from werkzeug.middleware.proxy_fix import ProxyFix
app = Flask(__name__)
# Apply ProxyFix middleware
# This line is crucial when running behind a reverse proxy
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_port=1, x_prefix=1)
@app.route('/')
def hello():
# Without ProxyFix, remote_addr would be the proxy's IP
# With ProxyFix, it's the actual client's IP
client_ip = request.remote_addr
# Without ProxyFix, url_root might be http:// if proxy-gunicorn is http
# With ProxyFix, it correctly reflects the client's original protocol (e.g., https://)
full_url = request.url_root
return f"Hello from Flask! Your IP is {client_ip} and you accessed via {full_url}"
if __name__ == '__main__':
app.run(debug=True)
Example Flask application demonstrating the use of ProxyFix.
ProxyFix
if your application is actually behind a trusted reverse proxy. If you apply ProxyFix
without a proxy, or with an untrusted proxy, a malicious client could forge X-Forwarded-*
headers to spoof their IP address or other request details, leading to security vulnerabilities.Configuring ProxyFix Parameters
The ProxyFix
middleware accepts several parameters to control which headers it trusts and how many proxies it expects. These parameters correspond to the number of proxies that add the respective X-Forwarded-*
header.
x_for
: Number of proxies that setX-Forwarded-For
(client IP).x_proto
: Number of proxies that setX-Forwarded-Proto
(protocol).x_host
: Number of proxies that setX-Forwarded-Host
(host).x_port
: Number of proxies that setX-Forwarded-Port
(port).x_prefix
: Number of proxies that setX-Forwarded-Prefix
(URL prefix).
In most common setups with a single reverse proxy (like Nginx) directly in front of Gunicorn, setting these to 1
is sufficient, as shown in the example. If you have a chain of proxies, you would adjust these numbers accordingly. For instance, if you have a load balancer and then Nginx, you might need x_for=2
.
sequenceDiagram actor Client participant ReverseProxy as "Reverse Proxy (Nginx/Apache)" participant Gunicorn participant ProxyFixMiddleware as "ProxyFix Middleware" participant FlaskApp as "Flask Application" Client->>ReverseProxy: HTTP/HTTPS Request ReverseProxy->>Gunicorn: Forwarded Request (adds X-Forwarded-* headers) Gunicorn->>ProxyFixMiddleware: WSGI Environment (with X-Forwarded-*) ProxyFixMiddleware->>FlaskApp: Modified WSGI Environment (corrected REMOTE_ADDR, wsgi.url_scheme, etc.) FlaskApp->>ProxyFixMiddleware: WSGI Response ProxyFixMiddleware->>Gunicorn: WSGI Response Gunicorn->>ReverseProxy: HTTP Response ReverseProxy->>Client: HTTP/HTTPS Response
Request flow with ProxyFix middleware, showing how it corrects the WSGI environment.
Integration with Gunicorn
When running your Flask application with Gunicorn, the app.wsgi_app = ProxyFix(app.wsgi_app)
line should be placed in your Flask application's main file (e.g., app.py
or wsgi.py
) where your app
instance is created. Gunicorn will then load this modified WSGI application. There's no special Gunicorn configuration required for ProxyFix
itself, as it operates at the WSGI application level.
However, it's important that your reverse proxy is correctly configured to send the X-Forwarded-*
headers. For example, in Nginx, you would typically include directives like these in your server or location block:
location / {
proxy_pass http://127.0.0.1:8000; # Your Gunicorn address
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
}
Example Nginx configuration for forwarding headers to Gunicorn.
X-Forwarded-*
headers. If these headers are missing or incorrect, ProxyFix
won't be able to do its job, and your Flask app will still receive incorrect client information.In summary, app.wsgi_app = ProxyFix(app.wsgi_app)
is an essential line of code for Flask applications deployed behind a reverse proxy. It acts as a crucial middleware, translating proxy-specific headers into standard WSGI environment variables, thereby ensuring your Flask application correctly identifies the true client and request context. Understanding its purpose and proper configuration is key to building robust and secure Flask deployments.