Tomcat Security Hardening & Reverse Proxy Architecture
Mitigate information disclosure, suppress stack traces, isolate unprivileged execution, and integrate trusted reverse proxies (NGINX, Cloudflare, AWS ALB).
Mask default Apache-Coyote/1.1 banner and eliminate technical stack traces on 404/500 errors.
Accurately restore client IP addresses (X-Forwarded-For) and scheme (X-Forwarded-Proto) from trusted upstream gateways.
Harden JSESSIONID cookies against XSS extraction and Cross-Site Request Forgery (CSRF).
In $CATALINA_BASE/conf/server.xml, configure the <Connector> element with a masked server attribute, and add the ErrorReportValve inside <Host> to suppress debug traces on error pages.
<!-- 1. Mask the Server HTTP Response Header in server.xml -->
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
maxParameterCount="1000"
server="Web Application Server"
xpoweredBy="false" />
<!-- 2. Suppress Server Version and Stack Traces inside <Host name="localhost" ...> -->
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
<!-- ErrorReportValve hides Tomcat version and stack trace from error responses -->
<Valve className="org.apache.catalina.valves.ErrorReportValve"
showReport="false"
showServerInfo="false" />
<!-- Standard Access Log Valve -->
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b %D" />
</Host>
When running behind NGINX, Cloudflare, AWS ALB, or HAProxy, Tomcat sees the proxy IP as the remote client unless RemoteIpValve is configured inside the <Engine> or <Host> container:
<!-- Place inside <Engine name="Catalina" ...> in server.xml -->
<Valve className="org.apache.catalina.valves.RemoteIpValve"
internalProxies="127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1|10\.\d+\.\d+\.\d+|172\.(1[6-9]|2[0-9]|3[0-1])\.\d+\.\d+|192\.168\.\d+\.\d+"
remoteIpHeader="x-forwarded-for"
proxiesHeader="x-forwarded-by"
protocolHeader="x-forwarded-proto"
protocolHeaderHttpsValue="https" />
Corresponding NGINX Location Block
Configure NGINX upstream proxy directives to forward the real client IP and TLS status:
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;
# Timeouts and buffers for production stability
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
proxy_buffering on;
proxy_buffer_size 8k;
proxy_buffers 8 64k;
}
In $CATALINA_BASE/conf/context.xml, enforce useHttpOnly and sameSiteCookies on all session cookies:
<!-- In conf/context.xml -->
<Context useHttpOnly="true">
<!-- SameSite cookie attribute (available in Tomcat 8.5.42+, 9.0.21+, 10.0+) -->
<CookieProcessor className="org.apache.tomcat.util.http.Rfc6265CookieProcessor"
sameSiteCookies="lax" />
</Context>
Global Security Filters in conf/web.xml
<!-- 1. Enforce HTTPS only for Session Cookies -->
<session-config>
<session-timeout>30</session-timeout>
<cookie-config>
<http-only>true</http-only>
<secure>true</secure>
</cookie-config>
<tracking-mode>COOKIE</tracking-mode>
</session-config>
<!-- 2. HTTP Header Security Filter (HSTS, X-Frame-Options, X-Content-Type-Options) -->
<filter>
<filter-name>httpHeaderSecurity</filter-name>
<filter-class>org.apache.catalina.filters.HttpHeaderSecurityFilter</filter-class>
<init-param>
<param-name>hstsEnabled</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>hstsMaxAgeSeconds</param-name>
<param-value>31536000</param-value>
</init-param>
<init-param>
<param-name>antiClickJackingOption</param-name>
<param-value>DENY</param-value>
</init-param>
<init-param>
<param-name>blockContentTypeSniffingEnabled</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>httpHeaderSecurity</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
Delete webapps/ROOT, webapps/docs, webapps/examples, webapps/manager, and webapps/host-manager from production installations.
Set chmod 640 conf/* and chmod 750 bin/ conf/. Only allow the unprivileged tomcat user write access to logs/, temp/, and work/.
Comment out unused AJP (8009) or plaintext HTTP connectors in server.xml if terminating TLS at reverse proxy.
Set <Server port="-1" shutdown="SHUTDOWN"> to completely disable listening on the TCP shutdown socket in containers/systemd.