Note: While this guide focuses on current stable versions (Debian 12 and RHEL 9), the steps should work similarly on newer versions like Debian 13 and RHEL 10 when they become available. Always check the official Apache Tomcat documentation for the latest compatibility information.
| Version | Status | Java Required | Best For |
|---|---|---|---|
| Tomcat 11 | Stable (11.0.x) | Java 17+ | New deployments, latest features |
| Tomcat 10 | Stable (10.1.x) | Java 11+ | Most production deployments |
| Tomcat 9 | Stable (9.0.x) | Java 8+ | Legacy applications, maximum compatibility |
This guide uses Tomcat 11. For Tomcat 10 or 9, adjust the version number in download URLs and paths.
On Debian 12:
sudo apt update
sudo apt install openjdk-17-jdk
On RHEL 9:
sudo dnf install java-17-openjdk-devel
Verify:
java -version
Note: Tomcat 11 requires Java 17 or later. For Tomcat 10, Java 11+ works. For Tomcat 9, Java 8+ is sufficient.
sudo useradd --system --home /opt/tomcat --shell /usr/sbin/nologin tomcat
cd /tmp
TOMCAT_VERSION=11.0.18
curl -LO "https://dlcdn.apache.org/tomcat/tomcat-11/v${TOMCAT_VERSION}/bin/apache-tomcat-${TOMCAT_VERSION}.tar.gz"
sudo mkdir -p /opt/tomcat
sudo tar -xzf "apache-tomcat-${TOMCAT_VERSION}.tar.gz" -C /opt/tomcat --strip-components=1
sudo chown -R tomcat:tomcat /opt/tomcat
Check the latest 11.0.x release before installation:
https://tomcat.apache.org/download-11.cgi
For production deployments, remove unnecessary default applications:
# Remove documentation and examples (reveal version info)
sudo rm -rf /opt/tomcat/webapps/docs
sudo rm -rf /opt/tomcat/webapps/examples
sudo rm -rf /opt/tomcat/webapps/ROOT
# Or keep ROOT but replace with your application
# sudo rm /opt/tomcat/webapps/ROOT/*
If you need to use the Manager or Host Manager applications, configure authentication and access control.
Edit the user configuration file:
sudo nano /opt/tomcat/conf/tomcat-users.xml
Add users and roles before the closing </tomcat-users> tag:
<tomcat-users>
<!-- Manager roles -->
<role rolename="manager-gui"/>
<role rolename="manager-script"/>
<role rolename="manager-jmx"/>
<role rolename="manager-status"/>
<!-- Host Manager roles -->
<role rolename="admin-gui"/>
<role rolename="admin-script"/>
<!-- Admin user with all roles -->
<user username="admin" password="CHANGE_THIS_PASSWORD" roles="manager-gui,manager-script,admin-gui"/>
<!-- Manager-only user -->
<user username="manager" password="CHANGE_THIS_PASSWORD" roles="manager-gui,manager-status"/>
</tomcat-users>
Role descriptions:
| Role | Access |
|---|---|
manager-gui |
HTML Manager interface |
manager-script |
Text interface and status |
manager-jmx |
JMX proxy access |
manager-status |
Status page only |
admin-gui |
Host Manager HTML interface |
admin-script |
Host Manager script interface |
Tomcat 10+ restricts Manager access to localhost by default. To allow remote access from specific IPs:
sudo nano /opt/tomcat/webapps/manager/META-INF/context.xml
Comment out or modify the RemoteAddrValve:
<Context>
<!-- Comment out the default localhost-only restriction -->
<!--
<Valve className="org.apache.catalina.valves.RemoteAddrValve"
allow="127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1" />
-->
<!-- Add your admin IP addresses -->
<Valve className="org.apache.catalina.valves.RemoteAddrValve"
allow="127\.\d+\.\d+\.\d+|::1|YOUR_ADMIN_IP_1|YOUR_ADMIN_IP_2" />
</Context>
sudo nano /opt/tomcat/webapps/host-manager/META-INF/context.xml
<Context>
<!-- Add your admin IP addresses -->
<Valve className="org.apache.catalina.valves.RemoteAddrValve"
allow="127\.\d+\.\d+\.\d+|::1|YOUR_ADMIN_IP_1|YOUR_ADMIN_IP_2" />
</Context>
Security Note: Only allow specific IP addresses. Never use
allow=".*"in production.
Create a service file:
sudo tee /etc/systemd/system/tomcat.service >/dev/null <<'SERVICE'
[Unit]
Description=Apache Tomcat Web Application Container
After=network.target
[Service]
Type=forking
User=tomcat
Group=tomcat
# Tomcat paths
Environment=CATALINA_HOME=/opt/tomcat
Environment=CATALINA_BASE=/opt/tomcat
Environment=CATALINA_PID=/opt/tomcat/temp/tomcat.pid
# Java configuration
# On Debian: JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
# On RHEL: JAVA_HOME=/usr/lib/jvm/java-17-openjdk
Environment=JAVA_HOME=/usr/lib/jvm/java-17-openjdk
# Startup/shutdown
ExecStart=/opt/tomcat/bin/startup.sh
ExecStop=/opt/tomcat/bin/shutdown.sh
Restart=on-failure
# Basic security (safe for most deployments)
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=full
ProtectHome=true
ProtectControlGroups=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectClock=yes
LockPersonality=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
RestrictNamespaces=yes
SystemCallArchitectures=native
CapabilityBoundingSet=
AmbientCapabilities=
# Required writable paths for Tomcat
ReadWritePaths=/opt/tomcat/logs
ReadWritePaths=/opt/tomcat/temp
ReadWritePaths=/opt/tomcat/work
ReadWritePaths=/opt/tomcat/webapps
# Optional: Custom temp directory
# Environment=JAVA_OPTS=-Djava.io.tmpdir=/var/tmp/tomcat-temp
# ReadWritePaths=/var/tmp/tomcat-temp
[Install]
WantedBy=multi-user.target
SERVICE
Note: On Debian, set
JAVA_HOMEto/usr/lib/jvm/java-17-openjdk-amd64. On RHEL, use/usr/lib/jvm/java-17-openjdk.
For maximum security, you can use stricter settings, but these may break applications that need write access outside Tomcat directories:
# Replace ProtectSystem=full with:
ProtectSystem=strict
# Add explicit writable paths:
ReadWritePaths=/opt/tomcat/logs
ReadWritePaths=/opt/tomcat/temp
ReadWritePaths=/opt/tomcat/work
ReadWritePaths=/opt/tomcat/webapps
ReadWritePaths=/etc/tomcat # Only if config is written at runtime
# Additional hardening (may break some apps):
PrivateDevices=yes
ProtectHostname=yes
ProtectProc=invisible
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
UMask=0077
Warning: If Tomcat fails to start with hardening enabled:
- Check logs:
journalctl -u tomcat -n 50- Look for “Permission denied” or “Read-only file system” errors
- Add missing paths to
ReadWritePaths=- Consider using
ProtectSystem=fullinstead ofstrict
sudo systemctl daemon-reload
sudo systemctl enable --now tomcat
On UFW (Debian/Ubuntu):
sudo ufw allow 8080/tcp
On firewalld (RHEL/CentOS):
sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --reload
Open http://SERVER_IP:8080 in your browser.
If you configured the Manager app, access it at:
http://SERVER_IP:8080/manager/html
For Host Manager:
http://SERVER_IP:8080/host-manager/html
autoDeploy="false" in server.xml)chmod -R 750 /opt/tomcat)| File | Purpose |
|---|---|
/opt/tomcat/conf/server.xml |
Connectors, ports, SSL |
/opt/tomcat/conf/tomcat-users.xml |
Users and roles |
/opt/tomcat/conf/web.xml |
Default servlet config |
/opt/tomcat/webapps/manager/META-INF/context.xml |
Manager access control |
/opt/tomcat/webapps/host-manager/META-INF/context.xml |
Host Manager access control |
# Check service status
sudo systemctl status tomcat
# View logs
sudo journalctl -u tomcat -n 50
# Check catalina.out
sudo tail -f /opt/tomcat/logs/catalina.out
If you see permission errors with systemd hardening:
# Check which paths are read-only
systemd-analyze security tomcat
# Add missing writable paths to the service file
sudo systemctl edit tomcat
# Add:
[Service]
ReadWritePaths=/path/that/needs/write/access
tomcat-users.xmlEdit systemd service to add JVM memory settings:
Environment=JAVA_OPTS=-Xms512m -Xmx2g -XX:MaxMetaspaceSize=512m
Prefer automation? Choose your deployment method:
| Method | Description | Playbook |
|---|---|---|
| Native Installation | Installs Tomcat directly on the host system | Apache Tomcat Ansible Setup |
| Docker Deployment | Deploys Tomcat using Docker Compose | Apache Tomcat Docker Ansible Setup |
Prefer manual container setup? See Apache Tomcat Docker Setup.
See Apache Tomcat Configuration for detailed configuration guidance including server.xml, connectors, and virtual hosting.
See Apache Tomcat Security for hardening and operational security controls.
Any questions?
Feel free to contact us. Find all contact information on our contact page.