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

Standard Proxy Headers

nginx
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:

HeaderPurpose
HostOriginal hostname the client requested
X-Real-IPSingle client IP
X-Forwarded-ForChain of IPs through proxies
X-Forwarded-Protohttp 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):

nginx
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:

nginx
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

nginx
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)

nginx
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 / in proxy_pass
  • Common when backend serves /users but public URL is /api/users

Case 3: Mismatch bug

nginx
location /api {
    proxy_pass http://127.0.0.1:8080/v1/;
}

Behavior is subtle—test with curl:

bash
curl -v http://127.0.0.1/api/users

Warning

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:

nginx
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:

nginx
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:

nginx
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)

nginx
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

nginx
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.