Skip to content
DBDeependra Bhatta~/notes
Linux#java · #systemd · #linux · #tomcat

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.

· updated · 4 min read
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.

Terminal listing /opt/apache-tomcat1 with the bin, conf, lib, logs, temp, webapps and work directories

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.

terminal
$ 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.

INItomcat1.service
etcsystemdsystemtomcat1.service
[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
DirectivePurpose
After=network.targetStarts Tomcat after the network is configured
Type=forkingstartup.sh launches Java in the background and exits; systemd treats the background process as the service
CATALINA_PIDTells Tomcat where to write its process ID
PIDFileTells systemd to read the main PID from the same file; recommended for Type=forking so systemd tracks the right process
CATALINA_HOMETomcat installation directory, where bin/ and lib/ live
CATALINA_BASEDirectory with conf/, logs/ and webapps/ for this instance; the same as CATALINA_HOME for a single instance
ExecStart / ExecStopstartup.sh calls catalina.sh start; shutdown.sh asks Tomcat to stop cleanly through its shutdown port
User / GroupRuns the process as tomcat instead of root
Restart=always / RestartSec=10Restarts Tomcat 10 seconds after it exits, which avoids rapid restart loops
WantedBy=multi-user.targetStarts 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

terminal
$ sudo systemctl daemon-reload
$ sudo systemctl enable --now tomcat1
$ sudo systemctl status tomcat1
  • daemon-reload makes systemd re-read unit files. Run it after every change to tomcat1.service.
  • enable --now creates the boot link and starts the service immediately.
  • status shows whether the service is active (running), its main PID, and the latest log lines.

Verify

Tomcat listens on port 8080 by default:

terminal
$ curl -I localhost:8080
$ sudo journalctl -u tomcat1 -n 50

journalctl -u tomcat1 shows systemd's log for the unit. Tomcat's own logs are in /opt/apache-tomcat1/logs/, mainly catalina.out.

Troubleshooting

SymptomLikely cause
status=203/EXECExecStart points to a missing script, or the script is not executable
Service starts, then fails after a timeoutPIDFile and CATALINA_PID do not match, or temp/ does not exist
Permission denied in catalina.outThe tomcat user does not own logs/, temp/ or work/; run the chown again
Changes to the unit file have no effectsystemctl 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 enable links them into multi-user.target.wants.
  • Tomcat's startup.sh forks, so use Type=forking with a matching CATALINA_PID and PIDFile.
  • A dedicated tomcat user that owns the installation limits the damage from a compromised application.
  • Restart=always with RestartSec=10 brings Tomcat back after a crash without a tight restart loop.
  • Run systemctl daemon-reload after every unit file change.