Skip to main content

HTTP Proxy Integration — Mastering proxy_pass

Using proxy_pass in Nginx to integrate with Tomcat is the most standard approach. This chapter covers everything you must know in production: header forwarding, real IP propagation, and upstream configuration.


Basic proxy_pass Configuration​

server {
listen 80;
server_name example.com;

location / {
proxy_pass http://127.0.0.1:8080;
}
}

This alone enables Nginx to forward requests to Tomcat. In production, you'll need additional settings for header forwarding, timeouts, and buffering.


Defining Backend with upstream Block​

The upstream block makes it explicit and easy to scale to multiple instances.

# /etc/nginx/conf.d/upstream.conf
upstream tomcat_backend {
server 127.0.0.1:8080;
keepalive 32;
}
# /etc/nginx/sites-available/myapp.conf
server {
listen 443 ssl;
server_name example.com;

ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

location / {
proxy_pass http://tomcat_backend;
}
}

Required Proxy Headers​

# /etc/nginx/snippets/proxy-headers.conf
# Include this file for reuse

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;
proxy_set_header X-Forwarded-Port $server_port;

# Required for keepalive connections
proxy_http_version 1.1;
proxy_set_header Connection "";
HeaderValueRole in Tomcat
Host$hostVirtual host matching
X-Real-IP$remote_addrReal client IP
X-Forwarded-For$proxy_add_x_forwarded_forProxy chain IP list
X-Forwarded-Proto$schemeOriginal protocol (http/https)

Real IP Propagation (RemoteIpValve)​

Configure Tomcat to return the real client IP from request.getRemoteAddr() instead of Nginx's IP (127.0.0.1).

Nginx Configuration​

set_real_ip_from   127.0.0.1;
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 172.16.0.0/12;
set_real_ip_from 192.168.0.0/16;
real_ip_header X-Forwarded-For;
real_ip_recursive on;

Tomcat server.xml Configuration​

<Valve className="org.apache.catalina.valves.RemoteIpValve"
remoteIpHeader="x-forwarded-for"
protocolHeader="x-forwarded-proto"
internalProxies="127\.0\.0\.1|10\.\d+\.\d+\.\d+|192\.168\.\d+\.\d+"/>

With this:

  • request.getRemoteAddr() → returns actual client IP
  • request.isSecure() → returns true when Nginx received HTTPS
  • Logs record the real client IP

Complete Proxy Configuration Example​

upstream tomcat_backend {
server 127.0.0.1:8080;
keepalive 32;
}

server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}

server {
listen 443 ssl http2;
server_name example.com;

ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;

set_real_ip_from 127.0.0.1;
real_ip_header X-Forwarded-For;
real_ip_recursive on;

client_max_body_size 50m;
client_body_buffer_size 128k;

location / {
proxy_pass http://tomcat_backend;

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;
proxy_set_header X-Forwarded-Port $server_port;

proxy_http_version 1.1;
proxy_set_header Connection "";

proxy_connect_timeout 10s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;

proxy_buffering on;
proxy_buffer_size 16k;
proxy_buffers 4 64k;
proxy_busy_buffers_size 128k;
}

error_page 502 503 504 /error/5xx.html;
location = /error/5xx.html {
root /var/www/myapp;
internal;
}

access_log /var/log/nginx/myapp_access.log combined;
error_log /var/log/nginx/myapp_error.log warn;
}

proxy_pass URI Handling Caveat​

The behavior changes depending on whether you include a URI in proxy_pass.

# Without URI — forwards the full location path
location /app/ {
proxy_pass http://127.0.0.1:8080;
# /app/foo → http://127.0.0.1:8080/app/foo
}

# With URI — replaces the location path
location /app/ {
proxy_pass http://127.0.0.1:8080/;
# /app/foo → http://127.0.0.1:8080/foo
}

# API path transformation example
location /api/ {
proxy_pass http://127.0.0.1:8080/api/v1/;
# /api/users → http://127.0.0.1:8080/api/v1/users
}

Special Path Handling​

WebSocket Proxy​

location /ws/ {
proxy_pass http://tomcat_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}

SSE (Server-Sent Events) Proxy​

location /events/ {
proxy_pass http://tomcat_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_buffering off; # Must disable buffering for SSE
proxy_cache off;
proxy_read_timeout 3600s;
}

File Upload Path​

location /upload/ {
proxy_pass http://tomcat_backend;
client_max_body_size 500m;
proxy_request_buffering off;
proxy_read_timeout 600s;
}

Validating Proxy Configuration​

# Validate Nginx config
sudo nginx -t

# Reload (zero-downtime)
sudo nginx -s reload

# Test proxy connection
curl -v https://example.com/api/test
# Check X-Forwarded-For, X-Forwarded-Proto headers

Summary​

SettingDetails
Upstreamupstream block + keepalive 32
Required headersHost, X-Real-IP, X-Forwarded-For, X-Forwarded-Proto
HTTP versionproxy_http_version 1.1 + Connection "" (required for keepalive)
Real IPNginx: real_ip_header + Tomcat: RemoteIpValve
WebSocketUpgrade: $http_upgrade + Connection: upgrade
SSEproxy_buffering off + proxy_cache off