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.