Start, Stop, and Environment

Introduction

Installing Tomcat is only half the job—you need reliable ways to start, watch, stop, and restart the JVM process, with the right environment variables set every time. This chapter covers startup / shutdown, foreground catalina run, JVM options via CATALINA_OPTS, Linux systemd units, Windows service basics, and how to fix port conflicts when something else already listens on 8080.

Prerequisites

How Tomcat Starts (Mental Model)

Code explanation:

  • Tomcat is a Java main class launched by shell/batch scripts—you are not running a separate native binary like Nginx
  • conf/server.xml defines HTTP port 8080 and shutdown port 8005 by default
  • Failures almost always appear in logs/catalina.*.log or on the console when using run

Basic Start and Stop (Linux / macOS)

Assume:

bash
export CATALINA_HOME=/opt/tomcat
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64   # adjust path

Background start

bash
$CATALINA_HOME/bin/startup.sh
curl -I http://127.0.0.1:8080/

Code explanation:

  • startup.sh calls catalina.sh start—Tomcat detaches and logs to logs/
  • On macOS/Linux you may see catalina.out in logs/ depending on version and redirect settings

Graceful stop

bash
$CATALINA_HOME/bin/shutdown.sh

Code explanation:

  • shutdown.sh opens the shutdown port (default 8005) and sends the string SHUTDOWN
  • Tomcat stops accepting new requests and winds down threads—prefer this over kill -9

Verify it stopped:

bash
curl -I http://127.0.0.1:8080/    # should fail to connect
ss -tlnp | grep 8080              # Linux: no listener

Foreground Run (Best for Learning)

bash
cd $CATALINA_HOME
./bin/catalina.sh run

Code explanation:

  • run keeps Tomcat in the foreground—logs stream to your terminal
  • Ctrl+C sends a shutdown signal—similar to stopping a Spring Boot app from IDEA
  • Use this when deploy fails and you want immediate stack traces

Stop from another terminal while run is active:

bash
$CATALINA_HOME/bin/shutdown.sh

Tip

Develop with run, Deploy with startup

Local debugging: catalina.sh run. Long-running laptop session or after you trust config: startup.sh.

Windows Start and Stop

CMD or PowerShell:

bat
set CATALINA_HOME=C:\apache-tomcat-10.1.34
set JAVA_HOME=C:\Program Files\Eclipse Adoptium\jdk-21.0.x-hotspot
cd %CATALINA_HOME%\bin
startup.bat

Foreground:

bat
catalina.bat run

Stop:

bat
shutdown.bat

Code explanation:

  • startup.bat often opens a second console window for Tomcat logs
  • Set JAVA_HOME and CATALINA_HOME in System Environment Variables so every new CMD window inherits them

Windows service (overview)

Tomcat ships service.bat in bin/ to register a Windows service (requires Administrator):

bat
cd %CATALINA_HOME%\bin
service.bat install Tomcat10

Then use Services (services.msc) or:

bat
sc start Tomcat10
sc stop Tomcat10

Uninstall:

bat
service.bat uninstall Tomcat10

Details vary by Tomcat minor version—run service.bat with no args for usage text on your install.

Environment Variables in Depth

VariableScopeTypical use
JAVA_HOMERequiredJDK root—scripts locate java
JRE_HOMEAlternativeRare on dev machines; JDK is preferred
CATALINA_HOMEStrongly recommendedTomcat product directory
CATALINA_BASEOptionalInstance dir; defaults to CATALINA_HOME
CATALINA_OPTSTomcat JVM onlyHeap, GC, debug flags for this Tomcat process
JAVA_OPTSAll Java in scriptShared flags—can affect child tools; prefer CATALINA_OPTS for Tomcat tuning

Example: heap size and encoding

Linux/macOS—export before start:

bash
export CATALINA_OPTS="-Xms256m -Xmx512m -Dfile.encoding=UTF-8"
$CATALINA_HOME/bin/startup.sh

Windows CMD:

bat
set CATALINA_OPTS=-Xms256m -Xmx512m -Dfile.encoding=UTF-8
startup.bat

Code explanation:

  • -Xms / -Xmx — initial and maximum heap; start small when learning
  • -Dfile.encoding=UTF-8 — reduces garbled logs or request handling on some locales
  • Do not set enormous heaps on a laptop—512m–1g is enough for tutorial WARs

Persisting variables

PlatformWhere
Linux (single user)~/.bashrc, ~/.profile
Linux (system service)systemd Environment= or EnvironmentFile=
macOS~/.zshrc
WindowsSystem Properties → Environment Variables

After editing shell profiles, open a new terminal before starting Tomcat.

catalina.sh and your exports

Scripts honor variables already in the environment. You can also create bin/setenv.sh (Linux/macOS) or bin/setenv.bat (Windows)—Tomcat sources them automatically if present:

bash
# $CATALINA_HOME/bin/setenv.sh
export CATALINA_OPTS="-Xms256m -Xmx512m -Dfile.encoding=UTF-8"

Code explanation:

  • setenv.sh keeps JVM flags with the Tomcat install instead of scattered shell profiles
  • File must be executable on Unix: chmod +x bin/setenv.sh

Graceful Shutdown vs Force Kill

MethodCommandWhen
Gracefulshutdown.sh / shutdown.batDefault—always try first
SIGTERMkill <pid>If shutdown port broken but process healthy
SIGKILLkill -9 <pid>Last resort—may corrupt in-flight work

Find Tomcat PID on Linux:

bash
ss -tlnp | grep 8080
pgrep -f catalina
ps aux | grep '[o]rg.apache.catalina'

Windows:

bat
netstat -ano | findstr :8080
tasklist | findstr java

Warning

Avoid kill -9 in Production

Forced kill skips servlet destroy() hooks and may leave lock files or half-written sessions. Fix shutdown.sh failures by checking port 8005 and logs/ first.

Shutdown port (8005)

In conf/server.xml (default):

xml
<Server port="8005" shutdown="SHUTDOWN">

Code explanation:

  • Only localhost should reach this port—firewall it on shared servers
  • If 8005 is blocked or changed without updating scripts, shutdown.sh hangs
  • Changing the shutdown string from SHUTDOWN is a hardening option for advanced setups

Port 8080 Already in Use

Symptom in logs:

text
java.net.BindException: Address already in use

Find the conflict

Linux:

bash
ss -tlnp | grep 8080
sudo lsof -i :8080

Windows:

bat
netstat -ano | findstr :8080

Common culprits: another Tomcat, Spring Boot on 8080, IDE-run app, Docker container.

Option A: stop the other process

Stop Spring Boot in IDEA, run shutdown.sh on the other Tomcat, or docker stop ….

Option B: change Tomcat HTTP port

Edit conf/server.xml (backup first):

xml
<Connector port="8081" protocol="HTTP/1.1"
           connectionTimeout="20000"
           redirectPort="8443" />

Restart Tomcat, then:

bash
curl -I http://127.0.0.1:8081/

Full Connector discussion: server.xml and Connectors.

Linux systemd Service (Official tar.gz Install)

Production servers should not depend on manual startup.sh after SSH logout. Create a unit file.

/etc/systemd/system/tomcat.service:

Enable and control:

bash
sudo systemctl daemon-reload
sudo systemctl enable tomcat
sudo systemctl start tomcat
sudo systemctl status tomcat
sudo journalctl -u tomcat -f

Code explanation:

  • Type=simple — systemd considers the service started when catalina.sh run execs; logs go to journal and logs/
  • User=tomcat — run as non-root; ensure /opt/tomcat ownership matches
  • ExecStop — calls graceful shutdown.sh

Option 2: Type=forking with startup.sh

Some teams use Type=forking because startup.sh backgrounds the JVM. That requires a correct PIDFile= and Tomcat version alignment—easy to misconfigure. Prefer Option 1 unless your runbook already standardizes on forking.

EnvironmentFile for secrets

ini
EnvironmentFile=/etc/tomcat/tomcat.env

/etc/tomcat/tomcat.env:

bash
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
CATALINA_HOME=/opt/tomcat
CATALINA_OPTS=-Xms256m -Xmx512m

Restrict permissions: chmod 640 /etc/tomcat/tomcat.env.

See also Linux developer workflow for Environment= patterns with Java apps.

OS Package Tomcat (systemctl)

If you installed tomcat10 via APT:

bash
sudo systemctl start tomcat10
sudo systemctl stop tomcat10
sudo systemctl restart tomcat10
sudo systemctl status tomcat10

Code explanation:

  • Service name and paths are distro-defined—not /opt/tomcat/bin/startup.sh
  • Config may live under /etc/tomcat10/; logs under /var/log/tomcat10/
  • Mixing package Tomcat with manual startup.sh on the same port causes conflicts—pick one method

Restart Workflow After Config Change

Many conf/ changes require a restart (not hot-reload):

bash
$CATALINA_HOME/bin/shutdown.sh
# wait until port 8080 is free
$CATALINA_HOME/bin/startup.sh

Or with systemd:

bash
sudo systemctl restart tomcat

Code explanation:

  • server.xml Connector changes need restart
  • Some context.xml / web.xml changes trigger redeploy depending on settings—do not rely on that in production; restart during maintenance windows

Health Check Habit

After every start:

bash
./bin/catalina.sh version
curl -I http://127.0.0.1:8080/
tail -20 logs/catalina.$(date +%Y-%m-%d).log
CheckPass criteria
Version scriptShows Tomcat 10.1.x and expected Java
HTTPHTTP/1.1 200 or redirect from ROOT app
Log tailNo BindException, no OutOfMemoryError

Common Problems

SymptomLikely causeFix
Neither JAVA_HOME nor JRE_HOMEEnv not set in this shell/serviceSet JAVA_HOME; restart terminal or systemd
Shutdown hangsWrong shutdown port; hung threadsCheck 8005; use catalina.sh run to see thread dump; last resort kill then inspect logs
Tomcat starts, 404 on /ROOT app removedDeploy app or open /docs if samples remain
systemd start fails immediatelyWrong paths, permissionjournalctl -u tomcat -n 50; verify User= owns logs/ and webapps/
Two Tomcats after rebootManual startup + systemd both enabledDisable one; only one process per port

Post-Chapter Checklist

  • Start with startup.sh and stop with shutdown.sh
  • Run once with catalina.sh run and read console output
  • Set JAVA_HOME, CATALINA_HOME, and optional CATALINA_OPTS
  • Know how to find what uses port 8080
  • (Linux) Created or understood a systemd unit for persistent Tomcat

What Comes Next

ChapterTopic
05 — server.xml and ConnectorsPorts, Engine, Host, Connector tuning
07 — Deploying WARPut applications in webapps/
20 — TroubleshootingExtended diagnostic playbook

FAQ

startup.sh vs catalina.sh start?

They are the same path—startup.sh is a thin wrapper that invokes catalina.sh start.

Do I need to export CATALINA_HOME every time?

If you use setenv.sh and full paths in systemd, no. Interactive shells still benefit from CATALINA_HOME in your profile.

Can Tomcat reload without full restart?

Some app redeploys happen with autoDeploy, but Connector and JVM flag changes need a full restart. When in doubt, restart.

Tomcat vs Spring Boot on the same machine?

Both default to 8080—run only one listener, or change one port. Stop Boot in IDEA before starting standalone Tomcat.

Why run as a non-root tomcat user?

Principle of least privilege—compromised web apps should not own the whole server. See Tomcat Security Basics.

WSL2: startup from Windows or WSL?

Start Tomcat inside WSL with Linux scripts; browse at http://127.0.0.1:8080/ from Windows browsers.

journalctl empty but Tomcat works?

Logs may only go to $CATALINA_HOME/logs/ depending on unit setup. Check both journalctl -u tomcat and logs/catalina*.log.