Job for tomcat.service failed because the control process exited with error code

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

  1. Wrong path or missing execute bit (203/EXEC). The unit points at /opt/tomcat but 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/tomcat to the versioned directory, and restore the bit with sudo chmod +x /opt/tomcat/bin/*.sh.
  2. Type does not match the start script. startup.sh launches Tomcat in the background and exits. With Type=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-reload after editing.

  3. JAVA_HOME is not set in the unit. systemd does not read your shell profile, so a JAVA_HOME exported in .bashrc does not exist for the service. The journal says Neither the JAVA_HOME nor the JRE_HOME environment variable is defined. Set it in the unit with Environment=JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64, using the path from readlink -f $(which java) minus the trailing /bin/java.
  4. The service user cannot write logs. Tomcat writes catalina.out and its work and temp directories as the user in the unit’s User= 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.

  1. 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) '.
  2. The heap does not fit. An -Xmx in CATALINA_OPTS larger than the machine can give stops the JVM before Tomcat loads. Size it against real memory with the JVM heap calculator.
  3. 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

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.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *