Read the status number first. Run systemctl status tomcat. If it shows status=203/EXEC, the path in your unit’s ExecStart is wrong or not executable, and nothing in Tomcat’s own logs will help because Tomcat never ran.
systemctl status tomcat | grep -E 'Process|status=' ls -l /opt/tomcat/bin/startup.sh /opt/tomcat/bin/catalina.sh
Most Tomcat installs on Linux use a hand-written unit file rather than a packaged one, so the unit itself is where most of these failures live.
The unit file causes
- Wrong path or missing execute bit (203/EXEC). The unit points at
/opt/tomcatbut Tomcat was unpacked to/opt/apache-tomcat-10.1.x, or the scripts lost their execute permission when the archive was copied. Fix the path, or symlink/opt/tomcatto the versioned directory, and restore the bit withsudo chmod +x /opt/tomcat/bin/*.sh. - Type does not match the start script.
startup.shlaunches Tomcat in the background and exits. WithType=simple, systemd sees the script exit and kills the Tomcat it just started. Use one of these two pairs and nothing else:# option 1: background script, tell systemd it forks Type=forking ExecStart=/opt/tomcat/bin/startup.sh PIDFile=/opt/tomcat/temp/tomcat.pid Environment=CATALINA_PID=/opt/tomcat/temp/tomcat.pid # option 2: foreground, simpler and preferred Type=simple ExecStart=/opt/tomcat/bin/catalina.sh run
Run
sudo systemctl daemon-reloadafter editing. - JAVA_HOME is not set in the unit. systemd does not read your shell profile, so a
JAVA_HOMEexported in.bashrcdoes not exist for the service. The journal saysNeither the JAVA_HOME nor the JRE_HOME environment variable is defined. Set it in the unit withEnvironment=JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64, using the path fromreadlink -f $(which java)minus the trailing/bin/java. - The service user cannot write logs. Tomcat writes
catalina.outand its work and temp directories as the user in the unit’sUser=line. Files extracted as root leave it with nothing writable:sudo chown -R tomcat:tomcat /opt/tomcat/logs /opt/tomcat/temp /opt/tomcat/work /opt/tomcat/webapps
When the unit is fine but Tomcat still dies
Once the status shows 1/FAILURE rather than 203, Tomcat did start and its own log has the reason: sudo tail -60 /opt/tomcat/logs/catalina.out.
- Port 8080 or 8005 already bound. 8005 is the shutdown port and is forgotten surprisingly often. A second Tomcat, or Jenkins, which also defaults to 8080, is the usual culprit:
sudo ss -tlnp | grep -E ':(8080|8005|8443) '. - The heap does not fit. An
-XmxinCATALINA_OPTSlarger than the machine can give stops the JVM before Tomcat loads. Size it against real memory with the JVM heap calculator. - A broken server.xml. The log names the line. A connector block edited for SSL is the usual source.
How to stop it happening again
Use Type=simple with catalina.sh run. It removes the PID file, the forking rules and an entire class of the failures above, and systemd tracks the real JVM process directly. Put every environment variable Tomcat needs in the unit, never in a shell profile.
Related
- JVM heap size calculator: correct Xms and Xmx for CATALINA_OPTS.
- systemd service unit generator: build the unit with the right Type, User and Environment lines.
- Job for elasticsearch.service failed: another JVM service where heap sizing is the common cause.
Tomcat starts with startup.sh but not with systemctl
Two causes cover almost every case. Either the unit is Type=simple pointing at startup.sh, so systemd kills Tomcat when the script exits, or JAVA_HOME exists only in your shell. Running by hand inherits your environment; systemd does not.
What does status=203/EXEC mean?
systemd could not execute the command in ExecStart at all: the file does not exist, is not executable, or has a broken interpreter line. Tomcat never ran, so its logs are empty. Check the exact path with ls -l.
Leave a Reply