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 "";
| Header | Value | Role in Tomcat |
|---|---|---|
Host | $host | Virtual host matching |
X-Real-IP | $remote_addr | Real client IP |
X-Forwarded-For | $proxy_add_x_forwarded_for | Proxy chain IP list |
X-Forwarded-Proto | $scheme | Original 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 IPrequest.isSecure()→ returnstruewhen 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
| Setting | Details |
|---|---|
| Upstream | upstream block + keepalive 32 |
| Required headers | Host, X-Real-IP, X-Forwarded-For, X-Forwarded-Proto |
| HTTP version | proxy_http_version 1.1 + Connection "" (required for keepalive) |
| Real IP | Nginx: real_ip_header + Tomcat: RemoteIpValve |
| WebSocket | Upgrade: $http_upgrade + Connection: upgrade |
| SSE | proxy_buffering off + proxy_cache off |