Skip to main content

FastAPI Proxy Headers

When deploying FastAPI applications in production environments, they often run behind proxies, load balancers, or reverse proxies like Nginx, Traefik, or AWS Application Load Balancer. In these setups, understanding how to properly handle proxy headers becomes crucial for security and functionality.

Introduction to Proxy Headers

In a typical deployment architecture, client requests pass through one or more intermediary servers (proxies) before reaching your FastAPI application:

Client → Proxy/Load Balancer → FastAPI Application

When this happens, your FastAPI application sees the proxy's IP address as the client IP instead of the actual client's IP address. Similarly, information about the original protocol (HTTP vs HTTPS) can be lost.

Proxies solve this by adding special headers to the forwarded request:

  • X-Forwarded-For: Contains the original client IP address
  • X-Forwarded-Proto: Indicates the protocol of the original request (HTTP/HTTPS)
  • X-Forwarded-Host: The original host requested by the client
  • Forwarded: A standardized header that can contain all of the above information

Why Proxy Headers Matter

Handling proxy headers correctly is important for several reasons:

  1. Security: Accurate client identification for rate limiting and security policies
  2. Logging: Proper tracking of client origins in logs
  3. URL Generation: Creating correct absolute URLs in responses
  4. SSL Enforcement: Redirecting users to HTTPS when needed

Configuring FastAPI for Proxy Headers

FastAPI provides a built-in way to handle proxy headers through the trusted_hosts parameter in its configuration.

Basic Setup

Here's how to configure a FastAPI application to trust proxy headers:

python
from fastapi import FastAPI
from fastapi.middleware.trustedhost import TrustedHostMiddleware

app = FastAPI()

# Add middleware to trust host headers
app.add_middleware(
TrustedHostMiddleware, allowed_hosts=["example.com", "*.example.com"]
)

@app.get("/")
async def root():
return {"message": "Hello World"}

This configuration ensures that requests are only accepted from the specified hosts, helping prevent HTTP Host header attacks.

Working with Client IP Addresses

To access the client's true IP address in your FastAPI handlers, you'll need to look at the appropriate headers. Here's how:

python
from fastapi import FastAPI, Request

app = FastAPI()

@app.get("/client-info")
async def get_client_info(request: Request):
# Get client IP considering X-Forwarded-For header
client_host = request.client.host

# Get headers to see all forwarded information
forwarded_for = request.headers.get("x-forwarded-for")
forwarded_proto = request.headers.get("x-forwarded-proto")

return {
"client_host": client_host,
"forwarded_for": forwarded_for,
"forwarded_proto": forwarded_proto,
"is_secure": request.url.scheme == "https"
}

When running behind a proxy, this endpoint would show the forwarded information rather than the proxy's details.

Using Starlette's ProxyHeadersMiddleware

FastAPI is built on top of Starlette, which provides a helpful middleware for handling proxy headers. This middleware automatically adjusts the client.host and url.scheme based on the proxy headers:

python
from fastapi import FastAPI, Request
from starlette.middleware.proxy_headers import ProxyHeadersMiddleware

app = FastAPI()

# Add the ProxyHeadersMiddleware to trust forwarded headers
app.add_middleware(ProxyHeadersMiddleware, trusted_hosts=["*"])

@app.get("/client-address")
async def get_client_address(request: Request):
return {
"client_host": request.client.host,
"scheme": request.url.scheme,
"url": str(request.url)
}

With this middleware, request.client.host will correctly show the client's IP from the X-Forwarded-For header, and request.url.scheme will properly reflect whether the original request was HTTP or HTTPS.

caution

Setting trusted_hosts=["*"] trusts all proxy headers from any source. In production, you should restrict this to only your actual proxy servers' IP addresses to prevent IP spoofing attacks.

Real-World Example: Rate Limiting Based on Client IP

A practical application of correctly handling proxy headers is implementing rate limiting based on the actual client IP, not the proxy's IP:

python
from fastapi import FastAPI, Request, Response, Depends
from starlette.middleware.proxy_headers import ProxyHeadersMiddleware
import time
from typing import Dict, List, Tuple

app = FastAPI()
app.add_middleware(ProxyHeadersMiddleware, trusted_hosts=["10.0.0.1", "proxy.internal"])

# Simple in-memory rate limiting store
# In production, use Redis or another distributed store
rate_limit_store: Dict[str, List[Tuple[float, int]]] = {}
RATE_LIMIT = 5 # requests
TIME_WINDOW = 10 # seconds

async def check_rate_limit(request: Request, response: Response):
client_ip = request.client.host
now = time.time()

# Initialize or clean old requests
if client_ip not in rate_limit_store:
rate_limit_store[client_ip] = []
else:
# Remove entries older than the time window
rate_limit_store[client_ip] = [
(timestamp, count) for timestamp, count in rate_limit_store[client_ip]
if now - timestamp < TIME_WINDOW
]

# Count recent requests
recent_requests = sum(count for _, count in rate_limit_store[client_ip])

# Update headers with rate limit info
response.headers["X-Rate-Limit"] = str(RATE_LIMIT)
response.headers["X-Rate-Limit-Remaining"] = str(max(0, RATE_LIMIT - recent_requests))

# If there are recent requests, add to them, otherwise create new entry
if rate_limit_store[client_ip] and rate_limit_store[client_ip][-1][0] == now:
rate_limit_store[client_ip][-1] = (now, rate_limit_store[client_ip][-1][1] + 1)
else:
rate_limit_store[client_ip].append((now, 1))

# Check if rate limit exceeded
if recent_requests >= RATE_LIMIT:
return {"error": "Rate limit exceeded", "retry_after": TIME_WINDOW}
return None

@app.get("/api/resource")
async def protected_resource(rate_limit_check=Depends(check_rate_limit)):
if rate_limit_check:
return rate_limit_check
return {"data": "This is protected resource data"}

This implementation:

  1. Correctly identifies the true client IP by using request.client.host after the ProxyHeadersMiddleware processes the proxy headers
  2. Applies rate limiting based on the actual client IP, not the proxy's IP
  3. Returns rate limit information in the response headers

HTTPS Detection with Proxy Headers

Another common scenario is detecting whether the original client request was made over HTTPS, which is important for security-related features:

python
from fastapi import FastAPI, Request

app = FastAPI()

@app.get("/check-https")
async def check_https(request: Request):
# With ProxyHeadersMiddleware, this will correctly reflect the original scheme
is_https = request.url.scheme == "https"

# Manual check of the X-Forwarded-Proto header
forwarded_proto = request.headers.get("x-forwarded-proto")
manually_detected_https = forwarded_proto == "https" if forwarded_proto else None

return {
"is_https": is_https,
"scheme": request.url.scheme,
"manually_detected_https": manually_detected_https,
"forwarded_proto": forwarded_proto
}

Configuring with Uvicorn

When running FastAPI applications with Uvicorn, you can also configure proxy header handling at the server level:

python
import uvicorn
from fastapi import FastAPI

app = FastAPI()

if __name__ == "__main__":
uvicorn.run(
"main:app",
host="0.0.0.0",
port=8000,
forwarded_allow_ips=["10.0.0.1", "proxy.internal"],
proxy_headers=True
)

The forwarded_allow_ips parameter specifies which IPs are allowed to set proxy headers, and proxy_headers=True enables the processing of these headers. This achieves the same effect as using the ProxyHeadersMiddleware.

Security Considerations

When working with proxy headers, keep these security considerations in mind:

  1. Trust only your proxies: Configure trusted_hosts or forwarded_allow_ips to only include your actual proxy servers' IP addresses.
  2. Validate headers: Don't blindly trust proxy headers without verification.
  3. Consider using standard headers: The newer Forwarded header is more secure than the older X-Forwarded-* headers.
  4. Layer your security: Don't rely solely on header inspection for security measures.

Example: Full Configuration with Nginx

Here's a complete example showing both the Nginx configuration and FastAPI application:

Nginx configuration:

nginx
server {
listen 80;
server_name example.com;

location / {
proxy_pass http://localhost:8000;
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;
}
}

FastAPI application:

python
from fastapi import FastAPI, Request
from starlette.middleware.proxy_headers import ProxyHeadersMiddleware

app = FastAPI()

# Trust headers from our Nginx proxy
app.add_middleware(ProxyHeadersMiddleware, trusted_hosts=["127.0.0.1"])

@app.get("/")
async def root(request: Request):
return {
"client_ip": request.client.host, # Will show the real client IP
"scheme": request.url.scheme, # Will show http or https correctly
"url": str(request.url) # Will construct URLs with correct scheme
}

# Run with: uvicorn app:app --host 0.0.0.0 --port 8000

Summary

Properly handling proxy headers in FastAPI applications is essential when deploying behind proxies or load balancers. By using the appropriate middleware and understanding how these headers work, you can:

  • Correctly identify client IP addresses
  • Detect the original request protocol (HTTP vs HTTPS)
  • Implement security measures like rate limiting based on actual client information
  • Generate correct URLs in your responses

The key components to remember are:

  1. The ProxyHeadersMiddleware from Starlette
  2. Configuring trusted proxy sources
  3. Accessing client information through the request object
  4. Being cautious about which proxy headers you trust

Additional Resources and Exercises

Further Reading

Exercises

  1. Basic Proxy Setup: Configure FastAPI with ProxyHeadersMiddleware and create an endpoint that displays all forwarded headers.

  2. Rate Limiter: Expand the rate limiter example to use Redis for distributed rate limiting in a multi-server environment.

  3. Security Exercise: Create middleware that validates that the X-Forwarded-For header follows the expected pattern and rejects suspicious requests.

  4. HTTPS Redirector: Build middleware that redirects all HTTP requests to HTTPS by checking the X-Forwarded-Proto header.

  5. Logging Enhancement: Create custom logging middleware that includes the original client IP from proxy headers in all log entries.

By mastering proxy headers in FastAPI, you'll be well-prepared to deploy your applications in professional, production environments with enhanced security and proper client identification.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)