Tomcat hardening should focus on connector security, management-app isolation, strict JVM/process controls, and proper systemd sandboxing.
Version: This guide covers Tomcat 9, 10, and 11.
In production, remove default applications that reveal version information:
# Remove documentation and examples
sudo rm -rf /opt/tomcat/webapps/docs
sudo rm -rf /opt/tomcat/webapps/examples
sudo rm -rf /opt/tomcat/webapps/ROOT
# Or replace ROOT with your application
# sudo rm -rf /opt/tomcat/webapps/ROOT/*
# cp your-app.war /opt/tomcat/webapps/ROOT.war
In tomcat-users.xml:
<tomcat-users>
<role rolename="manager-gui"/>
<role rolename="admin-gui"/>
<user username="admin" password="STRONG_PASSWORD" roles="manager-gui,admin-gui"/>
</tomcat-users>
In Manager context.xml (/opt/tomcat/webapps/manager/META-INF/context.xml):
<Context>
<Valve className="org.apache.catalina.valves.RemoteAddrValve"
allow="127\.\d+\.\d+\.\d+|::1|YOUR_ADMIN_IP_ONLY" />
</Context>
Best practices:
allow=".*" in productionIn server.xml Host element:
<Host name="localhost"
appBase="webapps"
unpackWARs="true"
autoDeploy="false"
deployOnStartup="false"
deployXML="false">
If you don’t need AJP (most deployments don’t):
<!-- Comment out or remove AJP connector -->
<!--
<Connector port="8009" protocol="AJP/1.3" ... />
-->
If AJP is required:
<Connector port="8009"
protocol="AJP/1.3"
address="127.0.0.1"
redirectPort="8443"
secret="YOUR_STRONG_SECRET_MIN_32_CHARS"
allowedRequestAttributesPattern=".*" />
For HTTPS connectors:
<Connector port="8443"
protocol="org.apache.coyote.http11.Http11NioProtocol"
SSLEnabled="true"
scheme="https"
secure="true"
sslProtocol="TLSv1.2"
sslEnabledProtocols="TLSv1.2,TLSv1.3"
ciphers="TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256"
useBodyEncodingForURI="true">
<SSLHostConfig>
<Certificate certificateKeystoreFile="/opt/tomcat/ssl/keystore.jks"
certificateKeystorePassword="KEYSTORE_PASSWORD"
type="RSA" />
</SSLHostConfig>
</Connector>
Disable weak ciphers and protocols:
<Connector port="8080"
protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
maxParameterCount="1000"
maxPostSize="2097152"
maxHttpHeaderSize="8192"
maxThreads="200"
minSpareThreads="25"
allowTrace="false"
server="Apache"
xpoweredBy="false"
rejectSuspiciousURIs="true" />
| Attribute | Purpose |
|---|---|
maxParameterCount |
Prevents DoS via excessive parameters |
maxPostSize |
Limits POST body size |
maxHttpHeaderSize |
Limits header size |
allowTrace |
Prevents TRACE method XSS attacks |
server |
Hides Tomcat version |
xpoweredBy |
Removes X-Powered-By header |
rejectSuspiciousURIs |
Blocks directory traversal patterns |
Already configured in setup guide:
[Service]
User=tomcat
Group=tomcat
Safe baseline configuration:
[Service]
# User isolation
User=tomcat
Group=tomcat
# Filesystem protection
ProtectSystem=full
ProtectHome=true
PrivateTmp=yes
ProtectControlGroups=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectClock=yes
# Privilege restrictions
NoNewPrivileges=yes
CapabilityBoundingSet=
AmbientCapabilities=
# System call filtering
SystemCallArchitectures=native
LockPersonality=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
RestrictNamespaces=yes
# Required writable paths
ReadWritePaths=/opt/tomcat/logs
ReadWritePaths=/opt/tomcat/temp
ReadWritePaths=/opt/tomcat/work
ReadWritePaths=/opt/tomcat/webapps
Advanced hardening (test thoroughly - may break some apps):
[Service]
ProtectSystem=strict
PrivateDevices=yes
ProtectHostname=yes
ProtectProc=invisible
# Network restrictions (if no raw socket access needed)
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
# Additional restrictions
MemoryDenyWriteExecute=yes
RemoveIPC=yes
UMask=0077
# Explicit writable paths
ReadWritePaths=/opt/tomcat/logs
ReadWritePaths=/opt/tomcat/temp
ReadWritePaths=/opt/tomcat/work
ReadWritePaths=/opt/tomcat/webapps
ReadWritePaths=/etc/tomcat # Only if config written at runtime
ProtectSystem=strict| Issue | Cause | Solution |
|---|---|---|
| Log writing fails | /opt/tomcat/logs is read-only |
Add ReadWritePaths=/opt/tomcat/logs |
| Temp file errors | /opt/tomcat/temp is read-only |
Add ReadWritePaths=/opt/tomcat/temp |
| Session persistence fails | /opt/tomcat/work is read-only |
Add ReadWritePaths=/opt/tomcat/work |
| Webapp deployment fails | /opt/tomcat/webapps is read-only |
Add ReadWritePaths=/opt/tomcat/webapps |
| Config write fails | /etc/tomcat is read-only |
Add ReadWritePaths=/etc/tomcat or move config |
# Check security status
systemd-analyze security tomcat
# Test with increasing hardening levels
# Start with ProtectSystem=full, then try strict
# Monitor for permission errors
journalctl -u tomcat -f | grep -i "permission\|read-only"
Disable or secure the shutdown port in server.xml:
<!-- Recommended: Disable shutdown port -->
<Server port="-1" shutdown="SHUTDOWN">
<!-- Alternative: Use strong password -->
<Server port="8005" shutdown="VERY_STRONG_RANDOM_PASSWORD_MIN_32_CHARS">
In server.xml:
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>
<Valve className="org.apache.catalina.valves.ErrorReportValve"
showServerInfo="false"
showReport="false" />
<Valve className="org.apache.catalina.valves.AccessLogValve"
directory="logs"
prefix="localhost_access_log"
suffix=".txt"
pattern="%h %l %u %t "%r" %s %b %D "%{User-Agent}i""
rotate="true"
maxDays="90" />
For admin applications:
<Valve className="org.apache.catalina.valves.RemoteAddrValve"
allow="127\.0\.0\.1|::1|YOUR_ADMIN_IP_1|YOUR_ADMIN_IP_2"
denyStatus="404" />
In context.xml or specific application context:
<Context>
<Manager sessionAttributeValueClassNameFilter="java\.lang\.(?:Boolean|Integer|Long|Number|String)|org\.apache\.catalina\.realm\.GenericPrincipal\$SerializablePrincipal|\[Ljava.lang.String;"
warnOnSessionAttributeFilterFailure="true"/>
<CookieProcessor className="org.apache.tomcat.util.http.Rfc6265CookieProcessor"
sameSiteCookies="strict"/>
</Context>
# Tomcat installation
sudo chown -R tomcat:tomcat /opt/tomcat
sudo chmod -R 750 /opt/tomcat
# Configuration files
sudo chmod 640 /opt/tomcat/conf/*.xml
sudo chmod 600 /opt/tomcat/conf/tomcat-users.xml
# Logs
sudo chmod 750 /opt/tomcat/logs
sudo chmod 640 /opt/tomcat/logs/*.log
# systemd service
sudo chmod 644 /etc/systemd/system/tomcat.service
# UFW (Debian/Ubuntu)
sudo ufw allow 8080/tcp # HTTP
sudo ufw allow 8443/tcp # HTTPS (if direct)
# sudo ufw allow 8009/tcp # AJP (only if needed, restrict to specific IPs)
sudo ufw deny 8005/tcp # Shutdown port
# firewalld (RHEL/CentOS)
sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --permanent --add-port=8443/tcp
# sudo firewall-cmd --permanent --add-port=8009/tcp --add-source=ADMIN_IP
sudo firewall-cmd --reload
Put Tomcat behind Nginx or Apache for TLS termination:
server {
listen 443 ssl http2;
server_name app.example.com;
# TLS configuration
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;
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;
}
# Block direct access to Manager
location /manager {
allow 127.0.0.1;
allow YOUR_ADMIN_IP;
deny all;
proxy_pass http://127.0.0.1:8080;
}
location /host-manager {
deny all;
}
}
# Check connector configuration
grep -E "<Connector|AJP|sslEnabledProtocols|ciphers" /opt/tomcat/conf/server.xml
# Check deployed webapps
ls -la /opt/tomcat/webapps
# Check systemd hardening
sudo systemctl cat tomcat | grep -E "NoNewPrivileges|ProtectSystem|PrivateTmp|ReadWritePaths"
# Check listening ports
sudo ss -tlnp | grep java
# Check for version disclosure
curl -I http://localhost:8080 | grep -i server
# Test Manager access (should fail from unauthorized IPs)
curl -u admin:password http://localhost:8080/manager/html
# Check logs for security events
grep -i "unauthorized\|forbidden\|denied\|failed" /opt/tomcat/logs/*.log | tail -20
tomcat)ReadWritePaths=Note: Always use the latest stable version of Apache Tomcat and keep your Java runtime updated to protect against known vulnerabilities. Regularly check the security pages for updates and advisories. Subscribe to the Tomcat security mailing list for critical announcements.
Any questions?
Feel free to contact us. Find all contact information on our contact page.