Install and configure Apache Tomcat 11 with Java 21, SSL/TLS certificates, JVM performance tuning, security hardening, and production-ready monitoring for enterprise Java web applications.
Prerequisites
- Root or sudo access
- Domain name pointed to server
- At least 4GB RAM
- Java 21 compatible hardware
What this solves
Apache Tomcat 11 provides a robust servlet container for Java web applications with support for Jakarta EE 10 and modern Java features. This tutorial sets up a production-ready Tomcat 11 installation with SSL certificates, optimized JVM settings, security hardening, and comprehensive monitoring. You'll configure automatic SSL certificate management, fine-tune garbage collection and memory allocation, secure the management interface, and implement JMX monitoring for performance insights.
Step-by-step installation
Update system packages
Start by updating your package manager to ensure you get the latest security patches and package versions.
sudo apt update && sudo apt upgrade -y
sudo apt install -y wget curl unzip
Install Java 21 JDK
Tomcat 11 requires Java 11 or newer, but Java 21 provides better performance and security features for production environments.
sudo apt install -y openjdk-21-jdk
java -version
Create Tomcat user and directories
Create a dedicated system user for Tomcat with restricted privileges and proper directory structure.
sudo useradd -r -m -U -d /opt/tomcat -s /bin/false tomcat
sudo mkdir -p /opt/tomcat/logs /opt/tomcat/temp /opt/tomcat/work
Download and install Tomcat 11
Download the latest Tomcat 11 release and extract it to the proper directory structure.
cd /tmp
wget https://downloads.apache.org/tomcat/tomcat-11/v11.0.0/bin/apache-tomcat-11.0.0.tar.gz
tar -xzf apache-tomcat-11.0.0.tar.gz
sudo cp -r apache-tomcat-11.0.0/* /opt/tomcat/
sudo chown -R tomcat:tomcat /opt/tomcat
sudo chmod +x /opt/tomcat/bin/*.sh
Configure environment variables
Set up Java home and Tomcat environment variables for consistent operation across system restarts.
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
CATALINA_HOME=/opt/tomcat
CATALINA_BASE=/opt/tomcat
Apply the environment changes:
source /etc/environment
Configure JVM optimization settings
Create optimized JVM settings for production workloads with proper garbage collection and memory management.
#!/bin/bash
JVM Memory Settings
CATALINA_OPTS="$CATALINA_OPTS -Xms2048m -Xmx4096m"
Garbage Collection Optimization (G1GC)
CATALINA_OPTS="$CATALINA_OPTS -XX:+UseG1GC"
CATALINA_OPTS="$CATALINA_OPTS -XX:G1HeapRegionSize=16m"
CATALINA_OPTS="$CATALINA_OPTS -XX:MaxGCPauseMillis=200"
CATALINA_OPTS="$CATALINA_OPTS -XX:+ParallelRefProcEnabled"
JVM Performance Tuning
CATALINA_OPTS="$CATALINA_OPTS -XX:+OptimizeStringConcat"
CATALINA_OPTS="$CATALINA_OPTS -XX:+UseStringDeduplication"
CATALINA_OPTS="$CATALINA_OPTS -Djava.awt.headless=true"
Security Settings
CATALINA_OPTS="$CATALINA_OPTS -Djava.security.egd=file:/dev/./urandom"
JMX Monitoring
CATALINA_OPTS="$CATALINA_OPTS -Dcom.sun.management.jmxremote"
CATALINA_OPTS="$CATALINA_OPTS -Dcom.sun.management.jmxremote.port=9999"
CATALINA_OPTS="$CATALINA_OPTS -Dcom.sun.management.jmxremote.ssl=false"
CATALINA_OPTS="$CATALINA_OPTS -Dcom.sun.management.jmxremote.authenticate=false"
CATALINA_OPTS="$CATALINA_OPTS -Dcom.sun.management.jmxremote.local.only=true"
GC Logging
CATALINA_OPTS="$CATALINA_OPTS -Xlog:gc*:$CATALINA_BASE/logs/gc.log:time,tags"
export CATALINA_OPTS
Make the script executable:
sudo chown tomcat:tomcat /opt/tomcat/bin/setenv.sh
sudo chmod 755 /opt/tomcat/bin/setenv.sh
Configure server.xml for SSL and performance
Configure Tomcat's main server configuration with SSL connector and performance optimizations.
Install and configure Let's Encrypt SSL certificates
Install Certbot for automatic SSL certificate management and renewal.
sudo apt install -y certbot
Generate SSL certificate for your domain:
sudo certbot certonly --standalone -d example.com
Convert SSL certificates to Java keystore
Convert Let's Encrypt certificates to PKCS12 format that Tomcat can use directly.
sudo openssl pkcs12 -export -in /etc/letsencrypt/live/example.com/fullchain.pem \
-inkey /etc/letsencrypt/live/example.com/privkey.pem \
-out /opt/tomcat/conf/keystore.p12 \
-name tomcat -passout pass:changeit
Set proper permissions on the keystore:
sudo chown tomcat:tomcat /opt/tomcat/conf/keystore.p12
sudo chmod 600 /opt/tomcat/conf/keystore.p12
Configure SSL certificate auto-renewal
Create a script to automatically renew certificates and update the Tomcat keystore.
#!/bin/bash
Renew Let's Encrypt certificate and update Tomcat keystore
certbot renew --quiet
if [ $? -eq 0 ]; then
# Convert renewed certificate to PKCS12
openssl pkcs12 -export -in /etc/letsencrypt/live/example.com/fullchain.pem \
-inkey /etc/letsencrypt/live/example.com/privkey.pem \
-out /opt/tomcat/conf/keystore.p12 \
-name tomcat -passout pass:changeit
# Set correct permissions
chown tomcat:tomcat /opt/tomcat/conf/keystore.p12
chmod 600 /opt/tomcat/conf/keystore.p12
# Restart Tomcat to load new certificate
systemctl restart tomcat
fi
Make the script executable and add to crontab:
sudo chmod +x /usr/local/bin/renew-tomcat-ssl.sh
sudo crontab -e
Add this line to run renewal check daily:
0 2 * /usr/local/bin/renew-tomcat-ssl.sh
Configure Tomcat user management and security
Set up secure user authentication for the Tomcat manager application with role-based access control.
Restrict manager application access
Configure IP-based access restrictions for the Tomcat manager and host-manager applications.
Create systemd service for Tomcat
Configure Tomcat as a systemd service with proper resource limits and automatic startup.
[Unit]
Description=Apache Tomcat Web Application Container
After=network.target
[Service]
Type=forking
Environment=JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
Environment=CATALINA_PID=/opt/tomcat/temp/tomcat.pid
Environment=CATALINA_HOME=/opt/tomcat
Environment=CATALINA_BASE=/opt/tomcat
Environment="CATALINA_OPTS=-Xms2048m -Xmx4096m -server -XX:+UseParallelGC"
Environment="JAVA_OPTS=-Djava.awt.headless=true -Djava.security.egd=file:/dev/./urandom"
ExecStart=/opt/tomcat/bin/startup.sh
ExecStop=/opt/tomcat/bin/shutdown.sh
User=tomcat
Group=tomcat
UMask=0007
RestartSec=10
Restart=always
Security settings
NoNewPrivileges=true
PrivateTmp=true
ProtectHome=true
ProtectSystem=strict
ReadWritePaths=/opt/tomcat/
Resource limits
LimitNOFILE=65536
LimitNPROC=4096
[Install]
WantedBy=multi-user.target
Configure system resource limits
Set appropriate system resource limits for the Tomcat user to handle high-traffic applications. This complements the systemd resource limits and ensures proper resource allocation across system restarts.
echo "tomcat soft nofile 65536" | sudo tee -a /etc/security/limits.conf
echo "tomcat hard nofile 65536" | sudo tee -a /etc/security/limits.conf
echo "tomcat soft nproc 4096" | sudo tee -a /etc/security/limits.conf
echo "tomcat hard nproc 4096" | sudo tee -a /etc/security/limits.conf
For more detailed resource limit configuration, refer to our Linux system resource limits guide.
Configure log rotation
Set up log rotation to prevent Tomcat logs from consuming excessive disk space.
/opt/tomcat/logs/catalina.out {
copytruncate
daily
rotate 52
compress
missingok
create 644 tomcat tomcat
}
/opt/tomcat/logs/*.log {
copytruncate
daily
rotate 52
compress
missingok
create 644 tomcat tomcat
}
/opt/tomcat/logs/localhost_access_log*.txt {
copytruncate
daily
rotate 52
compress
missingok
create 644 tomcat tomcat
}
Configure firewall rules
Open the necessary ports for Tomcat HTTP, HTTPS, and JMX monitoring access.
sudo ufw allow 8080/tcp
sudo ufw allow 8443/tcp
sudo ufw allow from 127.0.0.1 to any port 9999
Enable and start Tomcat service
Enable Tomcat to start automatically on boot and start the service immediately.
sudo systemctl daemon-reload
sudo systemctl enable tomcat
sudo systemctl start tomcat
sudo systemctl status tomcat
Configure performance monitoring with JMX
Set up JMX monitoring tools
Install monitoring tools to track JVM performance, garbage collection, and application metrics.
cd /opt/tomcat/bin
sudo -u tomcat wget https://repo1.maven.org/maven2/io/prometheus/jmx/jmx_prometheus_javaagent/0.20.0/jmx_prometheus_javaagent-0.20.0.jar
Create JMX monitoring configuration
Configure JMX metrics collection for comprehensive application monitoring.
---
rules:
# JVM metrics
- pattern: 'java.lang(.+)'
name: jvm_memory_heap_$1
type: GAUGE
# Garbage Collection metrics
- pattern: 'java.lang'
name: jvm_gc_collection_count
labels:
gc: $1
type: COUNTER
# Tomcat connector metrics
- pattern: 'Catalina<(.+)>'
name: tomcat_connector_$2
labels:
port: $1
type: GAUGE
# Thread pool metrics
- pattern: 'Catalina<(.+)>'
name: tomcat_threadpool_$2
labels:
name: $1
type: GAUGE
Update setenv.sh to include Prometheus JMX agent:
echo 'CATALINA_OPTS="$CATALINA_OPTS -javaagent:/opt/tomcat/bin/jmx_prometheus_javaagent-0.20.0.jar=8088:/opt/tomcat/conf/jmx-config.yml"' | sudo tee -a /opt/tomcat/bin/setenv.sh
Verify your setup
Check that Tomcat is running correctly and all components are properly configured:
# Check Tomcat service status
sudo systemctl status tomcat
Verify Tomcat is listening on correct ports
sudo netstat -tlnp | grep java
Check SSL certificate
curl -I https://example.com:8443
Test HTTP redirect to HTTPS
curl -I http://example.com:8080
Check JVM memory usage
jstat -gc $(pgrep -f tomcat)
Monitor JMX metrics
curl http://localhost:8088/metrics | head -20
Check log files
sudo tail -f /opt/tomcat/logs/catalina.out
sudo tail -f /opt/tomcat/logs/gc.log
Access the Tomcat manager interface at https://example.com:8443/manager using the configured admin credentials.
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Service fails to start | Java not found or wrong version | which java && java -version to verify Java 21+ installation |
| Permission denied errors | Incorrect file ownership | sudo chown -R tomcat:tomcat /opt/tomcat and chmod 755 /opt/tomcat/bin/*.sh |
| SSL handshake failures | Keystore password mismatch | Verify keystore password matches server.xml configuration |
| Out of memory errors | Insufficient heap size | Increase -Xmx value in setenv.sh and restart service |
| Manager app 403 forbidden | IP restriction in context.xml | Add your IP to RemoteAddrValve allow pattern |
| High CPU usage | Inappropriate GC settings | Monitor GC logs and tune G1GC parameters or switch collectors |
| Port already in use | Another process using 8080/8443 | sudo lsof -i :8080 to identify conflicting process |
| Certificate renewal fails | Certbot validation issues | Check domain DNS resolution and firewall rules for port 80 |
Next steps
- Set up NGINX reverse proxy with SSL to handle static content and SSL termination
- Configure advanced firewall rules for enhanced security
- Configure Tomcat clustering with HAProxy load balancing for high availability
- Set up comprehensive Tomcat monitoring with Prometheus and Grafana
- Optimize database connection pooling for high-performance applications
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Configuration
TOMCAT_VERSION="11.0.0"
TOMCAT_USER="tomcat"
TOMCAT_HOME="/opt/tomcat"
JAVA_HOME_DEBIAN="/usr/lib/jvm/java-21-openjdk-amd64"
JAVA_HOME_RHEL="/usr/lib/jvm/java-21-openjdk"
cleanup() {
echo -e "${RED}[ERROR] Installation failed. Cleaning up...${NC}"
systemctl stop tomcat 2>/dev/null || true
systemctl disable tomcat 2>/dev/null || true
rm -rf /opt/tomcat
userdel -r tomcat 2>/dev/null || true
rm -f /etc/systemd/system/tomcat.service
systemctl daemon-reload
}
trap cleanup ERR
usage() {
echo "Usage: $0 [domain_name] [ssl_email]"
echo "Example: $0 example.com admin@example.com"
echo "If no domain provided, SSL will be skipped"
exit 1
}
print_step() {
echo -e "${GREEN}[$1] $2${NC}"
}
print_warning() {
echo -e "${YELLOW}[WARNING] $1${NC}"
}
# Check if running as root
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}This script must be run as root${NC}"
exit 1
fi
# Parse arguments
DOMAIN="${1:-}"
SSL_EMAIL="${2:-}"
if [[ -n "$DOMAIN" && -z "$SSL_EMAIL" ]]; then
echo -e "${RED}SSL email is required when domain is provided${NC}"
usage
fi
# Detect distribution
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
JAVA_HOME="$JAVA_HOME_DEBIAN"
;;
almalinux|rocky|centos|rhel|ol)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
JAVA_HOME="$JAVA_HOME_RHEL"
;;
fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
JAVA_HOME="$JAVA_HOME_RHEL"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
JAVA_HOME="$JAVA_HOME_RHEL"
;;
*)
echo -e "${RED}Unsupported distribution: $ID${NC}"
exit 1
;;
esac
else
echo -e "${RED}Cannot detect distribution${NC}"
exit 1
fi
print_step "1/8" "Updating system packages"
if [[ "$PKG_MGR" == "apt" ]]; then
apt update && apt upgrade -y
$PKG_INSTALL wget curl unzip openjdk-21-jdk
else
$PKG_INSTALL epel-release
$PKG_INSTALL wget curl unzip java-21-openjdk-devel
fi
print_step "2/8" "Verifying Java installation"
java -version
print_step "3/8" "Creating Tomcat user and directories"
useradd -r -m -U -d "$TOMCAT_HOME" -s /bin/false "$TOMCAT_USER" || true
mkdir -p "$TOMCAT_HOME"/{logs,temp,work}
chown -R "$TOMCAT_USER:$TOMCAT_USER" "$TOMCAT_HOME"
print_step "4/8" "Downloading and installing Tomcat $TOMCAT_VERSION"
cd /tmp
wget -q "https://downloads.apache.org/tomcat/tomcat-11/v${TOMCAT_VERSION}/bin/apache-tomcat-${TOMCAT_VERSION}.tar.gz"
tar -xzf "apache-tomcat-${TOMCAT_VERSION}.tar.gz"
cp -r "apache-tomcat-${TOMCAT_VERSION}"/* "$TOMCAT_HOME/"
chown -R "$TOMCAT_USER:$TOMCAT_USER" "$TOMCAT_HOME"
chmod 755 "$TOMCAT_HOME"/bin/*.sh
print_step "5/8" "Configuring JVM optimization settings"
cat > "$TOMCAT_HOME/bin/setenv.sh" << 'EOF'
#!/bin/bash
# JVM Memory Settings
CATALINA_OPTS="$CATALINA_OPTS -Xms2048m -Xmx4096m"
# Garbage Collection Optimization (G1GC)
CATALINA_OPTS="$CATALINA_OPTS -XX:+UseG1GC"
CATALINA_OPTS="$CATALINA_OPTS -XX:G1HeapRegionSize=16m"
CATALINA_OPTS="$CATALINA_OPTS -XX:MaxGCPauseMillis=200"
CATALINA_OPTS="$CATALINA_OPTS -XX:+ParallelRefProcEnabled"
# JVM Performance Tuning
CATALINA_OPTS="$CATALINA_OPTS -XX:+OptimizeStringConcat"
CATALINA_OPTS="$CATALINA_OPTS -XX:+UseStringDeduplication"
CATALINA_OPTS="$CATALINA_OPTS -Djava.awt.headless=true"
# Security Settings
CATALINA_OPTS="$CATALINA_OPTS -Djava.security.egd=file:/dev/./urandom"
# JMX Monitoring
CATALINA_OPTS="$CATALINA_OPTS -Dcom.sun.management.jmxremote"
CATALINA_OPTS="$CATALINA_OPTS -Dcom.sun.management.jmxremote.port=9999"
CATALINA_OPTS="$CATALINA_OPTS -Dcom.sun.management.jmxremote.ssl=false"
CATALINA_OPTS="$CATALINA_OPTS -Dcom.sun.management.jmxremote.authenticate=false"
CATALINA_OPTS="$CATALINA_OPTS -Dcom.sun.management.jmxremote.local.only=true"
# GC Logging
CATALINA_OPTS="$CATALINA_OPTS -Xlog:gc*:$CATALINA_BASE/logs/gc.log:time,tags"
export CATALINA_OPTS
EOF
chown "$TOMCAT_USER:$TOMCAT_USER" "$TOMCAT_HOME/bin/setenv.sh"
chmod 755 "$TOMCAT_HOME/bin/setenv.sh"
print_step "6/8" "Creating systemd service"
cat > /etc/systemd/system/tomcat.service << EOF
[Unit]
Description=Apache Tomcat 11
After=network.target
[Service]
Type=forking
User=$TOMCAT_USER
Group=$TOMCAT_USER
Environment="JAVA_HOME=$JAVA_HOME"
Environment="CATALINA_HOME=$TOMCAT_HOME"
Environment="CATALINA_BASE=$TOMCAT_HOME"
ExecStart=$TOMCAT_HOME/bin/startup.sh
ExecStop=$TOMCAT_HOME/bin/shutdown.sh
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable tomcat
systemctl start tomcat
print_step "7/8" "Configuring firewall"
if command -v ufw >/dev/null 2>&1; then
ufw allow 8080/tcp
ufw allow 8443/tcp
elif command -v firewall-cmd >/dev/null 2>&1; then
firewall-cmd --permanent --add-port=8080/tcp
firewall-cmd --permanent --add-port=8443/tcp
firewall-cmd --reload
fi
if [[ -n "$DOMAIN" ]]; then
print_step "8/8" "Installing SSL certificates"
$PKG_INSTALL certbot
# Generate SSL certificate
certbot certonly --standalone -d "$DOMAIN" --email "$SSL_EMAIL" --agree-tos --non-interactive
# Create keystore from Let's Encrypt certificate
openssl pkcs12 -export -in "/etc/letsencrypt/live/$DOMAIN/fullchain.pem" \
-inkey "/etc/letsencrypt/live/$DOMAIN/privkey.pem" \
-out "$TOMCAT_HOME/conf/keystore.p12" \
-name tomcat -CAfile "/etc/letsencrypt/live/$DOMAIN/chain.pem" \
-caname root -password pass:changeit
chown "$TOMCAT_USER:$TOMCAT_USER" "$TOMCAT_HOME/conf/keystore.p12"
chmod 600 "$TOMCAT_HOME/conf/keystore.p12"
# Configure SSL in server.xml
sed -i '/<Service name="Catalina">/a\ <Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"\
maxThreads="150" SSLEnabled="true">\
<SSLHostConfig>\
<Certificate certificateKeystoreFile="conf/keystore.p12"\
certificateKeystorePassword="changeit"\
certificateKeystoreType="PKCS12" />\
</SSLHostConfig>\
</Connector>' "$TOMCAT_HOME/conf/server.xml"
# Set up auto-renewal
echo "0 2 * * * certbot renew --quiet && systemctl restart tomcat" | crontab -
print_warning "SSL configured for $DOMAIN. Access via https://$DOMAIN:8443"
else
print_step "8/8" "Skipping SSL configuration (no domain provided)"
fi
# Verification
sleep 5
if systemctl is-active --quiet tomcat; then
echo -e "${GREEN}✓ Tomcat 11 installation completed successfully!${NC}"
echo -e "${GREEN}✓ Service is running on port 8080${NC}"
echo -e "${GREEN}✓ Access: http://$(hostname -I | awk '{print $1}'):8080${NC}"
[[ -n "$DOMAIN" ]] && echo -e "${GREEN}✓ SSL enabled: https://$DOMAIN:8443${NC}"
else
echo -e "${RED}✗ Tomcat service failed to start${NC}"
journalctl -u tomcat --no-pager -l
exit 1
fi
Review the script before running. Execute with: bash install.sh