Installing Tomcat 9 on Linux

Mascote do LinuxPro instalando um módulo de aplicação em um rack de servidores Apache Tomcat

Note (2026): Original article from April 2017 (Tomcat 9.0.0.M21 + Oracle JRE 8). The title and URL were kept because they are indexed; the procedure below installs Apache Tomcat 11.0.25 with OpenJDK 25 LTS, the current versions in September 2026.

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

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.