What is a reverse proxy?
- tool
- nginx
- versions
- nginx 1.x
- os
- any
A reverse proxy sits in front of one or more backend servers. Clients connect to the proxy as if it were the site; the proxy forwards each request to a backend and relays the response. Typical jobs: terminating TLS, routing paths or hostnames to different services, load balancing, caching, compression, and limiting request sizes. nginx, Caddy, HAProxy and Traefik are common reverse proxies.
A forward proxy is the opposite: it acts for clients (for example a corporate proxy) instead of for servers.
What the backend sees
Because the TCP connection now comes from the proxy, the backend sees the proxy's address. The proxy passes the original client details in headers:
mkdir -p logs temp
cat > app.js <<'EOF'
const s = require("http").createServer((req, res) => {
const h = (k) => req.headers[k] ?? "-";
res.end(`socket ${req.socket.remoteAddress} | host ${h("host")} | x-forwarded-for ${h("x-forwarded-for")} | x-forwarded-proto ${h("x-forwarded-proto")}\n`);
});
s.listen(3921, "127.0.0.1");
setTimeout(() => process.exit(0), 4000);
EOF
cat > nginx.conf <<'EOF'
pid logs/nginx.pid;
events {}
http {
access_log off;
server {
listen 127.0.0.1:3922;
location / {
proxy_pass http://127.0.0.1:3921;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}
EOF
node app.js &
nginx -p . -c nginx.conf -e logs/error.log &
sleep 1
echo "direct: $(curl -s -H 'Host: shop.example' http://127.0.0.1:3921/)"
echo "proxied: $(curl -s -H 'Host: shop.example' http://127.0.0.1:3922/)"
nginx -p . -c nginx.conf -e logs/error.log -s stopOutput
direct: socket 127.0.0.1 | host shop.example | x-forwarded-for - | x-forwarded-proto - proxied: socket 127.0.0.1 | host shop.example | x-forwarded-for 127.0.0.1 | x-forwarded-proto http
The application should read the client IP from X-Forwarded-For (or the standard Forwarded header) only when the request comes from a trusted proxy; otherwise clients can forge it. Frameworks have a setting for this, such as Express trust proxy or Django SECURE_PROXY_SSL_HEADER.