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:
- Production Tomcat 8.5 / 9 / 10 on self-hosted or cloud hosts
- Java web apps fronted by Nginx reverse proxy for HTTP
- Compliance or security acceptance that requires middleware baseline hardening
- Apps that already saw deserialization attack alerts and need an emergency block
Prerequisites
- root or sudo on the Tomcat host
- Tomcat has `CATALINA_HOME` set (this tutorial defaults to `/opt/tomcat`)
- The app is currently reachable so you can regression-test after hardening
How it works
Tomcat risk concentrates in three places:
- Manager: default manager / host-manager without access control lets attackers brute default accounts (tomcat/tomcat, admin/admin). After login, uploading a WAR equals owning the server.
- AJP: port 8009 speaks AJP binary. Ghostcat (CVE-2020-1938) used it to read arbitrary files and even run commands. Most workloads no longer need AJP.
- Deserialization: if the app depends on components that use `ObjectInputStream` (e.g. old Commons-Collections 3.x), crafted serialized data can trigger RCE, typically chained with CVE-2017-12617 PUT-method file writes.
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.