Running Tomcat as a systemd Service
Write a systemd unit that runs Apache Tomcat as a dedicated non-root user, starts it at boot, restarts it after a crash, and is managed with systemctl.

ON THIS PAGE
Starting Tomcat by hand with startup.sh works until the server reboots or the Java process crashes; then the application stays down until someone notices. A systemd unit fixes both problems. This guide turns a Tomcat installation in /opt into a service that runs as its own unprivileged user, starts at boot, restarts on failure, and is controlled with systemctl like any other daemon.
Prerequisites
- A Linux server with systemd.
- Java installed and on the default
PATH. - Tomcat extracted to
/opt/apache-tomcat1. Adjust the paths below if yours differ.

How systemd finds services
systemd reads services from unit files. Packages install theirs in /lib/systemd/system/; your own units belong in /etc/systemd/system/. When you run systemctl enable, systemd creates a symbolic link in a target's .wants directory, for example /etc/systemd/system/multi-user.target.wants/apache2.service. That link is what starts the service at boot.
Create the tomcat user
Run Tomcat as a dedicated system user, not as root. If an attacker compromises a web application, they get only the rights of that user.
$ sudo useradd -r -d /opt/apache-tomcat1 -s /bin/false tomcat
$ sudo chown -R tomcat:tomcat /opt/apache-tomcat1-r creates a system account, -d sets its home to the Tomcat directory, and -s /bin/false blocks interactive logins. chown gives the user ownership of the installation, because Tomcat writes to logs/, temp/, work/ and webapps/.
Write the unit file
Create /etc/systemd/system/tomcat1.service. The file name, without .service, becomes the service name.
[Unit]
Description=Apache Tomcat Web Application Container
After=network.target
[Service]
Type=forking
Environment=CATALINA_PID=/opt/apache-tomcat1/temp/tomcat.pid
Environment=CATALINA_HOME=/opt/apache-tomcat1
Environment=CATALINA_BASE=/opt/apache-tomcat1
PIDFile=/opt/apache-tomcat1/temp/tomcat.pid
ExecStart=/opt/apache-tomcat1/bin/startup.sh
ExecStop=/opt/apache-tomcat1/bin/shutdown.sh
User=tomcat
Group=tomcat
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target| Directive | Purpose |
|---|---|
After=network.target | Starts Tomcat after the network is configured |
Type=forking | startup.sh launches Java in the background and exits; systemd treats the background process as the service |
CATALINA_PID | Tells Tomcat where to write its process ID |
PIDFile | Tells systemd to read the main PID from the same file; recommended for Type=forking so systemd tracks the right process |
CATALINA_HOME | Tomcat installation directory, where bin/ and lib/ live |
CATALINA_BASE | Directory with conf/, logs/ and webapps/ for this instance; the same as CATALINA_HOME for a single instance |
ExecStart / ExecStop | startup.sh calls catalina.sh start; shutdown.sh asks Tomcat to stop cleanly through its shutdown port |
User / Group | Runs the process as tomcat instead of root |
Restart=always / RestartSec=10 | Restarts Tomcat 10 seconds after it exits, which avoids rapid restart loops |
WantedBy=multi-user.target | Starts the service during normal boot once it is enabled |
Check three paths before you start the service: the CATALINA_* directories, the ExecStart and ExecStop scripts, and the temp/ directory that holds the PID file. A single wrong path is enough to make the unit fail.
Enable and start the service
$ sudo systemctl daemon-reload
$ sudo systemctl enable --now tomcat1
$ sudo systemctl status tomcat1daemon-reloadmakes systemd re-read unit files. Run it after every change totomcat1.service.enable --nowcreates the boot link and starts the service immediately.statusshows whether the service isactive (running), its main PID, and the latest log lines.
Verify
Tomcat listens on port 8080 by default:
$ curl -I localhost:8080
$ sudo journalctl -u tomcat1 -n 50journalctl -u tomcat1 shows systemd's log for the unit. Tomcat's own logs are in /opt/apache-tomcat1/logs/, mainly catalina.out.
Troubleshooting
| Symptom | Likely cause |
|---|---|
status=203/EXEC | ExecStart points to a missing script, or the script is not executable |
| Service starts, then fails after a timeout | PIDFile and CATALINA_PID do not match, or temp/ does not exist |
Permission denied in catalina.out | The tomcat user does not own logs/, temp/ or work/; run the chown again |
| Changes to the unit file have no effect | systemctl daemon-reload was not run |
This service is the base for the deployment in Build and Deploy a Java WAR to Tomcat with Maven and Jenkins.
Key takeaways
- Custom unit files go in
/etc/systemd/system/;systemctl enablelinks them intomulti-user.target.wants. - Tomcat's
startup.shforks, so useType=forkingwith a matchingCATALINA_PIDandPIDFile. - A dedicated
tomcatuser that owns the installation limits the damage from a compromised application. Restart=alwayswithRestartSec=10brings Tomcat back after a crash without a tight restart loop.- Run
systemctl daemon-reloadafter every unit file change.
Keep reading
- Essential Linux Commands for DevOps
A task-based Linux command reference: navigating and managing files, reading logs, grep, sed, cut and awk, redirection, Vim, services, and basic networking.
- tmux Cheat Sheet: Sessions, Windows, Panes and Copy Mode
A tmux reference for remote work: sessions that survive SSH drops, windows, split panes, copy mode, synchronized panes, and a minimal tmux.conf.
- Logical Volume Management (LVM) in Linux
Add a disk to a VirtualBox VM and take it through every LVM layer: partition, physical volume, volume group and logical volume, then an ext4 mount in fstab.