What this guide covers
Apache Tomcat is the world's most widely used Java servlet container, powering everything from small Java web apps to enterprise Spring Boot deployments. Unlike Apache HTTP Server, Tomcat manages SSL through Java's keytool utility and a keystore file rather than PEM certificates placed on the filesystem. This guide walks through the complete installation process for the three actively supported Tomcat branches:
- Tomcat 9.0 (latest: 9.0.118) — implements Servlet 4.0 / JSP 2.3, still widely used in enterprise environments
- Tomcat 10.1 (latest: 10.1.55) — implements Servlet 6.0 / JSP 3.1, uses Jakarta EE namespace
- Tomcat 11.0 (latest: 11.0.22) — implements Servlet 6.1 / JSP 4.0, current development focus
Tomcat 8.5 and 10.0 are end-of-life and no longer receive security patches. If you are still running either branch, migration to Tomcat 9.0, 10.1, or 11.0 is strongly recommended before installing a new certificate.
All three active branches use the same SSLHostConfig / Certificate nested syntax in server.xml. Every command and snippet in this guide applies to all three unless noted otherwise.
Prerequisites
- Root or sudo access on the server (or equivalent Windows Administrator rights)
- Tomcat 9.0, 10.1, or 11.0 installed and running — check with: $CATALINA_HOME/bin/version.sh
- Java 11 or later installed — check with: java -version
- A domain name with an A record pointing to the server's public IP
- TCP port 8443 (or 443) open in your firewall
- An SSL certificate issued by your CA — the .crt file plus the CA bundle (or intermediate .crt files)
- The private key (.key) you used to generate the CSR — never share this file
If you have not ordered a certificate yet, see My-SSL's SSL certificate options. DV certificates are issued within minutes after domain validation. Have your issued .crt file and CA bundle ready before starting.
Certificate files you need
Your CA delivers a zip archive. Unlike Apache or Nginx, Tomcat does not read PEM files directly — you need to combine everything into a single keystore file. The table below shows what you receive and what role each file plays:
| File from CA | Role | Used in |
|---|---|---|
| your_domain.crt | Your server (leaf) certificate | PKCS12 bundle |
| ca_bundle.crt | Intermediate + root CA chain | PKCS12 bundle |
| your_domain.key | Private key (keep secret) | PKCS12 bundle |
| keystore.p12 | Final PKCS12 keystore (you create this) | server.xml |
PKCS12 vs JKS: PKCS12 (.p12 or .pfx) is the recommended format — it is the Java default since Java 9, interoperates with OpenSSL and Windows, and is the format commercial CAs typically deliver. This guide uses PKCS12 throughout. JKS still works but is legacy.
Step 1: Generate a CSR with keytool
If you have already received your certificate from the CA (you submitted a CSR externally), skip to . If you need to generate the CSR now, keytool creates a PKCS12 keystore and a CSR in one command:
Option A — Generate CSR and private key with keytool (recommended)
# Generate a new 2048-bit RSA private key and CSR inside a PKCS12 keystore keytool -genkeypair \ -alias tomcat \ -keyalg RSA \ -keysize 2048 \ -storetype PKCS12 \ -keystore /opt/tomcat/keystore.p12 \ -storepass changeit \ -dname "CN=yourdomain.com, OU=IT, O=Your Company, L=City, ST=State, C=US" \ -ext SAN=dns:yourdomain.com,dns:www.yourdomain.com \ -validity 1 # Export the CSR to submit to your CA keytool -certreq \ -alias tomcat \ -storetype PKCS12 \ -keystore /opt/tomcat/keystore.p12 \ -storepass changeit \ -file yourdomain.csr
- Replace
changeitwith a strong password — this protects your private key. - The
-ext SANflag adds Subject Alternative Names. Most CAs require at least the primary domain in SAN. - The
-validity 1value is a placeholder; your CA ignores it and sets validity based on the order.
Option B — Generate CSR with OpenSSL (if you prefer PEM format)
# Generate private key and CSR with OpenSSL openssl req -new -newkey rsa:2048 -nodes \ -keyout yourdomain.key \ -out yourdomain.csr \ -subj "/C=US/ST=State/L=City/O=Your Company/CN=yourdomain.com" # Submit yourdomain.csr to your CA # After issuance, continue to Step 2 to package the files into a PKCS12 keystore
The CSR generation guide covers both methods in depth, or use the online CSR Generator to create a CSR and matching key pair in your browser.
Step 2: Build the PKCS12 keystore
After your CA issues the certificate, you need to package the server certificate, intermediate CA chain, and private key into a single PKCS12 file that Tomcat reads.
Path A — You used keytool in Step 1 (existing keystore)
Your private key is already in keystore.p12. Import the CA-signed certificate and the CA bundle back into the same keystore:
# 1. Combine the server cert and CA bundle into a fullchain PEM cat your_domain.crt ca_bundle.crt > your_domain_fullchain.crt # 2. Import the fullchain certificate into the existing keystore # (must use the same alias as the key — "tomcat") keytool -importcert \ -alias tomcat \ -storetype PKCS12 \ -keystore /opt/tomcat/keystore.p12 \ -storepass changeit \ -file your_domain_fullchain.crt \ -trustcacerts
If prompted "Trust this certificate? [no]:", type yes for the root CA entry.
Path B — You used OpenSSL in Step 1 (PEM key file)
Package the PEM key, server certificate, and CA bundle together with OpenSSL:
# Create a PKCS12 keystore from PEM files openssl pkcs12 -export \ -in your_domain.crt \ -inkey your_domain.key \ -out /opt/tomcat/keystore.p12 \ -name tomcat \ -CAfile ca_bundle.crt \ -caname root \ -chain \ -passout pass:changeit
- The
-name tomcatsets the key alias — this must match thecertificateKeyAliasin server.xml (defaults totomcat). - Replace
changeitwith the same strong password you plan to use in server.xml.
Protect the keystore. Set file permissions so only the Tomcat process user can read it: chmod 600 /opt/tomcat/keystore.p12. Never place the keystore inside Tomcat's webapps/ directory or a web-accessible path.
Verify the keystore contents
keytool -list -v \ -storetype PKCS12 \ -keystore /opt/tomcat/keystore.p12 \ -storepass changeit # Expected output includes: # Alias name: tomcat # Entry type: PrivateKeyEntry # Certificate chain length: 3 (leaf + intermediate + root) # Subject: CN=yourdomain.com, ... # Valid from: ... until: ...
A chain length of 2 or 3 is correct (leaf + at least one intermediate). A chain length of 1 means the intermediate was not bundled — the certificate will show as untrusted on mobile clients. See our certificate chain guide for how to fix a broken chain.
Step 3: Configure the HTTPS connector in server.xml
Open $CATALINA_HOME/conf/server.xml in a text editor. Find the commented-out HTTPS connector block (usually around line 88) and replace it with the following. The SSLHostConfig / Certificate nested syntax is required for Tomcat 9.0+ and is the only supported approach in Tomcat 11:
Tomcat 9.0, 10.1, and 11.0 — modern SSLHostConfig syntax
<!-- HTTPS connector on port 8443 (change to 443 if Tomcat has net_bind_service) -->
<Connector port="8443"
protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="150"
SSLEnabled="true">
<SSLHostConfig
protocols="TLSv1.2+TLSv1.3"
ciphers="TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256"
honorCipherOrder="false">
<Certificate
certificateKeystoreFile="/opt/tomcat/keystore.p12"
certificateKeystoreType="PKCS12"
certificateKeystorePassword="changeit"
certificateKeyAlias="tomcat"
type="RSA" />
</SSLHostConfig>
</Connector>Key attributes explained
- protocol — Http11NioProtocol is the non-blocking Java TLS implementation. Use it unless you have a specific reason to use the APR/native connector.
- protocols —
TLSv1.2+TLSv1.3enables both;TLSv1.3alone restricts to TLS 1.3 only. - honorCipherOrder — set to
falsewith TLS 1.3 to let the client pick the preferred TLS 1.3 cipher suite. - certificateKeyAlias — must match the alias set during keystore creation (
tomcatby default).
Using port 443 on Linux
Linux requires root to bind ports below 1024. To let a non-root Tomcat bind port 443:
# Grant CAP_NET_BIND_SERVICE to the JVM sudo setcap 'cap_net_bind_service=+ep' \ $(readlink -f $(which java)) # Then change port="8443" to port="443" in server.xml
Avoid storing the keystore password in plain text. Production deployments can use Tomcat's StoreConfigLifecycleListener or an environment variable reference via certificateKeystorePassword="${KEYSTORE_PASS}" if your Tomcat version supports property substitution in server.xml (Tomcat 9.0.50+).
Step 4: Force HTTP → HTTPS
There are two common approaches to redirect HTTP traffic to HTTPS in Tomcat.
Option A — Redirect via web.xml (per-application)
Add a security constraint to your application's WEB-INF/web.xml (or $CATALINA_HOME/conf/web.xml for all applications):
<!-- Add inside <web-app> to force HTTPS for all URLs -->
<security-constraint>
<web-resource-collection>
<web-resource-name>Entire Application</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>Tomcat translates CONFIDENTIAL into a redirect to the HTTPS port. Also ensure the HTTP connector has the redirectPort attribute set to match your HTTPS port (default: redirectPort="8443").
Option B — Redirect at a reverse proxy (recommended for production)
In most production setups, Nginx or Apache HTTP Server sits in front of Tomcat as a reverse proxy. The proxy terminates TLS and handles the HTTP-to-HTTPS redirect, while Tomcat listens only on an internal port (e.g. 8080) without needing to be exposed to the internet. This approach also lets you leverage the HSTS header and OCSP stapling at the proxy layer, which has better performance than Tomcat's native JSSE implementation.
Step 5: Restart Tomcat and verify
After saving server.xml, restart Tomcat and check the logs for errors before testing in a browser:
# Graceful restart (waits for active requests to finish) $CATALINA_HOME/bin/shutdown.sh && $CATALINA_HOME/bin/startup.sh # Or if running as a systemd service sudo systemctl restart tomcat # Watch the startup log for SSL errors tail -f $CATALINA_HOME/logs/catalina.out
Look for a line like:
INFO: Starting ProtocolHandler ["https-jsse-nio-8443"]
If that line appears without errors, the HTTPS connector is active. Three ways to confirm:
1. Browser check
Open https://yourdomain.com:8443 (or port 443). The padlock should appear with no security warnings.
2. OpenSSL command line
openssl s_client -connect yourdomain.com:8443 -showcerts # Look for: Verify return code: 0 (ok) # Confirms the full certificate chain is present and trusted.
3. Online SSL Checker
Use the My-SSL SSL Checker to verify chain completeness, expiry date, and TLS protocol support from an external vantage point.
Harden TLS in Tomcat
The connector example in Step 3 already enforces TLS 1.2+ and strong cipher suites. Two additional hardening steps are recommended:
Disable session tickets for forward secrecy
TLS session tickets can undermine forward secrecy if the ticket encryption key is compromised. Disable them in SSLHostConfig:
<SSLHostConfig
protocols="TLSv1.2+TLSv1.3"
sessionCacheSize="0"
sessionTimeout="86400"
disableSessionTickets="true"
...>Add HSTS in web.xml
Tomcat 9.0.76+ and 10.1.11+ include a built-in HttpHeaderSecurityFilter that adds HSTS and other security headers. Add to web.xml:
<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>hstsIncludeSubDomains</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>antiClickJackingEnabled</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>xContentTypeOptionsEnabled</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>Automate renewals — critical with 199-day certificate lifetimes
2026 change: The CA/Browser Forum reduced the maximum TLS certificate validity to 199 days effective March 15, 2026 (Ballot SC-081). That means at least two renewals per year. The limit drops to 100 days in 2027 and 47 days by 2029 — at that point you'll need approximately 8 renewals per year per domain.
Renewal on Tomcat requires three steps each time: get the new certificate from your CA, rebuild the PKCS12 keystore, and restart Tomcat. A simple renewal script handles all three:
#!/bin/bash # renewal-tomcat.sh — run after receiving new cert files from your CA set -e KEYSTORE=/opt/tomcat/keystore.p12 PASS="changeit" ALIAS="tomcat" CERT=./your_domain.crt KEY=./your_domain.key CABUNDLE=./ca_bundle.crt # Rebuild the keystore with fresh certificate openssl pkcs12 -export \ -in "$CERT" -inkey "$KEY" \ -out "$KEYSTORE" \ -name "$ALIAS" \ -CAfile "$CABUNDLE" -caname root -chain \ -passout "pass:$PASS" chmod 600 "$KEYSTORE" # Restart Tomcat to load the new certificate sudo systemctl restart tomcat echo "Tomcat restarted with new certificate." # Verify openssl s_client -connect yourdomain.com:8443 -showcerts < /dev/null 2>&1 | \ grep -E "(subject|issuer|Verify return)"
Set a calendar alert 30 days before expiry. For broader context on why certificate lifetimes are shrinking, read our article on 2026 SSL certificate lifetime changes. For a deep dive on the 199-day milestone specifically, see SSL certificates are now limited to 199 days.
Troubleshooting common errors
java.io.IOException: keystore password was incorrect
The certificateKeystorePassword in server.xml does not match the password used when creating the keystore. Re-export the PKCS12 with a known password, or update the password in server.xml to match.
java.security.cert.CertificateException: No subject alternative names present
Your certificate does not have a SAN (Subject Alternative Name) matching the hostname. Most CAs issue SANs automatically for every DV certificate, but if the CSR was generated without -ext SAN, the resulting cert may lack them. Reissue the certificate with a CSR that includes the SAN extension.
ERR_CERT_AUTHORITY_INVALID / certificate verify failed
The certificate chain is incomplete. Verify that your PKCS12 file includes the intermediate CA: run keytool -list -v -storetype PKCS12 -keystore keystore.p12 -storepass changeit and check that Certificate chain length is 2 or 3. If it is 1, rebuild the keystore and include the ca_bundle.crt with the -CAfile / -caname flags.
INFO: HTTPS connector not listed in catalina.out after startup
Check for a fatal error earlier in catalina.out — common causes are a wrong file path to the keystore, a bad password, or an XML syntax error in server.xml. Run: grep -i 'error\|exception' $CATALINA_HOME/logs/catalina.out
Address already in use: bind — port 8443 (or 443)
Another process is already listening on that port. Find it with: sudo ss -tlnp | grep 8443. On Linux, binding port 443 without root requires the CAP_NET_BIND_SERVICE capability — see the 'Using port 443' note in Step 3.
javax.net.ssl.SSLException: Unrecognized SSL message (or ERR_SSL_PROTOCOL_ERROR)
The client is connecting to the HTTPS port over plain HTTP, or vice versa. Confirm the connector has SSLEnabled="true". If testing with curl, make sure you are using https:// not http://.