How To

Auto-Renew Let’s Encrypt SSL on Apache Tomcat

Let’s Encrypt provides free, automated TLS certificates that expire every 90 days. When running Apache Tomcat as your application server, you need a reliable process to renew those certificates and convert them into a Java keystore format that Tomcat can use. Manual renewal every 90 days is error-prone and eventually leads to expired certificates in production.

This guide covers the full workflow – installing Certbot, obtaining a certificate, converting it to PKCS12 keystore format, configuring Tomcat for HTTPS, and setting up automatic renewal with a cron job or systemd timer. We also cover an alternative approach using Nginx as a reverse proxy for SSL termination. The steps work on RHEL-based systems (RHEL 10, Rocky Linux 10, AlmaLinux 10) and Debian-based systems (Ubuntu 24.04, Debian 13).

Prerequisites

Before starting, make sure you have the following in place:

  • A Linux server running Ubuntu 24.04, Debian 13, RHEL 10, Rocky Linux 10, or AlmaLinux 10
  • Apache Tomcat 10 or 11 installed and running (Tomcat 9 also works with the same steps)
  • A registered domain name pointed to your server’s public IP address (A record)
  • Root or sudo access to the server
  • Java 17 or newer installed (required by Tomcat 10+). If you need to install Java, follow our guide on installing OpenJDK on Debian or installing Java on CentOS/Fedora/Rocky Linux
  • Ports 80 (HTTP) and 443 (HTTPS) open in the firewall and reachable from the internet

Step 1: Install Certbot

Certbot is the official ACME client from the Electronic Frontier Foundation for obtaining and managing Let’s Encrypt certificates. Install it using your distribution’s package manager.

On Ubuntu/Debian systems:

sudo apt update
sudo apt install -y certbot

On RHEL/Rocky Linux/AlmaLinux systems, enable the EPEL repository first:

sudo dnf install -y epel-release
sudo dnf install -y certbot

Verify the installation by checking the version:

certbot --version

You should see the Certbot version printed:

certbot 3.x.x

Step 2: Obtain a Let’s Encrypt SSL Certificate

There are two common methods to obtain a certificate – standalone mode and webroot mode. Choose the one that fits your setup.

Option A: Standalone Mode

Standalone mode starts a temporary web server on port 80 for domain validation. This requires stopping Tomcat (or any other service using port 80) temporarily. Replace yourdomain.com with your actual domain:

sudo systemctl stop tomcat
sudo certbot certonly --standalone -d yourdomain.com -d www.yourdomain.com

Start Tomcat again after the certificate is issued:

sudo systemctl start tomcat

Option B: Webroot Mode

Webroot mode places validation files in a directory served by your running web application. This avoids stopping Tomcat. First, create a webroot directory and make sure Tomcat serves it:

sudo mkdir -p /var/lib/tomcat/webapps/ROOT/.well-known/acme-challenge
sudo chown -R tomcat:tomcat /var/lib/tomcat/webapps/ROOT/.well-known

Then request the certificate using the webroot path:

sudo certbot certonly --webroot -w /var/lib/tomcat/webapps/ROOT -d yourdomain.com -d www.yourdomain.com

After successful issuance with either method, Certbot stores the certificate files under /etc/letsencrypt/live/yourdomain.com/. Confirm they exist:

sudo ls -la /etc/letsencrypt/live/yourdomain.com/

You should see four files – cert.pem, chain.pem, fullchain.pem, and privkey.pem:

lrwxrwxrwx 1 root root 41 Mar 22 10:00 cert.pem -> ../../archive/yourdomain.com/cert1.pem
lrwxrwxrwx 1 root root 42 Mar 22 10:00 chain.pem -> ../../archive/yourdomain.com/chain1.pem
lrwxrwxrwx 1 root root 46 Mar 22 10:00 fullchain.pem -> ../../archive/yourdomain.com/fullchain1.pem
lrwxrwxrwx 1 root root 44 Mar 22 10:00 privkey.pem -> ../../archive/yourdomain.com/privkey1.pem

Step 3: Convert Certificate to PKCS12 Keystore

Tomcat’s JSSE connector uses Java keystore format for TLS. You need to convert the PEM certificates from Let’s Encrypt into a PKCS12 keystore file. The openssl pkcs12 command combines the private key and full certificate chain into a single .p12 file.

Create a directory for the keystore and run the conversion:

sudo mkdir -p /opt/tomcat-keystore
sudo openssl pkcs12 -export \
  -in /etc/letsencrypt/live/yourdomain.com/fullchain.pem \
  -inkey /etc/letsencrypt/live/yourdomain.com/privkey.pem \
  -out /opt/tomcat-keystore/yourdomain.p12 \
  -name tomcat \
  -password pass:changeit

Set proper ownership so only the Tomcat user can read the keystore:

sudo chown tomcat:tomcat /opt/tomcat-keystore/yourdomain.p12
sudo chmod 600 /opt/tomcat-keystore/yourdomain.p12

Verify the keystore contents with keytool:

keytool -list -keystore /opt/tomcat-keystore/yourdomain.p12 -storetype PKCS12 -storepass changeit

The output should show the tomcat alias with a PrivateKeyEntry type:

Keystore type: PKCS12
Keystore provider: SUN

Your keystore contains 1 entry

tomcat, Mar 22, 2026, PrivateKeyEntry,
Certificate fingerprint (SHA-256): AB:CD:EF:...

Important: Replace changeit with a strong password in production. Use the same password in both the openssl command and the Tomcat server.xml configuration.

Step 4: Configure Tomcat server.xml for SSL

Edit the Tomcat server.xml file to add an HTTPS connector that uses the PKCS12 keystore. The file is typically at /etc/tomcat/server.xml or /opt/tomcat/conf/server.xml depending on your installation method.

sudo vi /etc/tomcat/server.xml

Find the commented-out SSL connector section and replace it with the following. This configures Tomcat to listen on port 8443 with TLS 1.2 and TLS 1.3:

<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
           maxThreads="150" SSLEnabled="true"
           scheme="https" secure="true">
    <SSLHostConfig protocols="TLSv1.2+TLSv1.3">
        <Certificate certificateKeystoreFile="/opt/tomcat-keystore/yourdomain.p12"
                     certificateKeystorePassword="changeit"
                     certificateKeystoreType="PKCS12"
                     certificateKeyAlias="tomcat" />
    </SSLHostConfig>
</Connector>

If you want to redirect all HTTP traffic to HTTPS, add this redirect inside the existing HTTP connector (port 8080):

<Connector port="8080" protocol="HTTP/1.1"
           connectionTimeout="20000"
           redirectPort="8443" />

Then add a security constraint in web.xml to force the redirect. Open the web application’s web.xml:

sudo vi /etc/tomcat/web.xml

Add this block before the closing </web-app> tag:

<security-constraint>
    <web-resource-collection>
        <web-resource-name>Secured</web-resource-name>
        <url-pattern>/*</url-pattern>
    </web-resource-collection>
    <user-data-constraint>
        <transport-guarantee>CONFIDENTIAL</transport-guarantee>
    </user-data-constraint>
</security-constraint>

Restart Tomcat to apply the changes:

sudo systemctl restart tomcat

Step 5: Open Firewall Ports and Test HTTPS

Make sure ports 8443 (or 443 if you changed it) are open in your firewall.

On RHEL/Rocky Linux/AlmaLinux with firewalld:

sudo firewall-cmd --add-port=8443/tcp --permanent
sudo firewall-cmd --reload

On Ubuntu/Debian with UFW:

sudo ufw allow 8443/tcp

Test the HTTPS connection from the command line using curl:

curl -vI https://yourdomain.com:8443

Look for SSL connection using TLSv1.3 in the output, along with the Let’s Encrypt certificate details:

* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* Server certificate:
*  subject: CN=yourdomain.com
*  issuer: C=US; O=Let's Encrypt; CN=R11
*  SSL certificate verify ok.
HTTP/1.1 200

You can also open https://yourdomain.com:8443 in a browser and check the padlock icon to confirm the certificate is valid.

Step 6: Create the Auto-Renewal Script

Let’s Encrypt certificates expire every 90 days. Certbot handles the renewal itself, but after renewal you need to rebuild the PKCS12 keystore and restart Tomcat. A post-renewal hook script automates this entire process.

Create the renewal script:

sudo vi /opt/tomcat-keystore/renew-tomcat-ssl.sh

Add the following content. This script rebuilds the PKCS12 keystore from the renewed certificates and restarts Tomcat:

#!/bin/bash
# Tomcat Let's Encrypt SSL renewal script
# Converts renewed certificates to PKCS12 keystore and restarts Tomcat

DOMAIN="yourdomain.com"
KEYSTORE_DIR="/opt/tomcat-keystore"
KEYSTORE_PASS="changeit"
CERT_DIR="/etc/letsencrypt/live/${DOMAIN}"

# Remove old keystore
rm -f "${KEYSTORE_DIR}/${DOMAIN}.p12"

# Build new PKCS12 keystore from renewed certs
openssl pkcs12 -export \
  -in "${CERT_DIR}/fullchain.pem" \
  -inkey "${CERT_DIR}/privkey.pem" \
  -out "${KEYSTORE_DIR}/${DOMAIN}.p12" \
  -name tomcat \
  -password "pass:${KEYSTORE_PASS}"

# Set permissions
chown tomcat:tomcat "${KEYSTORE_DIR}/${DOMAIN}.p12"
chmod 600 "${KEYSTORE_DIR}/${DOMAIN}.p12"

# Restart Tomcat to load the new certificate
systemctl restart tomcat

echo "$(date): Tomcat SSL keystore renewed for ${DOMAIN}" >> /var/log/tomcat-ssl-renewal.log

Make the script executable:

sudo chmod +x /opt/tomcat-keystore/renew-tomcat-ssl.sh

Step 7: Configure Automatic Renewal with Cron or Systemd Timer

There are two ways to schedule automatic renewal – a cron job or Certbot’s built-in deploy hook with systemd timers. Both work well.

Option A: Certbot Deploy Hook (Recommended)

The cleanest approach is to register the script as a Certbot deploy hook. This way the keystore rebuild only runs when a certificate actually gets renewed, not on every renewal attempt:

sudo cp /opt/tomcat-keystore/renew-tomcat-ssl.sh /etc/letsencrypt/renewal-hooks/deploy/tomcat-ssl.sh

Certbot’s systemd timer (installed by default on most distributions) runs certbot renew twice daily. When a renewal succeeds, it automatically executes all scripts in /etc/letsencrypt/renewal-hooks/deploy/.

Check that the Certbot timer is active:

sudo systemctl status certbot.timer

The timer should show as active and enabled:

● certbot.timer - Run certbot twice daily
     Loaded: loaded (/lib/systemd/system/certbot.timer; enabled; preset: enabled)
     Active: active (waiting)
    Trigger: Sun 2026-03-22 18:25:00 UTC; 5h left

If the timer is not present, enable it:

sudo systemctl enable --now certbot.timer

Option B: Cron Job

If your distribution does not include the Certbot systemd timer, set up a cron job instead. Open the root crontab:

sudo crontab -e

Add a line to attempt renewal twice daily and run the keystore rebuild hook on success:

0 3,15 * * * certbot renew --deploy-hook /opt/tomcat-keystore/renew-tomcat-ssl.sh >> /var/log/certbot-renew.log 2>&1

This runs at 3:00 AM and 3:00 PM daily. Certbot only performs the actual renewal when the certificate is within 30 days of expiry, so the deploy hook only triggers when needed.

Step 8: Verify Auto-Renewal Works

Test the full renewal process with a dry run to make sure everything is configured correctly:

sudo certbot renew --dry-run

A successful dry run shows this message at the end:

Congratulations, all simulated renewals succeeded:
  /etc/letsencrypt/live/yourdomain.com/fullchain.pem (success)

If the dry run fails, check these common issues:

  • Port 80 blocked – Certbot needs port 80 open for HTTP-01 challenges even when using standalone mode
  • DNS not pointing to server – the A record for your domain must resolve to the server’s public IP
  • Another service on port 80 – if using standalone mode, stop the conflicting service or switch to webroot mode

You can also manually test the deploy hook to make sure the keystore rebuild works:

sudo /opt/tomcat-keystore/renew-tomcat-ssl.sh

Check the log file to confirm it ran successfully:

cat /var/log/tomcat-ssl-renewal.log

You should see a timestamped entry confirming the renewal:

Sat Mar 22 12:00:00 UTC 2026: Tomcat SSL keystore renewed for yourdomain.com

Step 9: Alternative – Nginx Reverse Proxy for SSL Termination

An alternative to configuring SSL directly in Tomcat is to put Nginx in front of Tomcat and let Nginx handle TLS termination. This is often simpler to manage because Nginx works natively with PEM certificate files – no keystore conversion needed. Certbot also has a dedicated Nginx plugin that handles certificate renewal and Nginx reload automatically.

Install Nginx and the Certbot Nginx plugin. On Ubuntu/Debian:

sudo apt install -y nginx python3-certbot-nginx

On RHEL/Rocky Linux/AlmaLinux:

sudo dnf install -y nginx python3-certbot-nginx

Create an Nginx virtual host configuration that proxies requests to Tomcat on port 8080:

sudo vi /etc/nginx/conf.d/tomcat.conf

Add the following server block. This proxies all traffic to Tomcat and preserves the original client IP in headers:

server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;

    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;
    }
}

Test the Nginx configuration and start the service:

sudo nginx -t
sudo systemctl enable --now nginx

Now use Certbot with the Nginx plugin to obtain the certificate and automatically configure SSL in Nginx. If you have used Certbot previously for a standalone Let’s Encrypt certificate, you can switch to the Nginx method:

sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

Certbot modifies the Nginx config to add SSL directives and sets up automatic renewal. Verify the final configuration:

sudo nginx -t
curl -vI https://yourdomain.com

With this approach, Tomcat listens only on localhost port 8080 (HTTP), and Nginx handles all external HTTPS traffic on port 443. Renewal is fully automatic since the Certbot Nginx plugin reloads Nginx after each renewal. For more details on configuring Nginx as a reverse proxy with Let’s Encrypt, see our dedicated guide.

When using Nginx as a reverse proxy, configure Tomcat to trust the proxy headers. Open server.xml and add a RemoteIpValve inside the <Host> block:

sudo vi /etc/tomcat/server.xml

Add this valve configuration so Tomcat uses the real client IP from the X-Forwarded-For header:

<Valve className="org.apache.catalina.valves.RemoteIpValve"
       remoteIpHeader="X-Forwarded-For"
       protocolHeader="X-Forwarded-Proto" />

Open firewall ports 80 and 443 for Nginx, and restrict Tomcat’s port 8080 to localhost only.

Conclusion

You now have a working setup for automatic Let’s Encrypt SSL renewal on Apache Tomcat – either through direct PKCS12 keystore conversion with a deploy hook script, or through an Nginx reverse proxy that handles TLS termination. Both approaches keep your certificates current without manual intervention. For the official Tomcat SSL/TLS documentation, check the Apache Tomcat project site.

For production deployments, monitor certificate expiry with a tool like Prometheus or a simple cron check using openssl s_client, and set up email alerts so you catch any renewal failures before the certificate expires.

Related Articles

Containers Configure OPNsense as Kubernetes API Load Balancer (port 6443) AlmaLinux Install phpMyAdmin on Rocky Linux 10 / AlmaLinux 10 Web Hosting Resolve “413 Request Entity Too Large Error” on Nginx / Apache Apache Hosting PHP Applications on Linux with Apache / Nginx Web Server

Press ESC to close