{"id":1856,"date":"2026-03-22T21:07:29","date_gmt":"2018-03-07T14:11:07","guid":{"rendered":"https:\/\/computingforgeeks.com\/?p=1856"},"modified":"2026-03-22T21:07:30","modified_gmt":"2026-03-22T18:07:30","slug":"auto-renew-letsencrypt-ssl-tomcat","status":"publish","type":"post","link":"https:\/\/computingforgeeks.com\/auto-renew-letsencrypt-ssl-tomcat\/","title":{"rendered":"Auto-Renew Let&#8217;s Encrypt SSL on Apache Tomcat"},"content":{"rendered":"\n<p><a href=\"https:\/\/letsencrypt.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">Let&#8217;s Encrypt<\/a> 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.<\/p>\n\n\n\n<p>This guide covers the full workflow &#8211; 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).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Prerequisites<\/h2>\n\n\n\n<p>Before starting, make sure you have the following in place:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>A Linux server running Ubuntu 24.04, Debian 13, RHEL 10, Rocky Linux 10, or AlmaLinux 10<\/li>\n\n<li>Apache Tomcat 10 or 11 installed and running (Tomcat 9 also works with the same steps)<\/li>\n\n<li>A registered domain name pointed to your server&#8217;s public IP address (A record)<\/li>\n\n<li>Root or sudo access to the server<\/li>\n\n<li>Java 17 or newer installed (required by Tomcat 10+). If you need to install Java, follow our guide on <a href=\"https:\/\/computingforgeeks.com\/install-oracle-java-openjdk-on-debian-linux\/\" target=\"_blank\" rel=\"noreferrer noopener\">installing OpenJDK on Debian<\/a> or <a href=\"https:\/\/computingforgeeks.com\/how-to-install-java-18-on-centos-fedora\/\" target=\"_blank\" rel=\"noreferrer noopener\">installing Java on CentOS\/Fedora\/Rocky Linux<\/a><\/li>\n\n<li>Ports 80 (HTTP) and 443 (HTTPS) open in the firewall and reachable from the internet<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Step 1: Install Certbot<\/h2>\n\n\n\n<p>Certbot is the official ACME client from the Electronic Frontier Foundation for obtaining and managing Let&#8217;s Encrypt certificates. Install it using your distribution&#8217;s package manager.<\/p>\n\n\n\n<p>On Ubuntu\/Debian systems:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt update\nsudo apt install -y certbot<\/code><\/pre>\n\n\n\n<p>On RHEL\/Rocky Linux\/AlmaLinux systems, enable the EPEL repository first:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo dnf install -y epel-release\nsudo dnf install -y certbot<\/code><\/pre>\n\n\n\n<p>Verify the installation by checking the version:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>certbot --version<\/code><\/pre>\n\n\n\n<p>You should see the Certbot version printed:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>certbot 3.x.x<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Step 2: Obtain a Let&#8217;s Encrypt SSL Certificate<\/h2>\n\n\n\n<p>There are two common methods to obtain a certificate &#8211; standalone mode and webroot mode. Choose the one that fits your setup.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Option A: Standalone Mode<\/h3>\n\n\n\n<p>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 <code>yourdomain.com<\/code> with your actual domain:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl stop tomcat\nsudo certbot certonly --standalone -d yourdomain.com -d www.yourdomain.com<\/code><\/pre>\n\n\n\n<p>Start Tomcat again after the certificate is issued:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl start tomcat<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Option B: Webroot Mode<\/h3>\n\n\n\n<p>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:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo mkdir -p \/var\/lib\/tomcat\/webapps\/ROOT\/.well-known\/acme-challenge\nsudo chown -R tomcat:tomcat \/var\/lib\/tomcat\/webapps\/ROOT\/.well-known<\/code><\/pre>\n\n\n\n<p>Then request the certificate using the webroot path:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo certbot certonly --webroot -w \/var\/lib\/tomcat\/webapps\/ROOT -d yourdomain.com -d www.yourdomain.com<\/code><\/pre>\n\n\n\n<p>After successful issuance with either method, Certbot stores the certificate files under <code>\/etc\/letsencrypt\/live\/yourdomain.com\/<\/code>. Confirm they exist:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo ls -la \/etc\/letsencrypt\/live\/yourdomain.com\/<\/code><\/pre>\n\n\n\n<p>You should see four files &#8211; <code>cert.pem<\/code>, <code>chain.pem<\/code>, <code>fullchain.pem<\/code>, and <code>privkey.pem<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>lrwxrwxrwx 1 root root 41 Mar 22 10:00 cert.pem -> ..\/..\/archive\/yourdomain.com\/cert1.pem\nlrwxrwxrwx 1 root root 42 Mar 22 10:00 chain.pem -> ..\/..\/archive\/yourdomain.com\/chain1.pem\nlrwxrwxrwx 1 root root 46 Mar 22 10:00 fullchain.pem -> ..\/..\/archive\/yourdomain.com\/fullchain1.pem\nlrwxrwxrwx 1 root root 44 Mar 22 10:00 privkey.pem -> ..\/..\/archive\/yourdomain.com\/privkey1.pem<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Step 3: Convert Certificate to PKCS12 Keystore<\/h2>\n\n\n\n<p>Tomcat&#8217;s JSSE connector uses Java keystore format for TLS. You need to convert the PEM certificates from Let&#8217;s Encrypt into a PKCS12 keystore file. The <code>openssl pkcs12<\/code> command combines the private key and full certificate chain into a single <code>.p12<\/code> file.<\/p>\n\n\n\n<p>Create a directory for the keystore and run the conversion:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo mkdir -p \/opt\/tomcat-keystore\nsudo openssl pkcs12 -export \\\n  -in \/etc\/letsencrypt\/live\/yourdomain.com\/fullchain.pem \\\n  -inkey \/etc\/letsencrypt\/live\/yourdomain.com\/privkey.pem \\\n  -out \/opt\/tomcat-keystore\/yourdomain.p12 \\\n  -name tomcat \\\n  -password pass:changeit<\/code><\/pre>\n\n\n\n<p>Set proper ownership so only the Tomcat user can read the keystore:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo chown tomcat:tomcat \/opt\/tomcat-keystore\/yourdomain.p12\nsudo chmod 600 \/opt\/tomcat-keystore\/yourdomain.p12<\/code><\/pre>\n\n\n\n<p>Verify the keystore contents with <code>keytool<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>keytool -list -keystore \/opt\/tomcat-keystore\/yourdomain.p12 -storetype PKCS12 -storepass changeit<\/code><\/pre>\n\n\n\n<p>The output should show the <code>tomcat<\/code> alias with a PrivateKeyEntry type:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Keystore type: PKCS12\nKeystore provider: SUN\n\nYour keystore contains 1 entry\n\ntomcat, Mar 22, 2026, PrivateKeyEntry,\nCertificate fingerprint (SHA-256): AB:CD:EF:...<\/code><\/pre>\n\n\n\n<p><strong>Important:<\/strong> Replace <code>changeit<\/code> with a strong password in production. Use the same password in both the <code>openssl<\/code> command and the Tomcat <code>server.xml<\/code> configuration.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 4: Configure Tomcat server.xml for SSL<\/h2>\n\n\n\n<p>Edit the Tomcat <code>server.xml<\/code> file to add an HTTPS connector that uses the PKCS12 keystore. The file is typically at <code>\/etc\/tomcat\/server.xml<\/code> or <code>\/opt\/tomcat\/conf\/server.xml<\/code> depending on your installation method.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo vi \/etc\/tomcat\/server.xml<\/code><\/pre>\n\n\n\n<p>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:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;Connector port=\"8443\" protocol=\"org.apache.coyote.http11.Http11NioProtocol\"\n           maxThreads=\"150\" SSLEnabled=\"true\"\n           scheme=\"https\" secure=\"true\"&gt;\n    &lt;SSLHostConfig protocols=\"TLSv1.2+TLSv1.3\"&gt;\n        &lt;Certificate certificateKeystoreFile=\"\/opt\/tomcat-keystore\/yourdomain.p12\"\n                     certificateKeystorePassword=\"changeit\"\n                     certificateKeystoreType=\"PKCS12\"\n                     certificateKeyAlias=\"tomcat\" \/&gt;\n    &lt;\/SSLHostConfig&gt;\n&lt;\/Connector&gt;<\/code><\/pre>\n\n\n\n<p>If you want to redirect all HTTP traffic to HTTPS, add this redirect inside the existing HTTP connector (port 8080):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;Connector port=\"8080\" protocol=\"HTTP\/1.1\"\n           connectionTimeout=\"20000\"\n           redirectPort=\"8443\" \/&gt;<\/code><\/pre>\n\n\n\n<p>Then add a security constraint in <code>web.xml<\/code> to force the redirect. Open the web application&#8217;s <code>web.xml<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo vi \/etc\/tomcat\/web.xml<\/code><\/pre>\n\n\n\n<p>Add this block before the closing <code>&lt;\/web-app&gt;<\/code> tag:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;security-constraint&gt;\n    &lt;web-resource-collection&gt;\n        &lt;web-resource-name&gt;Secured&lt;\/web-resource-name&gt;\n        &lt;url-pattern&gt;\/*&lt;\/url-pattern&gt;\n    &lt;\/web-resource-collection&gt;\n    &lt;user-data-constraint&gt;\n        &lt;transport-guarantee&gt;CONFIDENTIAL&lt;\/transport-guarantee&gt;\n    &lt;\/user-data-constraint&gt;\n&lt;\/security-constraint&gt;<\/code><\/pre>\n\n\n\n<p>Restart Tomcat to apply the changes:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl restart tomcat<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Step 5: Open Firewall Ports and Test HTTPS<\/h2>\n\n\n\n<p>Make sure ports 8443 (or 443 if you changed it) are open in your firewall.<\/p>\n\n\n\n<p>On RHEL\/Rocky Linux\/AlmaLinux with firewalld:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo firewall-cmd --add-port=8443\/tcp --permanent\nsudo firewall-cmd --reload<\/code><\/pre>\n\n\n\n<p>On Ubuntu\/Debian with UFW:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo ufw allow 8443\/tcp<\/code><\/pre>\n\n\n\n<p>Test the HTTPS connection from the command line using <code>curl<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>curl -vI https:\/\/yourdomain.com:8443<\/code><\/pre>\n\n\n\n<p>Look for <code>SSL connection using TLSv1.3<\/code> in the output, along with the Let&#8217;s Encrypt certificate details:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>* SSL connection using TLSv1.3 \/ TLS_AES_256_GCM_SHA384\n* Server certificate:\n*  subject: CN=yourdomain.com\n*  issuer: C=US; O=Let's Encrypt; CN=R11\n*  SSL certificate verify ok.\nHTTP\/1.1 200<\/code><\/pre>\n\n\n\n<p>You can also open <code>https:\/\/yourdomain.com:8443<\/code> in a browser and check the padlock icon to confirm the certificate is valid.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 6: Create the Auto-Renewal Script<\/h2>\n\n\n\n<p>Let&#8217;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.<\/p>\n\n\n\n<p>Create the renewal script:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo vi \/opt\/tomcat-keystore\/renew-tomcat-ssl.sh<\/code><\/pre>\n\n\n\n<p>Add the following content. This script rebuilds the PKCS12 keystore from the renewed certificates and restarts Tomcat:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#!\/bin\/bash\n# Tomcat Let's Encrypt SSL renewal script\n# Converts renewed certificates to PKCS12 keystore and restarts Tomcat\n\nDOMAIN=\"yourdomain.com\"\nKEYSTORE_DIR=\"\/opt\/tomcat-keystore\"\nKEYSTORE_PASS=\"changeit\"\nCERT_DIR=\"\/etc\/letsencrypt\/live\/${DOMAIN}\"\n\n# Remove old keystore\nrm -f \"${KEYSTORE_DIR}\/${DOMAIN}.p12\"\n\n# Build new PKCS12 keystore from renewed certs\nopenssl pkcs12 -export \\\n  -in \"${CERT_DIR}\/fullchain.pem\" \\\n  -inkey \"${CERT_DIR}\/privkey.pem\" \\\n  -out \"${KEYSTORE_DIR}\/${DOMAIN}.p12\" \\\n  -name tomcat \\\n  -password \"pass:${KEYSTORE_PASS}\"\n\n# Set permissions\nchown tomcat:tomcat \"${KEYSTORE_DIR}\/${DOMAIN}.p12\"\nchmod 600 \"${KEYSTORE_DIR}\/${DOMAIN}.p12\"\n\n# Restart Tomcat to load the new certificate\nsystemctl restart tomcat\n\necho \"$(date): Tomcat SSL keystore renewed for ${DOMAIN}\" >> \/var\/log\/tomcat-ssl-renewal.log<\/code><\/pre>\n\n\n\n<p>Make the script executable:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo chmod +x \/opt\/tomcat-keystore\/renew-tomcat-ssl.sh<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Step 7: Configure Automatic Renewal with Cron or Systemd Timer<\/h2>\n\n\n\n<p>There are two ways to schedule automatic renewal &#8211; a cron job or Certbot&#8217;s built-in deploy hook with systemd timers. Both work well.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Option A: Certbot Deploy Hook (Recommended)<\/h3>\n\n\n\n<p>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:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo cp \/opt\/tomcat-keystore\/renew-tomcat-ssl.sh \/etc\/letsencrypt\/renewal-hooks\/deploy\/tomcat-ssl.sh<\/code><\/pre>\n\n\n\n<p>Certbot&#8217;s systemd timer (installed by default on most distributions) runs <code>certbot renew<\/code> twice daily. When a renewal succeeds, it automatically executes all scripts in <code>\/etc\/letsencrypt\/renewal-hooks\/deploy\/<\/code>.<\/p>\n\n\n\n<p>Check that the Certbot timer is active:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl status certbot.timer<\/code><\/pre>\n\n\n\n<p>The timer should show as active and enabled:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u25cf certbot.timer - Run certbot twice daily\n     Loaded: loaded (\/lib\/systemd\/system\/certbot.timer; enabled; preset: enabled)\n     Active: active (waiting)\n    Trigger: Sun 2026-03-22 18:25:00 UTC; 5h left<\/code><\/pre>\n\n\n\n<p>If the timer is not present, enable it:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl enable --now certbot.timer<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Option B: Cron Job<\/h3>\n\n\n\n<p>If your distribution does not include the Certbot systemd timer, set up a cron job instead. Open the root crontab:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo crontab -e<\/code><\/pre>\n\n\n\n<p>Add a line to attempt renewal twice daily and run the keystore rebuild hook on success:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>0 3,15 * * * certbot renew --deploy-hook \/opt\/tomcat-keystore\/renew-tomcat-ssl.sh >> \/var\/log\/certbot-renew.log 2>&1<\/code><\/pre>\n\n\n\n<p>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.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 8: Verify Auto-Renewal Works<\/h2>\n\n\n\n<p>Test the full renewal process with a dry run to make sure everything is configured correctly:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo certbot renew --dry-run<\/code><\/pre>\n\n\n\n<p>A successful dry run shows this message at the end:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Congratulations, all simulated renewals succeeded:\n  \/etc\/letsencrypt\/live\/yourdomain.com\/fullchain.pem (success)<\/code><\/pre>\n\n\n\n<p>If the dry run fails, check these common issues:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Port 80 blocked<\/strong> &#8211; Certbot needs port 80 open for HTTP-01 challenges even when using standalone mode<\/li>\n\n<li><strong>DNS not pointing to server<\/strong> &#8211; the A record for your domain must resolve to the server&#8217;s public IP<\/li>\n\n<li><strong>Another service on port 80<\/strong> &#8211; if using standalone mode, stop the conflicting service or switch to webroot mode<\/li>\n<\/ul>\n\n\n\n<p>You can also manually test the deploy hook to make sure the keystore rebuild works:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo \/opt\/tomcat-keystore\/renew-tomcat-ssl.sh<\/code><\/pre>\n\n\n\n<p>Check the log file to confirm it ran successfully:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cat \/var\/log\/tomcat-ssl-renewal.log<\/code><\/pre>\n\n\n\n<p>You should see a timestamped entry confirming the renewal:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Sat Mar 22 12:00:00 UTC 2026: Tomcat SSL keystore renewed for yourdomain.com<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Step 9: Alternative &#8211; Nginx Reverse Proxy for SSL Termination<\/h2>\n\n\n\n<p>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 &#8211; no keystore conversion needed. Certbot also has a dedicated Nginx plugin that handles certificate renewal and Nginx reload automatically.<\/p>\n\n\n\n<p>Install Nginx and the Certbot Nginx plugin. On Ubuntu\/Debian:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt install -y nginx python3-certbot-nginx<\/code><\/pre>\n\n\n\n<p>On RHEL\/Rocky Linux\/AlmaLinux:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo dnf install -y nginx python3-certbot-nginx<\/code><\/pre>\n\n\n\n<p>Create an Nginx virtual host configuration that proxies requests to Tomcat on port 8080:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo vi \/etc\/nginx\/conf.d\/tomcat.conf<\/code><\/pre>\n\n\n\n<p>Add the following server block. This proxies all traffic to Tomcat and preserves the original client IP in headers:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>server {\n    listen 80;\n    server_name yourdomain.com www.yourdomain.com;\n\n    location \/ {\n        proxy_pass http:\/\/127.0.0.1:8080;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n    }\n}<\/code><\/pre>\n\n\n\n<p>Test the Nginx configuration and start the service:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo nginx -t\nsudo systemctl enable --now nginx<\/code><\/pre>\n\n\n\n<p>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 <a href=\"https:\/\/computingforgeeks.com\/easiest-way-install-letsencrypt-linux\/\" target=\"_blank\" rel=\"noreferrer noopener\">standalone Let&#8217;s Encrypt certificate<\/a>, you can switch to the Nginx method:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com<\/code><\/pre>\n\n\n\n<p>Certbot modifies the Nginx config to add SSL directives and sets up automatic renewal. Verify the final configuration:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo nginx -t\ncurl -vI https:\/\/yourdomain.com<\/code><\/pre>\n\n\n\n<p>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 <a href=\"https:\/\/computingforgeeks.com\/configure-jenkins-behind-nginx-reverse-proxy-and-lets-encrypt-ssl\/\" target=\"_blank\" rel=\"noreferrer noopener\">configuring Nginx as a reverse proxy with Let&#8217;s Encrypt<\/a>, see our dedicated guide.<\/p>\n\n\n\n<p>When using Nginx as a reverse proxy, configure Tomcat to trust the proxy headers. Open <code>server.xml<\/code> and add a <code>RemoteIpValve<\/code> inside the <code>&lt;Host&gt;<\/code> block:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo vi \/etc\/tomcat\/server.xml<\/code><\/pre>\n\n\n\n<p>Add this valve configuration so Tomcat uses the real client IP from the <code>X-Forwarded-For<\/code> header:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;Valve className=\"org.apache.catalina.valves.RemoteIpValve\"\n       remoteIpHeader=\"X-Forwarded-For\"\n       protocolHeader=\"X-Forwarded-Proto\" \/&gt;<\/code><\/pre>\n\n\n\n<p>Open firewall ports 80 and 443 for Nginx, and restrict Tomcat&#8217;s port 8080 to localhost only.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>You now have a working setup for automatic Let&#8217;s Encrypt SSL renewal on Apache Tomcat &#8211; 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 <a href=\"https:\/\/tomcat.apache.org\/tomcat-11.0-doc\/ssl-howto.html\" target=\"_blank\" rel=\"noreferrer noopener\">official Tomcat SSL\/TLS documentation<\/a>, check the Apache Tomcat project site.<\/p>\n\n\n\n<p>For production deployments, monitor certificate expiry with a tool like Prometheus or a simple cron check using <code>openssl s_client<\/code>, and set up email alerts so you catch any renewal failures before the certificate expires.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Let&#8217;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 &#8230; <a title=\"Auto-Renew Let&#8217;s Encrypt SSL on Apache Tomcat\" class=\"read-more\" href=\"https:\/\/computingforgeeks.com\/auto-renew-letsencrypt-ssl-tomcat\/\" aria-label=\"Read more about Auto-Renew Let&#8217;s Encrypt SSL on Apache Tomcat\">Read more<\/a><\/p>\n","protected":false},"author":3,"featured_media":1294,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[299,50,75,349],"tags":[170,169],"class_list":["post-1856","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-how-to","category-linux-tutorials","category-security","category-web-hosting","tag-letsencrypt","tag-tomcat"],"_links":{"self":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts\/1856","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/comments?post=1856"}],"version-history":[{"count":1,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts\/1856\/revisions"}],"predecessor-version":[{"id":163627,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts\/1856\/revisions\/163627"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/media\/1294"}],"wp:attachment":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/media?parent=1856"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/categories?post=1856"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/tags?post=1856"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}