Apache Tomcat is the go-to open-source Java servlet container for deploying Java web applications. In this guide, we will walk through installing Apache Tomcat 10 (or 11) on RHEL 10, Rocky Linux 10, or AlmaLinux 10, then secure it with a free Let’s Encrypt SSL certificate. Every step includes a verification command so you know things are working before moving on.

Tomcat 10.1.x implements Jakarta EE 10, while Tomcat 11.0.x targets Jakarta EE 11. Both require Java 17 or later – we will use Java 21, which is the current LTS release and ships in the default RHEL 10 repositories.

Prerequisites

Before you begin, make sure you have:

  • A server running RHEL 10, Rocky Linux 10, or AlmaLinux 10 with root or sudo access.
  • A registered domain name (or subdomain) pointing to your server’s public IP address – required for Let’s Encrypt.
  • An active internet connection on the server.
  • Ports 8080 and 8443 (or 80/443 if you plan to proxy) available and not blocked by an upstream firewall.

Update your system packages first:

sudo dnf update -y

Step 1 – Install Java 21 (OpenJDK)

Tomcat 10.1 and 11.0 both run on Java 17+. We will install OpenJDK 21, the latest long-term support release available in the default repos:

sudo dnf install -y java-21-openjdk java-21-openjdk-devel

Verify the installation:

java -version

You should see output similar to:

openjdk version "21.0.x" 2025-xx-xx LTS
OpenJDK Runtime Environment (Red_Hat-21.0.x) (build 21.0.x+xx)
OpenJDK 64-Bit Server VM (Red_Hat-21.0.x) (build 21.0.x+xx, mixed mode, sharing)

Set JAVA_HOME for the system. Create a profile script:

echo 'export JAVA_HOME=/usr/lib/jvm/java-21-openjdk' | sudo tee /etc/profile.d/java.sh
source /etc/profile.d/java.sh
echo $JAVA_HOME

Step 2 – Create a Tomcat System User and Group

Running Tomcat under a dedicated non-login user limits what a compromised application can do. Create the user and group now:

sudo groupadd -r tomcat
sudo useradd -r -g tomcat -d /opt/tomcat -s /sbin/nologin tomcat

Verify:

id tomcat

Expected output:

uid=xxx(tomcat) gid=xxx(tomcat) groups=xxx(tomcat)

Step 3 – Download and Install Apache Tomcat

Head to the Apache Tomcat 10 download page (or the Tomcat 11 download page) and grab the latest stable binary tarball. At the time of writing, Tomcat 10.1.39 and 11.0.6 are current. Adjust the version number as needed:

For Tomcat 10:

TOMCAT_VER="10.1.39"
cd /tmp
wget https://dlcdn.apache.org/tomcat/tomcat-10/v${TOMCAT_VER}/bin/apache-tomcat-${TOMCAT_VER}.tar.gz

For Tomcat 11 (alternative):

TOMCAT_VER="11.0.6"
cd /tmp
wget https://dlcdn.apache.org/tomcat/tomcat-11/v${TOMCAT_VER}/bin/apache-tomcat-${TOMCAT_VER}.tar.gz

Extract the archive to /opt/tomcat:

sudo mkdir -p /opt/tomcat
sudo tar -xzf /tmp/apache-tomcat-${TOMCAT_VER}.tar.gz -C /opt/tomcat --strip-components=1

Set ownership so the tomcat user controls everything under /opt/tomcat:

sudo chown -R tomcat:tomcat /opt/tomcat
sudo chmod -R u+x /opt/tomcat/bin

Verify the files are in place:

ls -la /opt/tomcat/

You should see directories like bin, conf, lib, logs, webapps, work, and temp.

Step 4 – Configure CATALINA_HOME Environment Variable

Set the CATALINA_HOME variable system-wide so scripts and the systemd unit can reference it:

echo 'export CATALINA_HOME=/opt/tomcat' | sudo tee /etc/profile.d/tomcat.sh
source /etc/profile.d/tomcat.sh
echo $CATALINA_HOME

The output should print /opt/tomcat.

Step 5 – Create a Systemd Service Unit for Tomcat

A systemd unit file lets you manage Tomcat with standard systemctl commands and ensures it starts automatically on boot.

Create the unit file:

sudo tee /etc/systemd/system/tomcat.service <<'EOF'
[Unit]
Description=Apache Tomcat Web Application Container
After=network.target

[Service]
Type=forking

User=tomcat
Group=tomcat

Environment="JAVA_HOME=/usr/lib/jvm/java-21-openjdk"
Environment="CATALINA_HOME=/opt/tomcat"
Environment="CATALINA_BASE=/opt/tomcat"
Environment="CATALINA_PID=/opt/tomcat/temp/tomcat.pid"
Environment="CATALINA_OPTS=-Xms512M -Xmx1024M -server -XX:+UseParallelGC"

ExecStart=/opt/tomcat/bin/startup.sh
ExecStop=/opt/tomcat/bin/shutdown.sh

RestartSec=10
Restart=always

[Install]
WantedBy=multi-user.target
EOF

Reload systemd, start Tomcat, and enable it on boot:

sudo systemctl daemon-reload
sudo systemctl start tomcat
sudo systemctl enable tomcat

Verify Tomcat is running:

sudo systemctl status tomcat

You should see active (running) in the output. You can also check the listening port:

ss -tlnp | grep 8080

Step 6 – Configure Tomcat Manager Access (tomcat-users.xml)

The Tomcat Manager and Host Manager web apps are useful for deploying and managing applications through the browser. By default, no users have access. Let’s fix that.

Edit the users configuration file:

sudo nano /opt/tomcat/conf/tomcat-users.xml

Add the following lines just before the closing </tomcat-users> tag. Replace the password with something strong:

<role rolename="manager-gui"/>
<role rolename="admin-gui"/>
<user username="admin" password="YourStrongPasswordHere" roles="manager-gui,admin-gui"/>

By default, the Manager app only allows connections from localhost. To access it from your remote machine, edit the context files for both Manager and Host Manager:

sudo nano /opt/tomcat/webapps/manager/META-INF/context.xml

Find the Valve element with the RemoteAddrValve className and either comment it out or update the allow attribute to include your IP address. For example, to allow any IP (not recommended for production):

<!-- <Valve className="org.apache.catalina.valves.RemoteAddrValve"
  allow="127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1" /> -->

Do the same for the Host Manager context file:

sudo nano /opt/tomcat/webapps/host-manager/META-INF/context.xml

After making the changes, restart Tomcat:

sudo systemctl restart tomcat

Verify: Open http://your-server-ip:8080/manager/html in your browser and log in with the credentials you set above.

Step 7 – Configure server.xml (Connector Ports and Address Binding)

The main Tomcat configuration lives in /opt/tomcat/conf/server.xml. Let’s review the key connector settings.

sudo nano /opt/tomcat/conf/server.xml

The default HTTP connector looks like this:

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

You can change the port or add an address attribute to bind Tomcat to a specific interface. For example, to listen only on the public IP:

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

We will add the HTTPS/SSL connector in a later step after obtaining the Let’s Encrypt certificate. For now, leave redirectPort="8443" as is.

Step 8 – Open Firewall Ports

RHEL 10 and its derivatives use firewalld by default. Open ports 8080 (HTTP) and 8443 (HTTPS) along with port 80 (needed for Let’s Encrypt HTTP-01 challenge):

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

Verify the rules are active:

sudo firewall-cmd --list-ports

You should see 8080/tcp 8443/tcp 80/tcp in the output.

At this point, open http://your-server-ip:8080 in a browser. You should see the default Tomcat welcome page.

Step 9 – Install Certbot and Obtain a Let’s Encrypt Certificate

Certbot is the standard tool for obtaining and renewing Let’s Encrypt certificates. Install it from the EPEL repository:

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

Verify certbot is installed:

certbot --version

Now obtain a certificate using the standalone mode. This temporarily starts a web server on port 80, so make sure nothing else is using that port. Replace tomcat.example.com with your actual domain:

sudo certbot certonly --standalone -d tomcat.example.com

Follow the prompts. Once done, your certificate files will be at:

  • Certificate: /etc/letsencrypt/live/tomcat.example.com/fullchain.pem
  • Private key: /etc/letsencrypt/live/tomcat.example.com/privkey.pem

Verify the certificate:

sudo openssl x509 -in /etc/letsencrypt/live/tomcat.example.com/fullchain.pem -noout -dates

Step 10 – Create a PKCS12 Keystore from the Let’s Encrypt Certificate

Tomcat’s native SSL connector works with PKCS12 keystores. Convert the Let’s Encrypt PEM files into a PKCS12 keystore:

sudo openssl pkcs12 -export \
  -in /etc/letsencrypt/live/tomcat.example.com/fullchain.pem \
  -inkey /etc/letsencrypt/live/tomcat.example.com/privkey.pem \
  -out /opt/tomcat/conf/keystore.p12 \
  -name tomcat \
  -password pass:YourKeystorePassword

Set proper ownership and permissions on the keystore:

sudo chown tomcat:tomcat /opt/tomcat/conf/keystore.p12
sudo chmod 600 /opt/tomcat/conf/keystore.p12

Verify the keystore contents:

sudo keytool -list -keystore /opt/tomcat/conf/keystore.p12 -storetype PKCS12 -storepass YourKeystorePassword

You should see an entry with the alias tomcat.

Step 11 – Configure the Tomcat SSL Connector

Now edit server.xml to add an HTTPS connector that uses the PKCS12 keystore:

sudo nano /opt/tomcat/conf/server.xml

Add the following connector block (or uncomment and modify the existing SSL connector). Place it after the existing HTTP connector:

<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
           maxThreads="150" SSLEnabled="true"
           scheme="https" secure="true">
    <SSLHostConfig>
        <Certificate certificateKeystoreFile="/opt/tomcat/conf/keystore.p12"
                     certificateKeystorePassword="YourKeystorePassword"
                     certificateKeystoreType="PKCS12"
                     certificateKeyAlias="tomcat" />
    </SSLHostConfig>
</Connector>

Restart Tomcat to apply the SSL configuration:

sudo systemctl restart tomcat

Verify Tomcat is listening on port 8443:

ss -tlnp | grep 8443

Step 12 – Set Up Automatic Certificate Renewal

Let’s Encrypt certificates expire every 90 days. Certbot installs a systemd timer that handles renewal automatically. However, we need a post-renewal hook that rebuilds the keystore and restarts Tomcat.

Create a renewal hook script:

sudo tee /etc/letsencrypt/renewal-hooks/deploy/tomcat-keystore.sh <<'EOF'
#!/bin/bash
DOMAIN="tomcat.example.com"
KEYSTORE="/opt/tomcat/conf/keystore.p12"
KEYSTORE_PASS="YourKeystorePassword"

# Rebuild the PKCS12 keystore
openssl pkcs12 -export \
  -in /etc/letsencrypt/live/${DOMAIN}/fullchain.pem \
  -inkey /etc/letsencrypt/live/${DOMAIN}/privkey.pem \
  -out ${KEYSTORE} \
  -name tomcat \
  -password pass:${KEYSTORE_PASS}

chown tomcat:tomcat ${KEYSTORE}
chmod 600 ${KEYSTORE}

# Restart Tomcat to load the new certificate
systemctl restart tomcat
EOF

sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/tomcat-keystore.sh

Verify the certbot timer is active:

sudo systemctl list-timers | grep certbot

You should see the certbot-renew.timer listed. You can also do a dry run to confirm renewal works:

sudo certbot renew --dry-run

Step 13 – Test HTTPS Access

Open your browser and navigate to:

https://tomcat.example.com:8443

You should see the Tomcat default page served over HTTPS with a valid Let’s Encrypt certificate (no browser warnings).

You can also test from the command line:

curl -I https://tomcat.example.com:8443

Expected output should include HTTP/1.1 200 and proper SSL headers.

To check the SSL certificate details:

echo | openssl s_client -connect tomcat.example.com:8443 -servername tomcat.example.com 2>/dev/null | openssl x509 -noout -subject -dates

Troubleshooting

Tomcat fails to start

Check the Catalina log for errors:

sudo tail -100 /opt/tomcat/logs/catalina.out

Common causes include wrong JAVA_HOME path, permission issues on /opt/tomcat, or port conflicts. Verify Java is found:

sudo -u tomcat /usr/lib/jvm/java-21-openjdk/bin/java -version

Port 8080 or 8443 is not accessible

First check that Tomcat is actually listening:

ss -tlnp | grep -E '8080|8443'

Then check firewalld:

sudo firewall-cmd --list-all

If using a cloud provider, verify that the security group or network ACL allows traffic on these ports.

SSL certificate errors in browser

Verify the keystore was created correctly:

sudo keytool -list -v -keystore /opt/tomcat/conf/keystore.p12 -storetype PKCS12 -storepass YourKeystorePassword

Make sure the domain in the certificate matches the domain you are using in the browser. Also confirm that server.xml references the correct keystore path, password, and alias.

Certbot fails to obtain a certificate

The standalone mode needs port 80 to be free. Check if anything is using it:

ss -tlnp | grep :80

If Apache httpd or Nginx is running and you want to keep it, use the --webroot plugin instead of --standalone. Also verify that your DNS A record points to the correct IP:

dig +short tomcat.example.com

Manager app returns 403 Forbidden

This usually means the RemoteAddrValve in the Manager’s context.xml is blocking your IP. Either comment out the Valve or add your IP to the allow regex. Remember to restart Tomcat after changes.

Java heap space errors

If your applications run out of memory, increase the heap size in the systemd unit file. Edit CATALINA_OPTS:

Environment="CATALINA_OPTS=-Xms1024M -Xmx2048M -server -XX:+UseParallelGC"

Then reload and restart:

sudo systemctl daemon-reload
sudo systemctl restart tomcat

SELinux blocking Tomcat

On RHEL 10 with SELinux in enforcing mode, Tomcat may be blocked from binding to ports or accessing files. Check for denials:

sudo ausearch -m avc -ts recent

If SELinux is the issue, allow Tomcat to bind to its ports:

sudo semanage port -a -t http_port_t -p tcp 8080
sudo semanage port -a -t http_port_t -p tcp 8443

You may also need to set the correct file contexts:

sudo semanage fcontext -a -t tomcat_exec_t "/opt/tomcat/bin(/.*)?"
sudo restorecon -Rv /opt/tomcat/bin

Conclusion

You now have Apache Tomcat 10 (or 11) running on RHEL 10 / Rocky Linux 10 / AlmaLinux 10, secured with a Let’s Encrypt SSL certificate. The systemd service ensures Tomcat starts on boot, and the certbot renewal hook automatically rebuilds the keystore when certificates are renewed.

For a production deployment, consider placing Tomcat behind a reverse proxy like Nginx or Apache httpd. This lets you terminate SSL on the proxy, run Tomcat on standard ports without needing root, and add caching or load balancing as your traffic grows.

8 COMMENTS

  1. Hey! really useful post! just a question, let’s encrypt recommends to upgrade the certificate with a cron daily, if I update the certificate with .’/path/to/certbot-auto renew –no-self-upgrade’ should I generate again the PKCS12 file and the JKS? do you have any suggestion to automate that task with the approach you posted here?
    Thanks!

LEAVE A REPLY

Please enter your comment!
Please enter your name here