# Flask Nginx Production Setup: 9 Common Pitfalls and How to Fix Them

> Avoid common Flask Nginx production pitfalls like proxy header issues and incorrect upstream binding. Learn how to fix them for a reliable production setup.

- Repository: [Pallets/flask](https://github.com/pallets/flask)
- Tags: best-practices
- Published: 2026-02-21

---

**The most frequent Flask nginx production failures stem from mismatched proxy headers, missing ProxyFix middleware, and incorrect upstream binding between nginx and the WSGI server.**

Deploying a Flask application behind **nginx** creates a robust reverse-proxy architecture where nginx handles TLS termination, static assets, and buffering while forwarding dynamic requests to a WSGI server like Gunicorn or uWSGI. However, according to the `pallets/flask` source code and official documentation, several configuration details in `docs/deploying/nginx.rst` and `docs/deploying/proxy_fix.rst` are frequent sources of 502 errors, broken URL generation, and security vulnerabilities in production environments.

## The Reverse Proxy Architecture

In a standard Flask nginx production deployment, nginx sits between the internet and your application. Nginx receives external HTTP/HTTPS traffic, performs TLS termination, and serves static files directly from disk. For dynamic content, nginx acts as a reverse proxy, forwarding requests to a WSGI server running your Flask application on a local port or Unix socket. This separation of concerns improves performance and security, but requires precise configuration of headers, middleware, and network binding.

## Critical Configuration Pitfalls

### Mismatched Upstream URLs and Ports

The most immediate failure occurs when the `proxy_pass` directive in your nginx configuration points to a different address than where your WSGI server actually listens. A typo such as `http://127.0.0.1:8000/` while Gunicorn binds to port `8001` results in a **502 Bad Gateway** error. 

Verify your WSGI server’s bind address exactly matches the `proxy_pass` URL. As shown in `docs/deploying/nginx.rst` (lines 48-62), the minimal configuration expects your application server to listen on `http://127.0.0.1:8000/`.

### Improper X-Forwarded Header Forwarding

Without forwarding the `X-Forwarded-*` headers, Flask sees every request as originating from localhost (`127.0.0.1`), losing the original client IP, scheme, host, and path prefix. This breaks URL generation via `url_for()`, logging accuracy, and any IP-based security checks.

Add the four required `proxy_set_header` directives to your nginx server block as documented in `docs/deploying/nginx.rst` (lines 61-66):

```nginx
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;
proxy_set_header X-Forwarded-Prefix /;

```

### Missing ProxyFix Middleware

Even with correctly forwarded headers, Flask (via Werkzeug) ignores them by default for security reasons. Without explicit configuration, `request.remote_addr`, `request.url`, and `url_for()` will return incorrect values based on the internal proxy connection rather than the original client request.

Wrap your WSGI application with `werkzeug.middleware.proxy_fix.ProxyFix` as implemented in `docs/deploying/proxy_fix.rst` (lines 23-30):

```python
from werkzeug.middleware.proxy_fix import ProxyFix

app.wsgi_app = ProxyFix(
    app.wsgi_app,
    x_for=1,    # Trust X-Forwarded-For

    x_proto=1,  # Trust X-Forwarded-Proto

    x_host=1,   # Trust X-Forwarded-Host

    x_prefix=1  # Trust X-Forwarded-Prefix

)

```

### Incorrect Proxy Trust Levels

Setting the `x_for`, `x_proto`, `x_host`, or `x_prefix` values too high trusts headers from untrusted clients, creating IP spoofing vulnerabilities. Setting them too low discards legitimate forwarded values, causing redirect loops or broken links.

Count the actual proxy hops between the client and your application. In a standard single-nginx setup, use `1` for each parameter. If you have additional load balancers or CDNs, increment accordingly.

### Duplicate Static File Serving

Serving static assets from both nginx and Flask wastes bandwidth and can expose files unintentionally if Flask's static route has different access controls than nginx's filesystem permissions.

Configure a dedicated `location /static/` block in nginx to serve files directly from disk with caching headers, and ensure your production deployment does not rely on Flask's `app.static_folder` handling for performance-critical assets.

### Server Name and TLS Termination Issues

Using `server_name _;` (catch-all) is acceptable for testing, but in production you should specify actual domains to prevent host-header spoofing. Additionally, if you serve HTTPS from nginx but omit `proxy_set_header X-Forwarded-Proto $scheme;`, Flask will generate HTTP URLs, causing mixed-content warnings or insecure redirects.

Ensure the `X-Forwarded-Proto` header is set and that `ProxyFix` is configured with `x_proto=1` to trust it.

### Firewall and System Blocking

Even with perfect nginx and Flask configurations, operating system firewalls or SELinux policies may block traffic to the internal WSGI port (e.g., 8000). This manifests as connection timeouts or 502 errors despite services running.

Open the port locally using `ufw allow 8000` or configure SELinux rules. Alternatively, bind the WSGI server to a Unix socket file with appropriate permissions, eliminating the need for an open TCP port between nginx and your application.

## Complete Production Configuration Examples

### Minimal Nginx Reverse Proxy Configuration

This configuration mirrors the official example from `docs/deploying/nginx.rst` (lines 53-66):

```nginx
server {
    listen 80;
    server_name example.com;   # Replace with your domain

    location / {
        proxy_pass http://127.0.0.1:8000/;
        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;
        proxy_set_header X-Forwarded-Prefix /;
    }

    location /static/ {
        alias /path/to/your/app/static/;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }
}

```

### Flask Application with ProxyFix

Implement the middleware as described in `docs/deploying/proxy_fix.rst`:

```python

# app.py

from flask import Flask, request
from werkzeug.middleware.proxy_fix import ProxyFix

app = Flask(__name__)

# Trust one proxy (nginx) for all forwarded headers

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

@app.route("/")
def hello():
    # Now correctly reflects the real client IP

    return f"Hello! Your IP is {request.remote_addr}"

```

### Starting the WSGI Server

Bind Gunicorn to the exact address specified in your nginx `proxy_pass` directive:

```bash
gunicorn --bind 127.0.0.1:8000 wsgi:app

```

Ensure your [`wsgi.py`](https://github.com/pallets/flask/blob/main/wsgi.py) file exposes the application instance:

```python
from app import app as application

```

Apply configuration changes without dropping connections:

```bash
sudo systemctl reload nginx

```

## Summary

- **Match ports exactly**: Ensure `proxy_pass` in nginx points to the same host:port where your WSGI server binds.
- **Forward headers**: Include all four `X-Forwarded-*` headers so Flask receives original client information.
- **Enable ProxyFix**: Wrap your WSGI app with `werkzeug.middleware.proxy_fix.ProxyFix` and set trust levels to `1` for a single nginx proxy.
- **Serve static files once**: Let nginx handle `/static/` directly and disable Flask's static file serving in production if possible.
- **Reload services**: Run `systemctl reload nginx` after every configuration change to apply updates without downtime.

## Frequently Asked Questions

### Why does my Flask app return 502 Bad Gateway errors?

A 502 error indicates nginx cannot connect to the upstream WSGI server. Verify that your `proxy_pass` URL matches the bind address of Gunicorn or uWSGI exactly, including the port number. Check that the WSGI server is actually running and that no firewall rules block the connection between nginx and the application port.

### Why is request.remote_addr showing 127.0.0.1 instead of the client IP?

This occurs when `X-Forwarded-For` headers are not forwarded by nginx or when `ProxyFix` is not applied to your Flask application. Ensure your nginx configuration includes `proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;` and that you have wrapped `app.wsgi_app` with `ProxyFix` with `x_for` set to at least `1`.

### Do I need ProxyFix if I'm not using HTTPS?

Yes. While `ProxyFix` is essential for correct scheme detection behind HTTPS, you also need it to correctly identify the original client IP, host, and path prefix in any reverse-proxy setup. Without it, `request.remote_addr` will always show the nginx server's local IP, breaking logging and IP-based rate limiting regardless of the protocol.

### Should I use TCP ports or Unix sockets for the nginx-to-WSGI connection?

Both work, but Unix sockets often provide better performance and security for local communication. They eliminate TCP overhead and avoid the need to open firewall ports for internal traffic. If using sockets, set `proxy_pass http://unix:/path/to/app.sock;` in nginx and bind your WSGI server to the same socket file path with appropriate filesystem permissions.