Apache Tomcat: supported versions, install, and use with httpd

Updated November 2026

Apache Tomcat runs Java web applications: it implements the Jakarta Servlet, Pages (JSP), Expression Language and WebSocket specifications and serves them over HTTP. As of November 2026 three branches are supported: 11.0 (latest 11.0.27), 10.1 (10.1.61) and 9.0 (9.0.123), and Tomcat 9.0 support ends on 31 March 2027.

What Tomcat is

Tomcat is a servlet container. A Java developer writes a web application as servlets, JSP pages and supporting classes, packs it into a WAR file (a zip archive with a fixed layout), and drops it into Tomcat. Tomcat accepts the HTTP request, picks the right application and servlet, runs it, and sends back the response.

It is not a full Jakarta EE application server. It covers the web-tier specifications listed above plus Jakarta Authentication and Annotations, but not, for example, Enterprise Beans or messaging. Many frameworks only need the web tier, which is why Tomcat is so common: Spring applications, for example, are often deployed as WAR files on Tomcat or run with Tomcat embedded inside the application.

Architecture in plain words

A Tomcat installation has two roots. CATALINA_HOME holds the binary distribution; CATALINA_BASE holds one instance's conf, logs, webapps, work and temp folders. Keeping them separate makes upgrades simple: you replace the binaries and keep your configuration.

Supported versions

The project's "Which version do I want?" page lists these supported branches (as of November 2026; all three latest releases came out on 12 October 2026):

Supported Apache Tomcat branches, as of November 2026
BranchLatest releaseServlet specJava neededStatus
11.0.x11.0.276.117 or laterCurrent focus of development
10.1.x10.1.616.011 or laterSupported; no end date announced
9.0.x9.0.1234.08 or laterSupport ends 31 March 2027

Everything older is end of life: 10.0.x (2022-10-31), 8.5.x (2024-03-31), 8.0.x (2018-06-30) and 7.0.x (2021-03-31) among them. The project tells users of those versions to upgrade.

The end of Tomcat 9.0 and the new 9.1 branch

The end-of-support announcement for 9.0.x (published 11 February 2026) says that after 31 March 2027 there will very probably be no more 9.0.x releases and security reports will not be checked against 9.0.x. Download links and the latest release are removed from the mirrors after 30 June 2027; old releases stay in the archive.

For users who cannot leave the old javax.* APIs yet, a 9.1.x branch is due shortly before 9.0 support ends, with releases planned until 31 December 2030. The differences are small: 9.1 drops the APR/native connectors for HTTP, HTTPS and AJP, and supports only Tomcat Native 2.0.x, not 1.3.x (the project has a separate end-of-support notice for Tomcat Native 1.3.x). If you use the APR connectors, move to the NIO connectors first. The project still recommends moving to a newer branch rather than staying on 9.1.

javax vs jakarta: the big break

Tomcat 10.0 changed the package names of the specification APIs from javax.servlet to jakarta.servlet (and likewise for JSP, EL and WebSocket). An application built for Tomcat 9 must be recompiled against the new APIs, or converted. Tomcat 10.1 and 11 can convert an old application at deploy time: put the WAR in the webapps-javaee folder and the converted copy appears in webapps. The separate Tomcat Migration Tool for Jakarta EE (1.0.13, released 5 October 2026) does the same conversion ahead of time.

Install and try it

From the Apache release tarball (any Linux)

Tomcat 11 needs Java 17 or later. On Debian 13 the default Java is OpenJDK 21:

sudo apt install default-jre-headless          # Debian/Ubuntu
# RHEL/Fedora: sudo dnf install java-21-openjdk-headless
java -version

Download, verify and unpack (the SHA-512 file and the release managers' KEYS file sit next to each release):

cd /tmp
curl -O https://dlcdn.apache.org/tomcat/tomcat-11/v11.0.27/bin/apache-tomcat-11.0.27.tar.gz
curl -O https://downloads.apache.org/tomcat/tomcat-11/v11.0.27/bin/apache-tomcat-11.0.27.tar.gz.sha512
curl -O https://downloads.apache.org/tomcat/tomcat-11/v11.0.27/bin/apache-tomcat-11.0.27.tar.gz.asc
curl -O https://downloads.apache.org/tomcat/tomcat-11/KEYS
sha512sum -c apache-tomcat-11.0.27.tar.gz.sha512
gpg --import KEYS
gpg --verify apache-tomcat-11.0.27.tar.gz.asc apache-tomcat-11.0.27.tar.gz
sudo tar -xzf apache-tomcat-11.0.27.tar.gz -C /opt

Start it and check that it answers:

/opt/apache-tomcat-11.0.27/bin/startup.sh
curl -I http://localhost:8080/
/opt/apache-tomcat-11.0.27/bin/shutdown.sh

Logs go to logs/catalina.out and the dated files beside it. For a real server, run Tomcat as its own unprivileged user (never root), set memory options in CATALINA_OPTS inside bin/setenv.sh rather than JAVA_OPTS, and keep CATALINA_BASE separate from the unpacked release.

From Debian packages

Debian 13 (trixie) packages both current branches: tomcat11 (11.0.22 with Debian's security patches, as of November 2026) and tomcat10 (10.1.55).

sudo apt install tomcat11
systemctl status tomcat11

Distribution packages lag behind upstream version numbers but receive security fixes from the distribution's security team. Pick one route and stay with it; mixing a tarball and a package on one host leads to confusion about which one is running.

Tomcat behind Apache httpd

A common layout puts Apache httpd in front: it terminates HTTPS, serves static files, and forwards application requests to Tomcat on the same machine or a private network.

Browser Apache httpd :443 TLS, static files Tomcat 127.0.0.1 only HTTPS HTTP :8080 AJP :8009 + secret
Two ways to connect httpd to Tomcat. Either way, Tomcat should listen only on loopback or a private network.

Plain HTTP between httpd and Tomcat is the simplest option, easy to debug with curl, and works with any back end. Enable the modules (Debian/Ubuntu; on RHEL/Fedora they are loaded by /etc/httpd/conf.modules.d/ already):

sudo a2enmod proxy proxy_http headers ssl

Inside the HTTPS virtual host (your existing SSLEngine and certificate lines stay as they are):

<VirtualHost *:443>
    ServerName app.example.com
    # ... SSLEngine on, SSLCertificateFile, SSLCertificateKeyFile ...

    ProxyPreserveHost On
    RequestHeader set X-Forwarded-Proto "https"
    ProxyPass        "/" "http://127.0.0.1:8080/"
    ProxyPassReverse "/" "http://127.0.0.1:8080/"
</VirtualHost>

mod_proxy_http adds X-Forwarded-For on its own. To make Tomcat use the real client address and see that the original request was HTTPS, add the Remote IP valve inside the <Host> element of server.xml:

<Valve className="org.apache.catalina.valves.RemoteIpValve" />

By default it reads X-Forwarded-For and X-Forwarded-Proto and trusts proxies on loopback and private address ranges; set internalProxies if your proxy sits elsewhere or you want a narrower list. Finally, bind the HTTP connector to loopback so nobody can bypass httpd: add address="127.0.0.1" to the <Connector port="8080" ...> element. Test and reload httpd:

sudo apachectl configtest && sudo systemctl reload apache2   # RHEL/Fedora: httpd

On RHEL with SELinux enforcing, httpd may be refused when it connects to port 8080 ("permission denied" in error_log). Allow outbound proxy connections with sudo setsebool -P httpd_can_network_connect 1. The full proxy setup is covered in the reverse proxy guide; several Tomcats behind one httpd are covered in load balancing.

Option 2: mod_proxy_ajp

AJP is a binary protocol that passes TLS details and the authenticated user to Tomcat. It is useful mainly when an application depends on those attributes, or you are migrating from mod_jk. Tomcat treats AJP connections as more trusted than HTTP ones, so the port must never be reachable by untrusted users.

The AJP connector ships commented out in the default server.xml. Enable it on loopback with a secret:

<Connector protocol="AJP/1.3"
           address="127.0.0.1"
           port="8009"
           redirectPort="8443"
           secret="replace-with-a-long-random-string" />

Tomcat refuses to start an AJP connector without a secret, because secretRequired defaults to true. On the httpd side (2.4.42 or later), pass the same secret:

sudo a2enmod proxy proxy_ajp
# in the virtual host:
ProxyPass "/" "ajp://127.0.0.1:8009/" secret=replace-with-a-long-random-string

The secret sits in plain text in both files, so keep them readable only by root and the service user.

Ghostcat (CVE-2020-1938)

In February 2020 the Tomcat team published CVE-2020-1938, rated Important, better known as Ghostcat. Before 9.0.31 (and 8.5.51), Tomcat shipped with the AJP connector enabled and listening on all addresses. Anyone who could reach port 8009 could read any file in a web application, including WEB-INF, and make Tomcat process any file as a JSP. If the application also accepted file uploads stored inside the application, this became remote code execution. The fix hardened the AJP defaults, which is where the secret requirement and loopback-only default come from.

Check now. If you run any Tomcat older than the supported releases above, run ss -ltnp | grep 8009. A listener on 0.0.0.0 or a public address means you should firewall it, disable the AJP connector if you do not use it, and upgrade.

Upgrades and security advisories

Sources