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

  • Tomcat installed and running, reachable internally on its default port (8080 for HTTP).
  • Nginx installed on the same host or a separate front-end server.
  • A domain name pointing at the Nginx server.
  • A TLS certificate (Let’s Encrypt/Certbot is the standard free option).
  • Access to Tomcat’s conf/server.xml if adjusting the connector for proxy awareness.

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" />
  • proxyName and proxyPort tell Tomcat what hostname and port to use when generating absolute URLs (redirects, form actions), rather than exposing its internal bind address.
  • scheme="https" and secure="true" make Tomcat treat the connection as secure for the purposes of things like request.isSecure() in application code, even though Nginx is the one actually terminating TLS.

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:

  • Load the application’s login or landing page, and confirm session cookies are set with the Secure flag (visible in dev tools under Application/Cookies).
  • Submit a login form and check that any post-login redirect stays on the correct external domain rather than falling back to localhost:8080 or an internal IP — this confirms proxyName/proxyPort are working correctly.
  • If the app uses WebSockets (common with Spring’s STOMP/SockJS setups), verify live features work; if not already added, WebSocket proxying needs its own headers (see below).

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

  • Bind Tomcat’s connector to 127.0.0.1 once the proxy is verified working, using the address attribute on the connector, so it’s unreachable except through Nginx.
  • Disable the Tomcat Manager and Host Manager apps in production unless actively needed, and if they are needed, restrict access by IP at the Nginx layer rather than relying solely on Tomcat’s own authentication.
  • Set standard security headers:
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
  • Hide the Tomcat version banner by editing catalina.jar‘s ServerInfo.properties or using the Server header rewrite in Nginx:
proxy_hide_header Server;
add_header Server "web";
  • Keep Tomcat patched. A reverse proxy doesn’t protect against vulnerabilities in the application server itself; it only controls the network path to it.

Performance Tips

  • Serve static assets directly through Nginx rather than routing them through Tomcat’s servlet container whenever the app’s build process outputs them to a known directory — this is dramatically faster and reduces load on JVM threads.
  • Enable gzip compression for text-based responses:
gzip on;
gzip_types text/plain text/css application/javascript application/json;
gzip_min_length 1024;
  • Tune Tomcat’s connector thread pool (maxThreads, acceptCount) to match expected concurrency; Nginx can queue and buffer requests, but Tomcat still needs enough worker threads to process them.
  • Use keepalive connections between Nginx and Tomcat to avoid the overhead of establishing a new TCP connection per request:
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

  • Legacy enterprise Java applications that can’t easily be rearchitected but still need modern TLS and a professional-looking domain.
  • Multiple WAR deployments on one Tomcat instance, each proxied under a different subdomain or path prefix by Nginx.
  • Blue-green deployments, where Nginx switches traffic between two Tomcat instances running different application versions with a simple config reload.
  • API gateways for Spring Boot WAR deployments, where Nginx handles rate limiting and routing across several backend Tomcat services.

Best Practices Checklist

  • Set proxyName, proxyPort, and scheme="https" on the Tomcat connector whenever it’s placed behind a reverse proxy.
  • Pass X-Forwarded-For, X-Forwarded-Proto, and Host headers consistently.
  • Bind Tomcat to 127.0.0.1 once the proxy path is confirmed working.
  • Serve static assets directly from Nginx where possible instead of through the servlet container.
  • Match context paths exactly between the deployed WAR and the Nginx location block.
  • Use keepalive connections to the upstream to reduce connection overhead under load.
  • Disable or restrict the Tomcat Manager application in production.

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.

Total
1
Shares

Leave a Reply

Previous Post
How to Set Up Nginx as a Reverse Proxy for Apache Solr

How to Set Up Nginx as a Reverse Proxy for Apache Solr

Next Post
How to Set Up Nginx as a Reverse Proxy for Apache Superset

How to Set Up Nginx as a Reverse Proxy for Apache Superset

Related Posts