What is going on when I set app.wsgi_app = ProxyFix(app.wsgi_app) when running a Flask app on gun...

Learn what is going on when i set app.wsgi_app = proxyfix(app.wsgi_app) when running a flask app on gunicorn? with practical examples, diagrams, and best practices. Covers python, flask, wsgi devel...

Demystifying ProxyFix: Running Flask Apps Behind a Reverse Proxy with Gunicorn

Hero image for What is going on when I set app.wsgi_app = ProxyFix(app.wsgi_app) when running a Flask app on gun...

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 of X-Forwarded-For.
  • SERVER_NAME and SERVER_PORT: Set based on X-Forwarded-Host.
  • wsgi.url_scheme: Set to https if X-Forwarded-Proto is https.

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.

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 set X-Forwarded-For (client IP).
  • x_proto: Number of proxies that set X-Forwarded-Proto (protocol).
  • x_host: Number of proxies that set X-Forwarded-Host (host).
  • x_port: Number of proxies that set X-Forwarded-Port (port).
  • x_prefix: Number of proxies that set X-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.

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.