# Fix Nginx proxy_pass Not Forwarding Host Header to Backend
Your Nginx reverse proxy forwards requests to a backend application, but the application generates URLs with the wrong hostname. Links point to http://localhost:8080 instead of https://app.example.com. The issue is the Host header.
When Nginx uses proxy_pass, the default behavior depends on your configuration. If you do not explicitly set the Host header, Nginx sends the upstream server the address specified in the proxy_pass directive, not the original client Host header.
The Problem
```nginx server { server_name app.example.com;
location / { proxy_pass http://127.0.0.1:8080; } } ```
With this configuration, the backend receives:
GET / HTTP/1.0
Host: 127.0.0.1:8080
Connection: closeThe backend then generates redirects and links using 127.0.0.1:8080, which breaks for external clients.
The Fix
```nginx server { server_name app.example.com;
location / { proxy_pass http://127.0.0.1:8080; 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; } } ```
Each header serves a purpose:
- Host: The original hostname from the client request
- X-Real-IP: The actual client IP address
- X-Forwarded-For: The full chain of proxy IPs
- X-Forwarded-Proto: The original protocol (http or https)
Variable Options for Host
The choice of variable matters:
proxy_set_header Host $host; # Original Host header from client
proxy_set_header Host $http_host; # Same as $host but includes port if present
proxy_set_header Host $server_name; # The server_name from this server block
proxy_set_header Host "app.example.com"; # Hardcoded literal valueUse $host in most cases. It contains the value from the client's Host header, or if that header is missing, the server name from the matching server block.
When proxy_pass Changes the Host Automatically
If your proxy_pass uses a variable, Nginx does not modify the Host header:
proxy_pass http://$upstream;But if proxy_pass uses a literal address, Nginx sets the Host header to that address:
proxy_pass http://127.0.0.1:8080;This is the source of the confusion. Many administrators assume the original Host header passes through by default, but it does not when using literal addresses.
Backend-Specific Considerations
Node.js/Express
```javascript const express = require('express'); const app = express(); app.set('trust proxy', 1);
app.get('/', (req, res) => {
console.log(req.hostname); // Now shows "app.example.com"
res.send(Hello from ${req.hostname});
});
```
Django
USE_X_FORWARDED_HOST = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
ALLOWED_HOSTS = ['app.example.com', 'localhost']Verifying the Fix
Create a debug endpoint in your backend:
app.get('/debug/headers', (req, res) => {
res.json({
host: req.headers.host,
xRealIp: req.headers['x-real-ip'],
xForwardedFor: req.headers['x-forwarded-for'],
xForwardedProto: req.headers['x-forwarded-proto']
});
});Request https://app.example.com/debug/headers through the proxy and verify the host field shows app.example.com, not 127.0.0.1:8080.