
Tomcat remains the most widely used servlet container on Linux servers. This how-to installs the current branch from Apache's official tarball, with a dedicated user and a systemd unit — on Ubuntu 24.04/22.04 and equivalent Debian releases.
Which version to use
In September 2026 Apache maintains three stable branches. For a new installation, use 11:
| Tomcat | Minimum Java version | API | Status |
|---|---|---|---|
| 11.0.25 | 17+ | Jakarta EE 11 (jakarta.*) |
current — use this one |
| 10.1.59 | 11+ | Jakarta EE 10 (jakarta.*) |
supported |
| 9.0.121 | 8+ | Java EE 8 (javax.*) |
EOL on 31/03/2027 |
Tomcat 11 implements Servlet 6.1 / JSP 4.0 and requires Java 17 or later. OpenJDK 25 is the newest LTS and runs on this branch. Applications written for Tomcat 9 (javax.*) do not run directly on 10/11 — they need to migrate the namespace with the official tool. If the app is still javax.* and you cannot migrate now, see the Tomcat 9 section at the end.
Installing OpenJDK 25
Forget Oracle's tarball with its license cookie: on Ubuntu/Debian OpenJDK is installed through apt and receives security updates along with the system. On a server, the headless variant is enough (without graphical libraries).
sudo apt update
sudo apt install -y openjdk-25-jdk-headless
java -version
If the package openjdk-25-jdk-headless is not available in your release, install 21 (openjdk-21-jdk-headless) — Tomcat 11 accepts any Java ≥ 17. Check JAVA_HOME (the path differs between amd64 and arm64):
JAVA_HOME=$(dirname "$(dirname "$(readlink -f "$(which java)")")")
echo "$JAVA_HOME"
# exemplo amd64: /usr/lib/jvm/java-25-openjdk-amd64
Creating the tomcat group and user
The process should not run as root. A system account, with no login and no extra home directory:
sudo groupadd --system tomcat
sudo useradd --system --no-create-home --gid tomcat
--shell /usr/sbin/nologin --home-dir /usr/local/tomcat tomcat
Downloading and installing Tomcat 11
Get the tarball from the official CDN and check its SHA-512. The version below is 11.0.25 (12/08/2026); if a newer 11.0.x is already available at tomcat.apache.org/download-11.cgi, change VER.
VER=11.0.25
cd /tmp
curl -fLO "https://dlcdn.apache.org/tomcat/tomcat-11/v${VER}/bin/apache-tomcat-${VER}.tar.gz"
curl -fLO "https://downloads.apache.org/tomcat/tomcat-11/v${VER}/bin/apache-tomcat-${VER}.tar.gz.sha512"
sha512sum -c "apache-tomcat-${VER}.tar.gz.sha512"
sudo mkdir -p /usr/local/tomcat
sudo tar -xzf "apache-tomcat-${VER}.tar.gz" -C /usr/local/tomcat --strip-components=1
sudo chown -R tomcat:tomcat /usr/local/tomcat
sudo find /usr/local/tomcat -type d -exec chmod 750 {} ;
sudo find /usr/local/tomcat -type f -exec chmod 640 {} ;
sudo chmod 750 /usr/local/tomcat/bin/*.sh
JVM memory is configured in setenv.sh, which catalina.sh loads automatically — this avoids mixing options containing spaces into systemd:
sudo tee /usr/local/tomcat/bin/setenv.sh > /dev/null << 'EOF'
export CATALINA_OPTS="-Xms512M -Xmx1024M -server"
export JAVA_OPTS="-Djava.awt.headless=true -Djava.security.egd=file:/dev/./urandom"
EOF
sudo chown tomcat:tomcat /usr/local/tomcat/bin/setenv.sh
sudo chmod 750 /usr/local/tomcat/bin/setenv.sh
Adjust -Xmx to the machine's RAM. 1 GB is a reasonable starting point for a small host.
systemd unit
Type=simple with catalina.sh run keeps Java in the foreground, which is what systemd expects. (The essential systemd commands cover start/stop/status in more detail.)
JAVA_HOME=$(dirname "$(dirname "$(readlink -f "$(which java)")")")
sudo tee /etc/systemd/system/tomcat.service > /dev/null << EOF
[Unit]
Description=Apache Tomcat 11
After=network.target
[Service]
Type=simple
User=tomcat
Group=tomcat
Environment=JAVA_HOME=${JAVA_HOME}
Environment=CATALINA_HOME=/usr/local/tomcat
Environment=CATALINA_BASE=/usr/local/tomcat
WorkingDirectory=/usr/local/tomcat
ExecStart=/usr/local/tomcat/bin/catalina.sh run
SuccessExitStatus=143
Restart=on-failure
RestartSec=10
UMask=0007
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now tomcat
sudo systemctl status tomcat --no-pager
Checking that it started
ss -lntp | grep 8080
curl -I http://127.0.0.1:8080/
sudo journalctl -u tomcat -e --no-pager
Tomcat's default page should return 200 at http://IP:8080/. If the port does not open, the log in /usr/local/tomcat/logs/catalina.out and the journalctl show the reason (an incorrect JAVA_HOME is the most common).
Open 8080 in the firewall only if Tomcat will be accessed externally. With a reverse proxy on the same machine, keep 8080 on localhost only:
# acesso direto (se precisar)
sudo ufw allow 8080/tcp comment 'Tomcat'
Manager (optional)
Manager and Host Manager are closed to the outside world by default: the RemoteAddrValve in the apps manager and host-manager only accepts localhost. Create a user with a strong password — never admin/admin:
SENHA=$(openssl rand -base64 18)
echo "senha do manager: $SENHA"
sudo tee /usr/local/tomcat/conf/tomcat-users.xml > /dev/null << EOF
<?xml version="1.0" encoding="UTF-8"?>
<tomcat-users xmlns="https://tomcat.apache.org/xml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://tomcat.apache.org/xml tomcat-users.xsd"
version="1.0">
<role rolename="manager-gui"/>
<role rolename="admin-gui"/>
<user username="admin" password="${SENHA}" roles="manager-gui,admin-gui"/>
</tomcat-users>
EOF
sudo chown tomcat:tomcat /usr/local/tomcat/conf/tomcat-users.xml
sudo chmod 640 /usr/local/tomcat/conf/tomcat-users.xml
sudo systemctl restart tomcat
Visit http://127.0.0.1:8080/manager/html (use an SSH tunnel if the host is remote). Do not expose Manager to the internet.
Port 80: nginx in front, not iptables
In 2017 this article redirected 80→8080 with iptables NAT. Today the right approach is a reverse proxy: TLS, Host headers, and logs in nginx, Tomcat only on 127.0.0.1:8080.
server {
listen 80;
server_name app.exemplo.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;
}
}
If you really want the old NAT approach (without nginx):
sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8080
# persista a regra no firewall da distro (nftables/iptables-persistent/ufw),
# não no /etc/rc.local.
Still need Tomcat 9?
Legacy apps using javax.* (Spring Boot 2, Java EE 8) do not run on Tomcat 11. Two options:
- Migrate the WAR with the Tomcat Migration Tool for Jakarta EE and run it on 11.
- Stay on 9.0.x until the app migrates. The 9.0 branch receives fixes until 31/03/2027; the procedure is the same as this article, changing the download URL to 9.0.121 at download-90.cgi (Java 8+; OpenJDK 21 also works).
Original video (2017)
The recording below shows the original installation (Tomcat 9 milestone). The workflow — dedicated user, tarball, service at boot — remains the same; the versions and Java do not.
References
- Download Tomcat 11
- Which version? (official table)
- Tomcat 11 documentation
- End of support for Tomcat 9.0.x
- javax → jakarta migration
- OpenJDK in Ubuntu repositories
With that, you have Tomcat 11 + OpenJDK 25 running as a service, verified by checksum, without a proprietary JRE, and with updates through apt on the Java side. For the next 11.0.x patch, just replace the tarball and repeat chown — the JAVA_HOME and the systemd unit stay the same.