A service won't start

Status, then journal, then the exit code

Updated 2026-09-12

Two things happen when a service will not come up, and they look nothing alike. Either systemctl start refuses in front of you:

Job for tips-worker.service failed because the control process exited with error code.
See "systemctl status tips-worker.service" and "journalctl -xeu tips-worker.service" for details.

or it stays silent, returns zero, and leaves the service dead.

Why a silent start still leaves the service dead

The default Type=simple tells systemd that the service is up as soon as it has been forked. The program has not run yet at that point, so a missing binary, a bad argument or an immediate crash all happen after systemctl start has already reported success.

systemctl start tips-api.service
echo "systemctl start exit status: $?"
sleep 1
systemctl is-active tips-api.service
systemctl start exit status: 0
failed

The sleep is doing real work there. systemd reaps the process and records the failure asynchronously, so is-active asked immediately after start can still answer activating.

Units declaring Type=notify, Type=oneshot or Type=forking do report the failure to systemctl start, because in each of those systemd has agreed to wait for something before calling the job done. That is the whole difference between the two openings above, and it does not tell you which service is more broken.

Read the status block first

systemctl start tips-api.service
sleep 1
systemctl status tips-api.service --no-pager
× tips-api.service - Tips API
     Loaded: loaded (/etc/systemd/system/tips-api.service; static)
     Active: failed (Result: exit-code) since Sat 2026-09-12 19:35:42 UTC; 1s ago
   Duration: 10ms
 Invocation: b439443eb55c43abbf7b62b65b8b4867
    Process: 12760 ExecStart=/usr/local/bin/tips-api (code=exited, status=203/EXEC)
   Main PID: 12760 (code=exited, status=203/EXEC)
   Mem peak: 1.5M
        CPU: 5ms

Sep 12 19:35:42 deb1 systemd[1]: Started tips-api.service - Tips API.
Sep 12 19:35:42 deb1 (tips-api)[12760]: tips-api.service: Unable to locate executable '/usr/local/bin/tips-api': No such file or directory
Sep 12 19:35:42 deb1 (tips-api)[12760]: tips-api.service: Failed at step EXEC spawning /usr/local/bin/tips-api: No such file or directory
Sep 12 19:35:42 deb1 systemd[1]: tips-api.service: Main process exited, code=exited, status=203/EXEC
Sep 12 19:35:42 deb1 systemd[1]: tips-api.service: Failed with result 'exit-code'.

Three lines carry the diagnosis. Loaded: says whether systemd found a unit file and what it thinks of it. Active: says the current state and, in brackets, why it left the last one. Process: gives the exit code, which decides which of the branches below you are in:

Code What refused Where to look
203/EXEC The kernel could not run the program The path in ExecStart=, and its execute bit
200/CHDIR The service could not enter WorkingDirectory= Permissions along that path
1/FAILURE and other small numbers The program ran and chose to exit The journal, for what it printed first
226/NAMESPACE, 217/USER, 208/STDIN A sandboxing or identity directive The directive named in the code

The last eight or so lines of the journal come free at the bottom of the status block, which is usually enough. journalctl is how you get the rest.

The unit systemd could not find

systemctl start tips-typo.service
systemctl status tips-typo.service
Failed to start tips-typo.service: Unit tips-typo.service not found.
Unit tips-typo.service could not be found.

Either the name is wrong or the package that ships the unit is not installed. Tab completion after systemctl start lists the real names, and systemctl list-unit-files 'tips-*' narrows a guess. A unit that lives in a user session rather than the system one answers here too, so it wants systemctl --user.

The unit that is masked

systemctl start tips-legacy.service
systemctl status tips-legacy.service --no-pager | head -2
Failed to start tips-legacy.service: Unit tips-legacy.service is masked.
○ tips-legacy.service
     Loaded: masked (Reason: Unit tips-legacy.service is masked.)

Masking symlinks the unit to /dev/null, which is stronger than disabling it: it cannot be started at all, not even by another unit that depends on it. systemctl unmask tips-legacy.service reverses it.

Somebody masked it deliberately, often a package's maintainer script or the image a container was built from rather than a person, so find out why before undoing it. systemctl list-unit-files --state=masked shows what else on the machine is in the same state.

The program that ran and exited

systemctl start tips-worker.service
Job for tips-worker.service failed because the control process exited with error code.
See "systemctl status tips-worker.service" and "journalctl -xeu tips-worker.service" for details.

status=1/FAILURE means the program started, decided it could not continue and said so. Whatever it printed on the way out went to the journal. -o cat strips the timestamps and unit prefixes, so the program's own words are all that is left:

systemctl start tips-worker.service 2>/dev/null
sleep 1
journalctl -u tips-worker.service --no-pager -n 4 -o cat
tips-worker: cannot open /etc/tips/worker.conf: No such file or directory
tips-worker.service: Main process exited, code=exited, status=1/FAILURE
tips-worker.service: Failed with result 'exit-code'.
Failed to start tips-worker.service - Tips worker.

The first line is the answer and the other three are systemd narrating. A service that fails on startup usually names its own reason like this, so read upward from the exit code until you reach a line systemd did not write.

journalctl -u tips-worker.service -b gives everything since boot when the interesting line has already scrolled past -n 4, and -f follows a unit you are restarting in another terminal.

The directory the service could not enter

systemctl start tips-report.service 2>/dev/null
sleep 1
journalctl -u tips-report.service --no-pager -n 4 -o cat
tips-report.service: Failed at step CHDIR spawning /usr/local/bin/tips-report: Permission denied
tips-report.service: Main process exited, code=exited, status=200/CHDIR
tips-report.service: Failed with result 'exit-code'.
Failed to start tips-report.service - Tips report builder.

200/CHDIR happens before the program runs at all. systemd has already dropped to the User= the unit names, and that user cannot enter WorkingDirectory=:

stat -c "%A %U %n" /srv/tips/reports
drwx------ root /srv/tips/reports

Mode 700 owned by root, against a service running as tipssvc. Every directory along the path needs x for that user, not just the last one, which is the case file permissions covers in full and Permission denied has the tools for, including how to ask the question as the service's own account rather than as yourself. Failed at step is the phrase to search for generally: systemd names the step it was on, so NAMESPACE points at ProtectSystem= or PrivateTmp=, and USER points at a User= that does not exist.

The port that was already taken

systemctl start tips-listener.service 2>/dev/null
sleep 1
journalctl -u tips-listener.service --no-pager -n 4 -o cat
tips-serve: cannot bind 127.0.0.1:9101: Address already in use
tips-listener.service: Main process exited, code=exited, status=1/FAILURE
tips-listener.service: Failed with result 'exit-code'.
Failed to start tips-listener.service - Tips listener.

This one reads as 1/FAILURE like any other refusal to continue, and the program's own line is what distinguishes it. ss names the holder:

ss -ltnp "sport = :9101"
State  Recv-Q Send-Q Local Address:Port Peer Address:PortProcess                               
LISTEN 0      5          127.0.0.1:9101      0.0.0.0:*    users:(("tips-serve",pid=16700,fd=3))

Often the holder is an older copy of the same service that systemd has lost track of, started by hand or left behind by a unit that was edited while running. Where the port is held by something you cannot identify, kill whatever is using a port is the fuller procedure.

The service that is running and not answering

systemctl start tips-quiet.service
systemctl is-active tips-quiet.service
ss -ltn "sport = :9102"
echo "nothing is listening on 9102"
active
State Recv-Q Send-Q Local Address:Port Peer Address:Port
nothing is listening on 9102

systemd is telling the truth: the process it started is alive. active is a claim about a process, never about whether that process is doing its job, so a daemon that read the wrong config and never bound a socket, or bound 127.0.0.1 when the client is on another host, is active (running) throughout.

Check the socket rather than the unit whenever the complaint is that something cannot connect. ss -ltnp with no filter lists every listener on the machine, which answers "is it bound to the address I think it is" in one line.

Reading the unit file itself

systemctl cat prints the unit as systemd loaded it, drop-ins and all, which is not always the file you were editing:

systemctl cat tips-api.service
# /etc/systemd/system/tips-api.service
[Unit]
Description=Tips API

[Service]
ExecStart=/usr/local/bin/tips-api

A mistyped directive is not an error to systemd. It is ignored, so the unit runs with the default you were trying to override, which is a much quieter failure than a syntax error would be:

printf "ExecStrat=/usr/local/bin/tips-api\n" >> /etc/systemd/system/tips-api.service
systemd-analyze verify /etc/systemd/system/tips-api.service
/etc/systemd/system/tips-api.service:6: Unknown key 'ExecStrat' in section [Service], ignoring.
tips-api.service: Command /usr/local/bin/tips-api is not executable: No such file or directory

systemd-analyze verify catches both at once: the key nobody will ever act on, and the program that is not there. Worth running on any unit you have hand-edited, before wondering why the setting had no effect.

The daemon-reload answer, and where it stopped applying

The standard reply to a unit change that had no effect is that you forgot systemctl daemon-reload. On Debian 13 that is out of date: systemd 257 notices a changed unit file by itself, and acts on the new contents without being asked.

sed -i "s|^ExecStart=.*|ExecStart=/usr/bin/tips-api|" /etc/systemd/system/tips-api.service
systemctl show -p ExecStart --value tips-api.service | grep -o "path=[^ ]*"
path=/usr/bin/tips-api

No reload was run there, and a unit file created from scratch behaves the same way. Running daemon-reload is still harmless and still correct on Debian 12 and earlier, so keep it in a provisioning script that has to work on both. What it will not do is restart anything: a running service keeps the settings it started with until systemctl restart gives it the new ones.

Clearing the failed state

A failed unit stays failed in the list until it starts successfully or is reset, which is what makes the failed list worth reading at all:

systemctl start tips-api.service tips-worker.service 2>/dev/null
sleep 1
systemctl list-units --failed 'tips-*' --no-pager --no-legend
● tips-api.service    loaded failed failed Tips API
● tips-worker.service loaded failed failed Tips worker

Drop the pattern and you get every failed unit on the machine, which is the first command to run when something is misbehaving and nobody has pinned down what. Keep the pattern when you already know which service you are chasing and would rather not read past a getty that has been failing since boot.

systemctl reset-failed clears an entry once you have dealt with it:

systemctl start tips-worker.service 2>/dev/null
sleep 1
systemctl is-failed tips-worker.service
systemctl reset-failed tips-worker.service
systemctl is-failed tips-worker.service
failed
inactive

Resetting clears the start-rate counter as well as the failed state. A unit carrying Restart=on-failure restarts itself until it trips StartLimitBurst=, after which systemd stops trying and refuses for a reason that has nothing to do with the original fault. A service that has been failing all morning is worth resetting before the next attempt, so that what you see is the error you are trying to fix.