← News
/PLAYBOOK · 2026-08-05

Tomcat Security Hardening in Practice: Deployment Baseline and Deserialization Defense

Tomcat is the most common Java web middleware and a concentrated attack surface. This guide covers manager lockdown, connector config, deserialization fixes, and audit checks so you close high-risk gaps at deploy time.

Tomcat security hardening is mandatory before a Java web app goes live: Tomcat has long topped the web-middleware attack-surface list. Default manager, the AJP port, and deserialization components, once exposed, let an attacker take the host. This article gives a copy-ready plan across deployment baseline, connector config, and deserialization defense.

Applicable scenarios

Use this plan directly when:

Prerequisites

How it works

Tomcat risk concentrates in three places:

Steps

1. Minimal install and privilege isolation

`# Run as a dedicated non-root user; do not leave the tomcat directory owned by root
useradd -r -s /sbin/nologin -d /opt/tomcat tomcat
chown -R tomcat:tomcat /opt/tomcat
# Remove default docs and example apps to shrink the attack surface
rm -rf /opt/tomcat/webapps/examples /opt/tomcat/webapps/docs /opt/tomcat/webapps/ROOT
# Remove the management console apps (if you must keep them, see step 2 for separate hardening)
rm -rf /opt/tomcat/webapps/manager /opt/tomcat/webapps/host-manager`

2. Strong manager access control

If you must keep manager, first set strong passwords in `conf/tomcat-users.xml`, then add a Valve allowlist for the manager app in `conf/server.xml`:

`<Context path="/manager" privileged="true">
  <Valve className="org.apache.catalina.valves.RemoteAddrValve"
         allow="192.168.1.0/24|127.0.0.1"
         denyStatus="403"/>
</Context>`

Also split the management port from the app port; management should be intranet-only.

3. Disable AJP and tighten connectors

`# Comment out or delete the port 8009 listener in conf/server.xml
<!-- <Connector port="8009" protocol="AJP/1.3" redirectPort="8443"/> -->
# Keep only the HTTP/1.1 connector and add request limits
<Connector port="8080" protocol="HTTP/1.1"
           maxThreads="200" acceptCount="100"
           connectionTimeout="20000"
           maxHttpHeaderSize="8192"
           server="None"/>`

Key knobs: server=None hides the version; maxHttpHeaderSize caps header size against header injection; maxThreads / acceptCount cap concurrency against resource exhaustion.

4. Deserialization vulnerability defense

Most deserialization RCE comes from unsafe third-party deps. Handle in priority order:

`# 1) Upgrade to a patched version: 8.5.100+ / 9.0.99+ / 10.1.31+ (includes the CVE-2026-34486 fix)
cd /opt/tomcat/bin && ./version.sh | grep "Server version"
# 2) Scan and upgrade high-risk dependencies (focus on commons-collections 3.2.1 and earlier)
grep -R "commons-collections" /opt/tomcat/lib /your-app/WEB-INF/lib
# 3) Globally disable the PUT/DELETE file-write channel (CVE-2017-12617 fix)
#    Add the following for DefaultServlet in conf/web.xml:
<init-param>
  <param-name>readonly</param-name>
  <param-value>true</param-value>
</init-param>`

5. Nginx front-end protection

`# Expose only 80/443; do not expose 8080 or 8009
server {
    listen 443 ssl;
    server_name app.example.com;
    # Block common scan paths
    location ~* ^/(manager|host-manager|WEB-INF) {
        deny all;
        return 403;
    }
    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}`

Verification

`# Verify 8009 is closed
ss -tlnp | grep 8009  # should produce no output
# Verify the manager is denied from outside
curl -s -o /dev/null -w "%{http_code}
" http://your-ip:8080/manager/html  # 403
# Verify version information is hidden
curl -sI http://your-ip:8080/ | grep -i server  # should be Server: None
# Verify deserialization dependencies are fixed
java -jar ysoserial.jar CommonsCollections1 "id" | base64 | head -c 50 | grep -q .   && echo "Re-check the dependency audit with OWASP Dependency-Check"`

FAQ

Q1: App errors after disabling AJP?

Modern Spring Boot embedded Tomcat and pure HTTP reverse-proxy setups do not depend on AJP. If an internal module still uses AJP, keep it but bind 127.0.0.1 (`address="127.0.0.1"`), upgrade to a patched release, set `secretRequired`, and configure a shared secret.

Q2: Will upgrading Tomcat break the existing app?

Minor upgrades (e.g. 8.5.x → 8.5.y) are usually compatible. Major jumps (9 → 10) can break due to the Jakarta EE namespace change (`javax.*` → `jakarta.*`); validate in a test environment first. For deserialization fixes, upgrade the dependency jars first rather than the whole Tomcat.

Q3: How to find existing backdoor WARs quickly?

Compare `webapps` file timestamps against deploy records; inspect recently added `.war` files. Use `unzip -l xxx.war` to look for `shell.jsp`, `cmd.jsp`, and check `catalina.out` for abnormal deploy logs.

Summary

Tomcat hardening is "default-minimal, explicit-tighten": drop unused apps, isolate the manager, shut AJP, upgrade deserialization deps, then add Nginx front-end filters. After these five steps the main high-risk entry points are closed. Fold this checklist into CI so every release runs the security baseline automatically.