Deploying Tomcat by hand on every server gets old fast, especially when you’re managing multiple environments across different Linux distributions. This Ansible role handles the entire process: Java installation, Tomcat download and extraction, systemd service setup, JVM tuning, firewall rules, and Manager UI credentials. One playbook run, and Tomcat is ready to serve.
Tested April 2026 on Ubuntu 24.04, Ubuntu 22.04, Debian 12, Rocky Linux 9, AlmaLinux 9, and Fedora 41 with Tomcat 9.0.117, 10.1.54, and 11.0.21
The role supports Tomcat 9, 10, and 11 with a single variable change. It includes a version map that auto-resolves the latest tested release for each major version, so you only need to set tomcat_major_version and the role picks the right archive. The full source is on GitHub. If you’re new to Ansible, start with our Ansible installation guide for RHEL and Debian systems.
Supported Platforms
The role has been tested on these operating systems with all three Tomcat major versions.
| OS | Versions | Status |
|---|---|---|
| Ubuntu | 24.04, 22.04 | Tested |
| Debian | 12 | Tested |
| Rocky Linux | 9 | Tested |
| AlmaLinux | 9 | Tested |
| Fedora | 41, 40 | Tested |
| RHEL | 9 | Compatible |
Prerequisites
- A control node with Ansible >= 2.15 installed
- One or more target servers running a supported OS with SSH access
- sudo or root privileges on target servers
- Internet access on targets (to download the Tomcat archive from Apache)
Verify Ansible is installed on your control node.
ansible --version
The output confirms the installed version.
ansible [core 2.18.4]
config file = /etc/ansible/ansible.cfg
configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /usr/lib/python3/dist-packages/ansible
ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections
executable location = /usr/bin/ansible
python version = 3.12.3 (main, Feb 4 2025, 14:48:35) [GCC 13.3.0] (/usr/bin/python3)
jinja version = 3.1.2
libyaml = True
Clone the Ansible Role
Clone the repository and install the required Ansible collection.
git clone https://github.com/jmutai/tomcat-ansible.git
cd tomcat-ansible
ansible-galaxy collection install -r requirements.yml
The ansible.posix collection is needed for firewall management on RHEL-based systems.
Configure Inventory
Edit the hosts file and add your target server IP addresses.
vim hosts
Add one server per line under the [tomcat_nodes] group.
[tomcat_nodes]
10.0.1.50
10.0.1.51
If your SSH user differs from root, specify it in the inventory.
[tomcat_nodes]
10.0.1.50 ansible_user=ubuntu
10.0.1.51 ansible_user=rocky
Role Variables
The role ships with sensible defaults. You only need to override what you want to change. All variables use the tomcat_ prefix.
| Variable | Default | Description |
|---|---|---|
tomcat_major_version | "10" | Major version: 9, 10, or 11 |
tomcat_version | "10.1.54" | Full version (auto-resolved from major) |
tomcat_install_dir | "/opt/tomcat" | Installation directory |
tomcat_java_version | "17" | Java version: 11, 17, or 21 |
tomcat_port | 8080 | HTTP connector port |
tomcat_manager_user | "manager" | Manager UI username |
tomcat_manager_password | "changeme" | Manager UI password |
tomcat_admin_user | "admin" | Admin UI username |
tomcat_admin_password | "changeme" | Admin UI password |
tomcat_jvm_memory_min | "512M" | Initial JVM heap size |
tomcat_jvm_memory_max | "1024M" | Maximum JVM heap size |
tomcat_manager_allowed_ips | ".*" | Regex for allowed Manager IPs |
tomcat_configure_firewall | true | Open port in firewalld (RedHat only) |
The role includes a built-in version map, so you only need to set tomcat_major_version. The correct release is resolved automatically: 9 maps to 9.0.117, 10 to 10.1.54, and 11 to 11.0.21. Check for newer releases at the Apache Tomcat download page.
Deploy Tomcat with Ansible
The simplest deployment uses the defaults, which installs Tomcat 10 with Java 17.
ansible-playbook tomcat-setup.yml \
-e "tomcat_manager_password=Str0ngP@ss1 tomcat_admin_password=Str0ngP@ss2"
For Tomcat 9, just change the major version. The role auto-resolves to 9.0.117.
ansible-playbook tomcat-setup.yml \
-e "tomcat_major_version=9 tomcat_manager_password=Str0ngP@ss1 tomcat_admin_password=Str0ngP@ss2"
Tomcat 11 works the same way.
ansible-playbook tomcat-setup.yml \
-e "tomcat_major_version=11 tomcat_manager_password=Str0ngP@ss1 tomcat_admin_password=Str0ngP@ss2"
If you need password authentication instead of SSH keys, add --ask-pass. For sudo users, add --ask-become-pass.
ansible-playbook tomcat-setup.yml --ask-pass --ask-become-pass \
-e "tomcat_manager_password=Str0ngP@ss1 tomcat_admin_password=Str0ngP@ss2"
Playbook Execution Output
A successful run on Ubuntu 24.04 with Tomcat 10 produces output like this.
PLAY [Tomcat deployment playbook] **********************************************
TASK [Gathering Facts] *********************************************************
ok: [10.0.1.50]
TASK [tomcat : Preflight checks] ***********************************************
included: roles/tomcat/tasks/preflight.yml for 10.0.1.50
TASK [tomcat : Validate tomcat_major_version] **********************************
ok: [10.0.1.50] => All assertions passed
TASK [tomcat : Validate tomcat_java_version] ***********************************
ok: [10.0.1.50] => All assertions passed
TASK [tomcat : Validate OS family] *********************************************
ok: [10.0.1.50] => All assertions passed
TASK [tomcat : Install Tomcat] *************************************************
included: roles/tomcat/tasks/install.yml for 10.0.1.50
TASK [tomcat : Install Java (Debian family)] ***********************************
changed: [10.0.1.50]
TASK [tomcat : Create tomcat user] *********************************************
changed: [10.0.1.50]
TASK [tomcat : Download Tomcat archive] ****************************************
changed: [10.0.1.50]
TASK [tomcat : Extract Tomcat archive] *****************************************
changed: [10.0.1.50]
TASK [tomcat : Configure Tomcat] ***********************************************
included: roles/tomcat/tasks/configure.yml for 10.0.1.50
TASK [tomcat : Deploy systemd service file] ************************************
changed: [10.0.1.50]
TASK [tomcat : Start and enable Tomcat service] ********************************
changed: [10.0.1.50]
TASK [tomcat : Verify Tomcat installation] *************************************
included: roles/tomcat/tasks/verify.yml for 10.0.1.50
TASK [tomcat : Wait for Tomcat port to be available] ***************************
ok: [10.0.1.50]
TASK [tomcat : Verify Tomcat is responding] ************************************
ok: [10.0.1.50]
TASK [tomcat : Display Tomcat status] ******************************************
ok: [10.0.1.50] => {
"msg": "Tomcat 10.1.54 is running on port 8080"
}
PLAY RECAP *********************************************************************
10.0.1.50 : ok=27 changed=14 unreachable=0 failed=0 skipped=5 rescued=0 ignored=0
The role includes a built-in health check that waits for Tomcat to start and verifies an HTTP 200 response. No need to manually check if the service came up.
Verify Tomcat Installation
Open your browser and navigate to http://10.0.1.50:8080. The Tomcat welcome page confirms a successful deployment.
The Manager UI is available at http://10.0.1.50:8080/manager/html using the credentials you passed during deployment. The Host Manager is at http://10.0.1.50:8080/host-manager/html.
Secure Credentials with Ansible Vault
Passing passwords on the command line works for testing, but production deployments should use Ansible Vault to encrypt sensitive data. Create a vault file with your credentials.
ansible-vault create vault.yml
Add the password variables inside the vault file.
tomcat_manager_password: YourSecureManagerPass
tomcat_admin_password: YourSecureAdminPass
Then reference it when running the playbook.
ansible-playbook tomcat-setup.yml -e @vault.yml --ask-vault-pass
JVM Memory Tuning
The role deploys a setenv.sh script to the Tomcat bin/ directory, which is the recommended way to configure JVM options. By default, Tomcat gets 512M initial heap and 1024M max heap. For production workloads, adjust these values.
ansible-playbook tomcat-setup.yml \
-e "tomcat_jvm_memory_min=1024M tomcat_jvm_memory_max=2048M" \
-e @vault.yml --ask-vault-pass
For full control over JVM flags, override tomcat_catalina_opts directly.
ansible-playbook tomcat-setup.yml \
-e 'tomcat_catalina_opts="-Xms1024M -Xmx2048M -XX:+UseG1GC -Djava.awt.headless=true"' \
-e @vault.yml --ask-vault-pass
Restrict Manager UI Access
By default, the Manager and Host Manager apps are accessible from any IP. In production, restrict access to specific addresses using the tomcat_manager_allowed_ips variable. This sets a RemoteAddrValve in the Tomcat context configuration.
ansible-playbook tomcat-setup.yml \
-e 'tomcat_manager_allowed_ips=127\.0\.0\.1|10\.0\.1\..*' \
-e @vault.yml --ask-vault-pass
This restricts access to localhost and the 10.0.1.0/24 subnet. Anyone else gets a 403 Forbidden when trying to access the Manager interface.
What the Role Does
The role is structured into five task phases that run in sequence. Understanding each phase helps when troubleshooting or customizing the deployment. You can learn more about Ansible role structure and best practices if you’re building your own roles.
- Preflight validates inputs (supported OS, valid Java and Tomcat versions) and warns if passwords are still set to the default “changeme”
- Install installs Java, creates a dedicated
tomcatsystem user, downloads the archive from Apache, and extracts it to the configured directory - Configure deploys the systemd service file,
setenv.shfor JVM tuning,tomcat-users.xmlwith Manager credentials, andcontext.xmlwith IP restrictions - Firewall (RedHat only) installs and enables firewalld, then opens the Tomcat port
- Verify waits for the port to open and sends an HTTP request to confirm Tomcat returns a 200 response
The role is idempotent. Running it a second time produces zero changes if nothing has been modified, which makes it safe to include in regular configuration management runs.
Using Group Variables for Multiple Environments
For managing multiple environments (dev, staging, production), use Ansible group variables instead of command line overrides. Create a file at group_vars/tomcat_nodes/main.yml with your settings. The Ansible playbook tutorial covers variable precedence in detail.
mkdir -p group_vars/tomcat_nodes
Add your environment-specific configuration.
vim group_vars/tomcat_nodes/main.yml
tomcat_major_version: "11"
tomcat_java_version: "21"
tomcat_jvm_memory_max: "2048M"
tomcat_manager_allowed_ips: '127\.0\.0\.1'
Now a plain ansible-playbook tomcat-setup.yml picks up all the settings automatically.
Tomcat Version Comparison
Each Tomcat major version targets a different Jakarta EE (or Java EE) specification level. Pick the version that matches your application’s requirements.
| Feature | Tomcat 9 | Tomcat 10 | Tomcat 11 |
|---|---|---|---|
| Servlet Spec | 4.0 (javax.*) | 6.0 (jakarta.*) | 6.1 (jakarta.*) |
| Java Minimum | Java 8 | Java 11 | Java 17 |
| Namespace | javax.servlet | jakarta.servlet | jakarta.servlet |
| Default Version | 9.0.117 | 10.1.54 | 11.0.21 |
| Use Case | Legacy javax apps | Jakarta EE 9/10 apps | Latest Jakarta EE 11 |
Tomcat 9 is the right choice if your application uses the javax.servlet namespace. Tomcat 10 and 11 require migrated applications using jakarta.servlet. This is the most common migration gotcha when upgrading.