How to Set Up Nginx as a Reverse Proxy for Tomcat

How to Set Up Nginx as a Reverse Proxy for Tomcat

How to Set Up Nginx as a Reverse Proxy for Tomcat

Apache Tomcat has been running Java web applications for decades, and it still shows up everywhere from legacy enterprise systems to modern Spring Boot deployments that opt to run as WAR files inside Tomcat rather than an embedded server. What Tomcat doesn’t do particularly well on its own is handle modern TLS termination, static asset caching, and clean production URLs. That’s where Nginx comes in.

This guide covers setting up Nginx as a reverse proxy in front of Tomcat, including proper handling of Tomcat’s context paths, session behavior, and the specific headers Tomcat needs to generate correct redirect URLs.

Why Reverse Proxy Tomcat with Nginx

Tomcat can serve HTTPS directly through its own connector configuration, but doing certificate management, HTTP/2, and static file caching inside server.xml is clunky compared to Nginx, which was built for exactly that. A typical production setup runs Tomcat bound to localhost:8080 (HTTP, no TLS) and lets Nginx handle everything client-facing: TLS termination, compression, static file serving, and request routing to potentially multiple Tomcat instances or applications.

Requirements

Install Nginx:

sudo apt update && sudo apt install nginx -y

Confirm Tomcat is listening:

sudo ss -tlnp | grep 8080

Step 1: Configure Tomcat’s Connector for Proxy Awareness

Open conf/server.xml in the Tomcat installation directory (commonly /opt/tomcat/conf/server.xml or /etc/tomcat9/server.xml on Debian-based packages).

Locate the HTTP connector block and add proxy-related attributes:

<Connector port="8080" protocol="HTTP/1.1"
           connectionTimeout="20000"
           redirectPort="8443"
           proxyName="app.example.com"
           proxyPort="443"
           scheme="https"
           secure="true" />

Restart Tomcat after saving:

sudo systemctl restart tomcat

Step 2: Write the Nginx Configuration

Create the config file:

sudo nano /etc/nginx/sites-available/tomcat.conf

Complete example:

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

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

    ssl_certificate     /etc/letsencrypt/live/app.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;

    client_max_body_size 25M;

    # Serve static assets directly from disk if they live outside the WAR,
    # otherwise remove this block and let Tomcat serve everything.
    location /static/ {
        alias /var/www/app/static/;
        expires 30d;
        add_header Cache-Control "public";
    }

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

        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_read_timeout 120s;
        proxy_send_timeout 120s;
        proxy_connect_timeout 30s;

        proxy_buffering on;
        proxy_buffer_size 8k;
        proxy_buffers 8 8k;
    }
}

If the application is deployed under a specific context path instead of the ROOT context (for example /myapp instead of /), point the location block at that path explicitly:

location /myapp/ {
    proxy_pass http://127.0.0.1:8080/myapp/;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
}

Enable and test:

sudo ln -s /etc/nginx/sites-available/tomcat.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Step 3: Testing the Setup

Basic connectivity check:

curl -Ik https://app.example.com/

In the browser:

For applications using WebSockets, extend the config:

location /ws/ {
    proxy_pass http://127.0.0.1:8080/ws/;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
}

Troubleshooting

Redirects go to http://localhost:8080/... instead of the real domain This means Tomcat’s connector isn’t proxy-aware. Confirm proxyName, proxyPort, and scheme="https" were added correctly to the connector in server.xml, and that Tomcat was actually restarted after the change.

502 Bad Gateway Confirm Tomcat is actually running and bound to the expected port:

sudo systemctl status tomcat
sudo ss -tlnp | grep 8080

Check Tomcat’s own logs for startup errors:

tail -f /opt/tomcat/logs/catalina.out

404 errors for an application that works fine when accessed directly on port 8080 Usually a context path mismatch between the location block in Nginx and the actual deployed context. Confirm the WAR’s deployed path with:

ls /opt/tomcat/webapps/

Session gets lost after login (repeated login prompts) Check that cookies aren’t being duplicated or dropped due to a Host header mismatch. Also verify Tomcat’s useHttpOnly and cookie settings in context.xml aren’t conflicting with how Nginx passes headers.

Large file uploads fail with 413 Request Entity Too Large Increase client_max_body_size in the Nginx server block, and check Tomcat’s own maxPostSize and maxSwallowSize connector attributes, which can independently cap upload size even if Nginx allows it through.

Security Considerations

add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
proxy_hide_header Server;
add_header Server "web";

Performance Tips

gzip on;
gzip_types text/plain text/css application/javascript application/json;
gzip_min_length 1024;
upstream tomcat_backend {
    server 127.0.0.1:8080;
    keepalive 32;
}

Then reference proxy_pass http://tomcat_backend; and add proxy_set_header Connection ""; to enable persistent connections.

Real-World Use Cases

Best Practices Checklist

Tomcat behind Nginx is one of the more mature, well-trodden reverse proxy setups out there, but the two details that trip people up are almost always the same: forgetting to make the connector proxy-aware (proxyName/proxyPort/scheme), and mismatched context paths between the WAR deployment and the Nginx config. Get those two things right, and everything else is standard reverse proxy configuration.

Exit mobile version