A service won't come up. systemctl status shows a red "failed" and three lines that tell you almost nothing on their own. The usual forum advice is to raise StartLimitBurst until the error goes away. That only hides the problem: the unit keeps crashing, it just stops telling you about it. This article shows how to read the real cause of a systemd "start request repeated too quickly" failure on Debian 13 (trixie, systemd 257). It maps the exit status systemd reports to the setting that broke, then shows how to test a fix without guessing.
Tested on 2026-10-28 on a fresh Debian 13.7 VM (arm64, systemd 257.13-1~deb13u1): the exit codes, journal lines and start-limit behaviour below are real output from that test.
The error messages
A typical failure looks like this in systemctl status myapp.service and journalctl -xeu myapp.service:
× myapp.service - My App
Loaded: loaded (/etc/systemd/system/myapp.service; enabled; preset: enabled)
Active: failed (Result: exit-code) since Wed 2026-10-28 22:30:59 UTC; 1s ago
Duration: 44ms
Invocation: c3b3b5e22fd14ecab7cab96aa8098ce8
Process: 939 ExecStart=/usr/local/bin/myap (code=exited, status=203/EXEC)
myapp.service: Unable to locate executable '/usr/local/bin/myap': No such file or directory
myapp.service: Failed at step EXEC spawning /usr/local/bin/myap: No such file or directory
myapp.service: Main process exited, code=exited, status=203/EXEC
myapp.service: Failed with result 'exit-code'.
myapp.service: Scheduled restart job, restart counter is at 5.
myapp.service: Start request repeated too quickly.
myapp.service: Failed with result 'exit-code'.
Failed to start myapp.service - My App.
A unit that depends on it fails with a different message:
Dependency failed for myapp-worker.service - My App Worker.
myapp-worker.service: Job myapp-worker.service/start failed with result 'dependency'.
"Start request repeated too quickly" is never the cause. On Debian 13 the unit keeps the result of its last real failure, so systemctl status shows Result: exit-code, not start-limit-hit. systemd's $SERVICE_RESULT documentation does list start-limit-hit, and many forum posts show it, but in my test on systemd 257.13 it never appeared. A manual systemctl start while the limit is active is also misleading: it fails with "the control process exited with error code", although nothing was started. The useful information is the line before the limit message: exit-code plus a status= value. Scroll up in the journal to the first failure, not the last one.
Quick fix for the most common cause
On self-written units the top cause is status=203/EXEC: systemd could not execute the binary at all. Check the path and the execute bit, fix the unit, then clear the failed state:
systemctl cat myapp.service
ls -l /usr/local/bin/myapp
head -n1 /usr/local/bin/myapp
systemd-analyze verify /etc/systemd/system/myapp.service
The first argument of ExecStart= must be an absolute path or a plain file name that systemd resolves via its compiled-in search path (/usr/local/bin, /usr/bin and their sbin variants). Shell syntax like pipes, && or redirections doesn't work in ExecStart= without an explicit /bin/sh -c. If the file is a script, its shebang interpreter must exist too.
After the fix, reload and start. This restarts the service, so do it when a restart is acceptable:
systemctl daemon-reload
systemctl reset-failed myapp.service
systemctl start myapp.service
According to systemctl(1), reset-failed clears the failed state and also resets the start rate limit and restart counters. Without it, a unit that hit the limit refuses new starts until the interval has passed.
Exit status to cause table
The numbers from 200 upward are systemd's own codes from systemd.exec(5). They mean systemd failed while setting up the process, before your program ran any code. Code 1 is your program's own generic failure.
| Status | systemd meaning | Typical cause on a real server |
|---|---|---|
203/EXEC | The execve() call failed | Wrong ExecStart= path, missing execute bit, broken shebang, or the binary sits in a path a sandbox option hides (e.g. /home with ProtectHome=yes) |
200/CHDIR | Changing to the working directory failed | WorkingDirectory= doesn't exist or isn't reachable for User= |
217/USER | Failed to determine or change user credentials | User= names an account that doesn't exist (typo, or the package that creates it isn't installed) |
216/GROUP | Failed to determine or change group credentials | Group= or SupplementaryGroups= names a group that doesn't exist |
226/NAMESPACE | Failed to set up mount, UTS or IPC namespacing | ReadWritePaths=, ReadOnlyPaths= or BindPaths= point to a path that doesn't exist |
233/RUNTIME_DIRECTORY | Setting up the runtime directory failed | Problem creating the RuntimeDirectory= below /run |
238/STATE_DIRECTORY | Setting up the state directory failed | Problem creating StateDirectory= below /var/lib |
1/FAILURE | Generic failure from the program | The app itself gave up: bad config, port in use, or it can't write a file because ProtectSystem=strict made the path read-only |
The full list (including 209/STDOUT, 228/SECCOMP and 243/CREDENTIALS) is in the "Process Exit Codes" section of systemd.exec(5).
Diagnosing each cause
Read the journal lines before the failure
Show only this unit's messages since the last boot with the catalog explanations. The lines just above "Main process exited" name the step and the errno:
journalctl -b -x -u myapp.service --no-pager -o short-precise
systemctl show -p Result -p ExecMainStatus -p NRestarts myapp.service
The systemd 257 source has messages like these. They map straight to the table above:
myapp.service: Unable to locate executable '/usr/local/bin/myap': No such file or directory
myapp.service: Changing to the requested working directory failed: No such file or directory
myapp.service: Failed to determine user credentials: No such process
myapp.service: Failed to set up mount namespacing: /var/lib/myapp: No such file or directory
Check what systemd actually loaded
A drop-in in /etc/systemd/system/myapp.service.d/ can override ExecStart=, User= or sandbox options without you noticing. systemctl cat prints the main file and every drop-in with its path. systemd-analyze verify reports unknown directives, missing dependencies and ExecStart= commands that are missing or not executable:
systemctl cat myapp.service
systemd-analyze verify myapp.service
getent passwd myapp
systemctl show -p WorkingDirectory -p User -p ProtectSystem -p ReadWritePaths myapp.service
If getent prints nothing, you have found your 217/USER. Keep in mind that systemctl cat shows files on disk. If you edited a file by hand and forgot daemon-reload, systemd may still run the old version.
Replay the start with systemd-run
To test one theory at a time, run the same binary as a transient unit with only the properties you suspect. Type=exec makes systemd report an exec failure as a start failure instead of treating the fork as success (systemd-run(1) recommends this). --wait prints the exit status at the end, and --collect unloads the transient unit even if it failed:
systemd-run --wait --collect -p Type=exec -p User=myapp /usr/local/bin/myapp --check-config
systemd-run --wait --collect -p Type=exec -p User=myapp -p ProtectSystem=strict /usr/local/bin/myapp --check-config
systemd-run --wait --collect -p Type=exec -p User=myapp -p ProtectSystem=strict -p ReadWritePaths=/var/lib/myapp /usr/local/bin/myapp --check-config
Use a config-check or dry-run flag if your program has one. Otherwise the replay starts a second copy of the service, which may fight the real one for ports or lock files. If the first command passes and the second one fails, ProtectSystem=strict is your problem. If you want to watch output interactively, swap --wait for --pty.
Sandbox failures after hardening
If you hardened a unit along the lines of sandboxing systemd services on Debian 13, expect two kinds of failure. The first is 226/NAMESPACE, which happens when a listed path is missing. systemd.exec(5) documents a - prefix in ReadWritePaths= that makes missing paths non-fatal, but in most cases it's better to create the directory or use StateDirectory=. The second is quieter: the namespace works, but ProtectSystem=strict makes everything except /dev, /proc and /sys read-only. The app then fails with 1/FAILURE and a "Read-only file system" message in its own log.
Relax one option at a time in a drop-in instead of deleting the whole hardening block. Restarting the unit interrupts the service:
systemctl edit myapp.service
[Service]
# Test only: revert once the missing path is known
ProtectSystem=full
systemctl reset-failed myapp.service
systemctl restart myapp.service
systemctl status myapp.service
systemctl edit reloads the configuration itself after you save. If the service starts with the relaxed option, put ProtectSystem=strict back and add the exact path the app needs to ReadWritePaths=.
Fixes for each cause
- 200/CHDIR: create the directory with the right owner, or use
WorkingDirectory=~to get the home directory ofUser=. That needs a real home:adduser --systemsets it to/nonexistent, soWorkingDirectory=~fails with 200/CHDIR. Create the account with--home /var/lib/myapp, or useStateDirectory=myapptogether withWorkingDirectory=/var/lib/myapp. A-prefix makes a missing directory non-fatal, but that only hides a deployment error. - Runtime files in /run: don't create
/run/myappby hand or inExecStartPre=./runis a tmpfs and is empty after reboot. WithRuntimeDirectory=myapp, systemd creates/run/myappon start, owned byUser=andGroup=, and removes it on stop. - 217/USER: create the account (
adduser --system --group myapp) or fix the typo. For a service that needs no fixed account,DynamicUser=yesis another option. - 226/NAMESPACE: create the paths listed in the unit, or replace hand-made paths under
/var/libwithStateDirectory=. - 1/FAILURE: this is your program speaking. Run its own config test and read its log. For PHP-FPM behind nginx, the nginx 502 Bad Gateway with PHP-FPM guide covers the socket and pool errors that typically show up here.
Masked units and failed dependencies
A masked unit is linked to /dev/null and cannot be started at all. systemctl status shows Loaded: masked. Check whether someone masked it on purpose before you undo it:
systemctl is-enabled myapp.service
ls -l /etc/systemd/system/myapp.service
systemctl unmask myapp.service
For "Dependency failed", the unit you started is fine. One of its Requires= dependencies, ordered with After=, failed to start, and systemd.unit(5) says the dependent unit is then not started. Find the broken one and diagnose that unit with the steps above:
systemctl --failed
systemctl list-dependencies myapp-worker.service
systemctl show -p Requires -p After myapp-worker.service
Start limits and Restart= loops
On Debian 13, systemd-system.conf(5) gives DefaultStartLimitIntervalSec=10s and DefaultStartLimitBurst=5. systemd.service(5) gives RestartSec= a default of 100ms. With Restart=on-failure, a unit that breaks during setup is started five times in well under a second and then hits the limit. Per systemd.unit(5), a unit with Restart= that reaches the start limit is not restarted any more. That's the safety net working as intended.
StartLimitIntervalSec= and StartLimitBurst= are documented as [Unit] options, while Restart= and RestartSec= belong in [Service]. Many old answers put all four in [Service]. Put them where the man page says and check the result with systemctl show:
[Unit]
StartLimitIntervalSec=300
StartLimitBurst=5
[Service]
Restart=on-failure
RestartSec=5s
RestartSteps=4
RestartMaxDelaySec=60s
systemctl daemon-reload
systemctl show -p StartLimitBurst -p StartLimitIntervalUSec -p RestartUSec myapp.service
The wrong section fails only halfway. With both options in [Service], systemd 257 logs Unknown key 'StartLimitIntervalSec' in section [Service], ignoring and keeps the 10-second default, but it still accepts StartLimitBurst= there. systemd-analyze verify prints the warning and still exits with 0, so read its output instead of trusting the exit code.
RestartSteps= and RestartMaxDelaySec= give exponential backoff from RestartSec= up to the maximum. They only work if both are set and RestartSec= isn't zero. Watch the math, though. If the delay between restarts is longer than the interval divided by the burst, the limit is never reached and a broken unit loops forever with a growing NRestarts. Restart settings are for programs that crash now and then, not for configuration errors. A 203, 217 or 226 won't fix itself on the next try.
Prevent it next time
- Run
systemd-analyze verifyon every new or changed unit beforedaemon-reload, ideally in your deployment pipeline. - Prefer
RuntimeDirectory=,StateDirectory=andLogsDirectory=to directories created by hand or by scripts. - Add sandbox options one at a time and restart after each, so a failure points to a single change.
- Alert on failed units (
systemctl --failedin your monitoring) and on a risingNRestarts, so silent restart loops get noticed. - Record the exact package version you tested against:
systemctl --version. At the time of writing, manpages.debian.org lists systemd 257.13-1~deb13u1 for trixie.
Takeaways
- Ignore "Start request repeated too quickly" and find the first
status=value in the journal. - Look up the code: 203 exec, 200 working directory, 217 user, 226 namespace paths, 1 the app itself.
- Check the loaded config with
systemctl catandsystemd-analyze verify. - Replay with
systemd-run -pand relax one sandbox option at a time in a drop-in. - Put
StartLimit*in[Unit], use backoff in[Service], and don't raise limits to hide a config error. - Clear the state with
systemctl reset-failedafter the real fix, then start again.
Sources
- systemd.exec(5) - Debian trixie manpages
- systemd.unit(5) - Debian trixie manpages
- systemd.service(5) - Debian trixie manpages
- systemd-system.conf(5) - Debian trixie manpages
- systemd-run(1) - Debian trixie manpages
- systemd-analyze(1) - Debian trixie manpages
- systemctl(1) - Debian trixie manpages
- systemd v257 source: src/core/exec-invoke.c
- systemd v257 source: src/core/job.c
- systemd v257 source: src/core/unit.c
Comments