Skip to content

Instantly share code, notes, and snippets.

@drmalex07
Last active June 7, 2024 11:07
Show Gist options
  • Save drmalex07/e6e99dad070a78d5dab24ff3ae032ed1 to your computer and use it in GitHub Desktop.
Save drmalex07/e6e99dad070a78d5dab24ff3ae032ed1 to your computer and use it in GitHub Desktop.
An example configuration for Tomcat as systemd service. #tomcat #systemd #systemd.service

README

Let Tomcat is download and installed under /opt/tomcat. Also, let tomcat be a non-provileged user under which the server will be running.

We assume that we keep server's binaries under /opt/tomcat and we will create a server instance named foo under /var/tomcat/ (carrying its own conf, logs, webapps, work, lib directories). See also https://dzone.com/articles/running-multiple-tomcat.

Create a template service unit file at /etc/systemd/system/tomcat@.service:

[Unit]
Description=Tomcat - instance %i
After=syslog.target network.target

[Service]
Type=forking

User=tomcat
Group=tomcat

WorkingDirectory=/var/tomcat/%i

Environment="JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64/"
Environment="JAVA_OPTS=-Djava.security.egd=file:///dev/urandom"

Environment="CATALINA_PID=/var/tomcat/%i/run/tomcat.pid"
Environment="CATALINA_BASE=/var/tomcat/%i/"
Environment="CATALINA_HOME=/opt/tomcat/"
Environment="CATALINA_OPTS=-Xms512M -Xmx1024M -server -XX:+UseParallelGC"

ExecStart=/opt/tomcat/bin/startup.sh
ExecStop=/opt/tomcat/bin/shutdown.sh

#RestartSec=10
#Restart=always

[Install]
WantedBy=multi-user.target

Now, we can instantiate a service instance for our foo tomcat instance:

systemctl daemon-reload
systemctl enable tomcat@foo.service
systemctl start tomcat@foo.service
@dimka310
Copy link

Hi, Alex! Nice solution :) Can you suggest how to configure systemctl service for 2 different Tomcat instances on one server (e.g. 2 different Catalina Base folders). In this case ExecStart path will be different.

@drmalex07
Copy link
Author

@dimka310 I suppose that you need 2 separate service unit files (you cannot benefit from using service templates)

@supamanda
Copy link

supamanda commented Jul 23, 2018

In order to run 2 separate services:

  • in tomcat.service update the ExecStart and ExecStop to point to the <tomcat1_home>/bin/
  • in tomcat2.service update the ExecStart and ExecStop to point to the <tomcat2_home>/bin/

And in both remove all of the Environment lines because you can't share the CATALINA variables. I used a setenv.sh file in the <tomcat_home>/bin folder to set any java opts.

@rcrathore
Copy link

@supamanda it is not correct. the startup.sh and shutdown.sh file remain in CATALINA_HOME/bin. The setenv.sh is kept in CATALINA_BASE/bin.

@sardar-ji
Copy link

Hi Alex, Is it mandatory to give CATALINA_HOME and BASE path? I have just given ExecStart and ExectStop values, however it is not working as expected

@mahsandu
Copy link

mahsandu commented Dec 10, 2019

Thanks a lot to share. I am running webmin apache tomcat plugin: I have modified like below it is working:

[Unit]
Description=Tomcat - instance %i
After=syslog.target network.target

[Service]
Type=forking

User=tomcat
Group=tomcat

ExecStart=$CATALINA_HOME/bin/startup.sh
ExecStop=$CATALINA_HOME/bin/shutdown.sh

RestartSec=10
Restart=always

@honsberg
Copy link

@skloessel
Copy link

@honsberg - fascinating .. but where is the horror? Why not exploding the WAR file in the systemd service unit ;)

@tacerus
Copy link

tacerus commented Aug 7, 2022

Thanks for the sample unit file! I added some hardening options, if anyone is interested:

[Unit]
Description=Tomcat - %i
After=syslog.target network.target

[Service]
Type=forking

User=tomcat
Group=tomcat

Environment="JAVA_HOME=/usr/lib64/jvm/java-11-openjdk-11"
Environment="JAVA_OPTS=-Djava.security.egd=file:///dev/urandom"

Environment="CATALINA_PID=/run/tomcat/%i.pid"
Environment="CATALINA_BASE=/var/lib/tomcats/%i"
Environment="CATALINA_HOME=/opt/tomcat"
ExecStart=/opt/tomcat/bin/startup.sh
ExecStop=/opt/tomcat/bin/shutdown.sh

RuntimeDirectory=tomcat

#RestartSec=10
#Restart=always

ProtectSystem=strict
ProtectHome=yes
PrivateDevices=yes
PrivateTmp=yes
PrivateUsers=yes
ProtectKernelTunables=yes
ProtectKernelLogs=yes
ProtectControlGroups=yes
ReadWritePaths=/var/lib/tomcats/%i/logs
ReadWritePaths=/var/lib/tomcats/%i/webapps
ReadWritePaths=/var/lib/tomcats/%i/work
ReadWritePaths=/var/lib/tomcats/%i/temp
RestrictAddressFamilies=AF_INET6 AF_INET
SystemCallArchitectures=native
SystemCallFilter=@system-service

[Install]
WantedBy=multi-user.target

Instantiated services work perfectly - there is no need to create multiple unit files. Simply create the directory structure $CATALINA_BASE/<instance name>/{conf,logs,webapps,work,temp} for each application you want to run with Tomcat, populate it accordingly, and control the service using systemctl [start|stop|status] tomcat@<instance name>.

@mrg2k8
Copy link

mrg2k8 commented Aug 25, 2022

The website @honsberg shared is still my go-to reference when dealing with Tomcat and systemd. It moved location to https://jdebp.uk/FGA/systemd-house-of-horror/tomcat.html. Here's the unit file:

[Unit]
Description=Apache Tomcat Web Application Container
 
[Service]
User=tomcat
Group=tomcat
EnvironmentFile=-/etc/default/tomcat
ExecStart=/usr/bin/env ${JAVA_HOME}/bin/java \
$JAVA_OPTS $CATALINA_OPTS \
-classpath ${CLASSPATH} \
-Dcatalina.base=${CATALINA_BASE} \
-Dcatalina.home=${CATALINA_HOME} \
-Djava.endorsed.dirs=${JAVA_ENDORSED_DIRS} \
-Djava.io.tmpdir=${CATALINA_TMPDIR} \
-Djava.util.logging.config.file=${CATALINA_BASE}/conf/logging.properties \
-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager \
org.apache.catalina.startup.Bootstrap \
start
ExecStop=/usr/bin/env ${JAVA_HOME}/bin/java \
$JAVA_OPTS \
-classpath ${CLASSPATH} \
-Dcatalina.base=${CATALINA_BASE} \
-Dcatalina.home=${CATALINA_HOME} \
-Djava.endorsed.dirs=${JAVA_ENDORSED_DIRS} \
-Djava.io.tmpdir=${CATALINA_TMPDIR} \
-Djava.util.logging.config.file=${CATALINA_BASE}/conf/logging.properties \
-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager \
org.apache.catalina.startup.Bootstrap \
stop
 
[Install]
WantedBy=multi-user.target

Some variables are still defined in /etc/default/tomcat, but can be moved to the unit as well:

CATALINA_HOME=/usr/share/tomcat
CATALINA_BASE=/usr/share/tomcat
CATALINA_TMPDIR=/var/tmp/tomcat
JAVA_HOME=/usr/share/java/jre-x.y.z

Of course, this can be further improved but it's a good starting point.

@skloessel, @tacerus have you actually spent a few minutes to see what's inside /opt/tomcat/bin/startup.sh? That's where the horror is..

@tacerus
Copy link

tacerus commented Aug 25, 2022

It's generally bad practice to run shell scripts in systemd's ExecStart if a native command could be used, it's unfortunately what is still often shipped in distribution packages for Java and Node applications. Thanks for the link - interesting read. Your sample file is good - just keeping the variables in an EnvironmentFile would make for an easy transition to an instantiated service if required. Additionally, it makes it easier to change parameters, as editing variables inside the unit file requires a daemon-reload - but of course, that may not be a concern for services which stay consistent.

@bioinfornatics
Copy link

bioinfornatics commented Jan 29, 2023

Thanks for your sharing,

Below a working recipe extended from:

  • @mrg2k8 : to use java call directly instead of sh script
  • @tacerus : for the hardened part (I let you extend it to get a multi instance one ;-) )
  1. Firstly go to root like user
sudo -s
  1. create tomcat user
useradd --create-home --user-group --home-dir /opt/tomcat --system --shell /bin/false tomcat
  1. Download tomcat
tomcat_version='10.1.5'
tomcat_major_version="${tomcat_version%%.*}"
curl -Lo/tmp/apache-tomcat-${tomcat_version}.tar.gz https://dlcdn.apache.org/tomcat/tomcat-${tomcat_major_version}/v${tomcat_version}/bin/apache-tomcat-${tomcat_version}.tar.gz
install -o tomcat -d tomcat -D /opt/tomcat/${tomcat_version}
tar xf /tmp/apache-tomcat-${tomcat_version}.tar.gz -C /opt/tomcat/${tomcat_version}/ --strip-components=1
pushd /opt/tomcat/
  ln -s ${tomcat_version} latest
popd
chown -R tomcat: /opt/tomcat/
find /opt/tomcat/ -type d -print0  | xargs -0 chmod 770
find /opt/tomcat/ -type f -print0   | xargs -0 chmod 660
find /opt/tomcat/ -type f -name '*.sh'  | xargs chmod 770

  1. Create a tomcat service

Env var file

cat <<EOF> /etc/default/tomcat 
CATALINA_HOME=/opt/tomcat/latest
CATALINA_BASE=/opt/tomcat/latest
CATALINA_TMPDIR=/var/tmp/tomcat
JAVA_HOME=/usr/lib/jvm/jre
JAVA_OPTS=-Djava.security.egd=file:///dev/urandom
CATALINA_CLASSPATH=/opt/tomcat/latest/bin/bootstrap.jar:/opt/tomcat/latest/bin/tomcat-juli.jar
CATALINA_OPTS=-Djava.util.logging.config.file=/opt/tomcat/latest/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djdk.tls.ephemeralDHKeySize=2048 -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -Dorg.apache.catalina.security.SecurityListener.UMASK=0027 --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED
EOF

Systemd service file

cat <<'EOF'> /etc/systemd/system/tomcat.service 
[Unit]
Description=Apache Tomcat Web Application Container
 
[Service]
[Unit]
Description=Apache Tomcat Web Application Container
 
[Service]
User=tomcat
Group=tomcat
RuntimeDirectory=tomcat
EnvironmentFile=-/etc/default/tomcat


ProtectSystem=strict
ProtectHome=yes
PrivateDevices=yes
PrivateTmp=yes
PrivateUsers=yes
ProtectKernelTunables=yes
ProtectKernelLogs=yes
ProtectControlGroups=yes
ReadWritePaths=/opt/tomcat/latest/logs
ReadWritePaths=/opt/tomcat/latest/webapps
ReadWritePaths=/opt/tomcat/latest/work
ReadWritePaths=/opt/tomcat/latest/temp
RestrictAddressFamilies=AF_INET6 AF_INET
SystemCallArchitectures=native
SystemCallFilter=@system-service

ExecStart=/usr/bin/env ${JAVA_HOME}/bin/java \
${JAVA_OPTS} ${CATALINA_OPTS} \
-classpath ${CATALINA_CLASSPATH} \
-Dcatalina.base=${CATALINA_BASE} \
-Dcatalina.home=${CATALINA_HOME} \
-Djava.endorsed.dirs=${JAVA_ENDORSED_DIRS} \
-Djava.io.tmpdir=${CATALINA_TMPDIR} \
-Djava.util.logging.config.file=${CATALINA_BASE}/conf/logging.properties \
-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager \
org.apache.catalina.startup.Bootstrap \
start
ExecStop=/usr/bin/env ${JAVA_HOME}/bin/java \
${JAVA_OPTS} \
-classpath ${CATALINA_CLASSPATH} \
-Dcatalina.base=${CATALINA_BASE} \
-Dcatalina.home=${CATALINA_HOME} \
-Djava.endorsed.dirs=${JAVA_ENDORSED_DIRS} \
-Djava.io.tmpdir=${CATALINA_TMPDIR} \
-Djava.util.logging.config.file=${CATALINA_BASE}/conf/logging.properties \
-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager \
org.apache.catalina.startup.Bootstrap \
stop
 
[Install]
WantedBy=multi-user.target

 
[Install]
WantedBy=multi-user.target

EOF

Enjoy

systemctl start tomcat
systemctl status tomcat
systemctl stop tomcat

Current issue:

  1. stop will falsely tell to systemd that failled caused by exit code 143
  2. catalina log are not viewable into journalctl

@alparslanu6347
Copy link

alparslanu6347 commented May 14, 2023

My Systemd service file, it is working.

cd /etc/systemd/system
sudo vi tomcat.service
/etc/systemd/system/tomcat.service
[Unit]
Description=Apache Tomcat Web Application Container
After=syslog.target network.target

[Service]
Type=forking

Environment=JAVA_HOME=/usr/lib/jvm/jre
Environment=CATALINA_PID=/opt/tomcat/temp/tomcat.pid
Environment=CATALINA_HOME=/opt/tomcat
Environment=CATALINA_BASE=/opt/tomcat
Environment='CATALINA_OPTS=-Xms512M -Xmx1024M -server -XX:+UseParallelGC'
Environment='JAVA_OPTS=-Djava.awt.headless=true -Djava.security.egd=file:/dev/./urandom'

ExecStart=/opt/tomcat/bin/startup.sh
ExecStop=/bin/kill -15 $MAINPID

[Install]
WantedBy=multi-user.target

@Enigo
Copy link

Enigo commented Aug 3, 2023

Just wanted to draw your attention to this systemd issue, in particular this part:

don't use ExecStop= at all, just handle SIGTERM properly

Keeping ExecStop= directive in your tomcat systemd unit might lead to some issues.
For example, if your web service relies on some shutdown hooks to be executed on tomcat shutdown those won't be executed reliaby!

if you really want to keep the directive then do as @alparslanu6347 suggested ExecStop=/bin/kill -15 $MAINPID altho I think it is the same (or similar) way systemd is doing it by default anyways

@NickJH
Copy link

NickJH commented Sep 21, 2023

https://jdebp.eu/FGA/systemd-house-of-horror/tomcat.html

This is an excellent link and at the bottom gives a great way of avoiding Type=forking. Use a service file like:

# Tomcat systemd service file

[Unit]
Description=Tomcat Server
After=network.target

[Install]
WantedBy=multi-user.target

[Service]
Environment="CATALINA_BASE=/opt/tomcat-your_site"
WorkingDirectory=/opt/tomcat-your_site
User=tomcat
Group=tomcat
PermissionsStartOnly=true
ExecStart=/opt/tomcat-9.0.79/bin/catalina.sh run

Obviously adjust to suit your own environment. I need the WorkingDirectory for some apps to add their log files (so not the standard tomcat log files) to the logs folder. Many won't need it.
There is no need to set a CATALINA_HOME as the catalina.sh works it out from the path in the ExecStart statement.
I do not set JAVA_HOME here, preferring setenv.sh. This allows the instantiated version of the start up to run different versions of Java for different CATALINA_BASEs. Even if you do set it here, it can be overridden in setenv.sh.
PermissionsStartOnly is probably totally unnecessary.
It can be simply instantiated by changing the two references tomcat-your_site to tomcat-%I

If you want a pid file, add:

RuntimeDirectoryMode=755
RuntimeDirectoryPreserve=true
Environment=CATALINA_PID=/run/tomcat/tomcat-your_site.pid

Again, it can be instantiated.

@NickJH
Copy link

NickJH commented Nov 6, 2023

Addendum.

There is a problem using the ExecStart=/opt/tomcat-9.0.79/bin/catalina.sh run method to start tomcat.

The problem is that the run method outputs to stdout and stderr. It you use catalina.sh start instead, stdout and stderr get redirected to your catalina.out file by the startup script. Using startup.sh calls catalina.sh start so it behaves the same. This redirect does not happen with the run method and so you get no catalina.out file. It is possible to play around with the systemd unit file options StandardOutput=journal, StandardError=inherit and SyslogIdentifier=??? to output to the journal, but then you have to trap the messages in the journal using an rsyslogd configlet (if you use rsyslog) and this becomes more of a pain if you have multiple tomcats and you want multiple catalina.out log files one per tomcat ......... and then its permissions don't follow the tomcats etc.

Bottom line - don't use the run method to start your tomcat with systemd.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment