Proxy Headers and Path Rules
Introduction
Reverse proxies must send the right HTTP headers so backends know the original client IP, hostname, and scheme (HTTP vs HTTPS). Path rules around proxy_pass trailing slashes trip up even experienced operators. This chapter standardizes headers for Spring Boot and Node, and explains URI rewriting pitfalls.
Prerequisites
- Reverse Proxy Basics
- A working
proxy_passto127.0.0.1:8080or:3000
Standard Proxy Headers
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
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;
}Code explanation:
| Header | Purpose |
|---|---|
Host | Original hostname the client requested |
X-Real-IP | Single client IP |
X-Forwarded-For | Chain of IPs through proxies |
X-Forwarded-Proto | http or https as seen by client |
Backends use these for redirects, logging, and rate limits. Spring Boot can trust forwarded headers with server.forward-headers-strategy=framework (Spring Boot 2.2+).
Reusable include Snippet
Create /etc/nginx/proxy_params (Debian packages often ship one):
proxy_http_version 1.1;
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;Use in site config:
location / {
proxy_pass http://127.0.0.1:8080;
include /etc/nginx/proxy_params;
}proxy_pass Trailing Slash Rules
The URI passed upstream changes when location and proxy_pass combine with or without trailing slashes.
Case 1: No URI path in proxy_pass
location /api/ {
proxy_pass http://127.0.0.1:8080;
}Request: GET /api/users → upstream: GET /api/users
Full path preserved.
Case 2: URI path in proxy_pass (strip prefix)
location /api/ {
proxy_pass http://127.0.0.1:8080/;
}Request: GET /api/users → upstream: GET /users
Code explanation:
- Matching prefix
/api/is replaced by/inproxy_pass - Common when backend serves
/usersbut public URL is/api/users
Case 3: Mismatch bug
location /api {
proxy_pass http://127.0.0.1:8080/v1/;
}Behavior is subtle—test with curl:
curl -v http://127.0.0.1/api/usersWarning
Test Path Mapping After Every Change
Wrong slashes cause 404 on the backend while Nginx returns 502/404. Log upstream request path in the app during setup.
Split Public API and Internal Routes
Backend expects /v1/users, clients call /api/v1/users:
location /api/ {
proxy_pass http://127.0.0.1:8080/;
include /etc/nginx/proxy_params;
}Client: /api/v1/users → upstream: /v1/users if backend mounted at root with /v1 routes—or adjust to match your framework’s context path.
Spring Boot with server.servlet.context-path=/api may need no strip—preserve full path:
location /api/ {
proxy_pass http://127.0.0.1:8080;
}Align Nginx with application.properties—one source of truth for the prefix.
Timeouts
Slow APIs may need higher limits:
location / {
proxy_pass http://127.0.0.1:8080;
include /etc/nginx/proxy_params;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}504 errors often mean proxy_read_timeout too low for long reports or exports.
Buffering (Awareness)
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;Default buffering is fine for most APIs. Streaming responses (SSE) may need proxy_buffering off;—special case.
Full API server Example
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://127.0.0.1:8080;
include /etc/nginx/proxy_params;
proxy_read_timeout 120s;
}
}FAQ
Must I set all X-Forwarded-* headers?
Minimum for many apps: Host, X-Forwarded-For, X-Forwarded-Proto. Add others as frameworks require.
X-Forwarded-For trust?
Apps must not trust spoofed headers from clients—only from your Nginx hop. Configure framework trusted proxy settings.
proxy_pass with HTTPS upstream?
proxy_pass https://internal.example.com; plus proxy_ssl_* directives for cert verification (advanced).
Why 404 only through Nginx?
Often path strip mismatch—compare direct curl localhost:8080/... vs through Nginx.
Can I pass custom headers?
proxy_set_header X-Request-Id $request_id; if request_id module enabled, or map from $http_x_request_id.
Difference from rewrite?
proxy_pass forwards to another server; rewrite changes URI on same server before other handlers.