Skip to main content
    Installation

    How to Install an SSL Certificate on Apache Tomcat (9 / 10.1 / 11)

    Install SSL on Apache Tomcat 9, 10.1, and 11: keytool CSR generation, PKCS12 keystore import, HTTPS connector in server.xml, and HTTP-to-HTTPS redirect.

    MS
    My-SSL Team
    ·
    18 min read
    ·Published June 5, 2026·Updated June 5, 2026

    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 CARoleUsed in
    your_domain.crtYour server (leaf) certificatePKCS12 bundle
    ca_bundle.crtIntermediate + root CA chainPKCS12 bundle
    your_domain.keyPrivate key (keep secret)PKCS12 bundle
    keystore.p12Final 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)

    bash
    # 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 changeit with a strong password — this protects your private key.
    • The -ext SAN flag adds Subject Alternative Names. Most CAs require at least the primary domain in SAN.
    • The -validity 1 value 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)

    bash
    # 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:

    bash
    # 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:

    bash
    # 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 tomcat sets the key alias — this must match the certificateKeyAlias in server.xml (defaults to tomcat).
    • Replace changeit with 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

    bash
    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

    xml
    <!-- 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.
    • protocolsTLSv1.2+TLSv1.3 enables both; TLSv1.3 alone restricts to TLS 1.3 only.
    • honorCipherOrder — set to false with TLS 1.3 to let the client pick the preferred TLS 1.3 cipher suite.
    • certificateKeyAlias — must match the alias set during keystore creation (tomcat by default).

    Using port 443 on Linux

    Linux requires root to bind ports below 1024. To let a non-root Tomcat bind port 443:

    bash
    # 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):

    xml
    <!-- 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:

    bash
    # 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:

    text
    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

    bash
    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:

    xml
    <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:

    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:

    bash
    #!/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://.

    Other server installation guides

    FAQ