Debug systemd Units that Won't Start
Learn a repeatable workflow to debug systemd services that won't start: status output, journalctl, systemd-analyze verify, and safe override.conf patches.
Before you start
- ▸Root or sudo access on the target system
- ▸The service unit file already exists (installed or manually created)
- ▸Basic familiarity with a terminal text editor (nano, vim, or $EDITOR)
- ▸systemd version 245 or later (ships with Ubuntu 20.04+, Fedora 32+, RHEL 9+)
A service that refuses to start is one of the most common systemd headaches. The failure message in systemctl status is often terse, and the real cause is buried in the journal or in a unit file syntax error you typed at 2 AM. This guide walks through a repeatable diagnostic workflow: read the status, dig into the journal, verify the unit file statically, and apply targeted overrides without touching the vendor unit.
Step 1: Read the Status Output Properly
Most people glance at systemctl status and miss half of what it says. Run it with enough lines to see the last log burst:
systemctl status myapp.service -n 50
The output sections that matter most are:
- Loaded: — shows the unit file path and whether it is enabled. If it says
vendor preset: disabled, the service won't start at boot unless explicitly enabled. - Active: —
failed,activating (auto-restart), orinactive (dead)each point to different root causes. - Main PID / code=exited, status= — the exit code. A non-zero exit code (e.g.,
status=1/FAILURE,status=203/EXEC) narrows the problem immediately. Code 203 specifically means the binary was not found or not executable. - The log tail — the last lines printed by the service itself before it died.
If the active state is failed, note the result field. result=exit-code means the process started and then exited badly. result=start-limit-hit means systemd gave up after repeated restart attempts — you must reset it before the service can try again:
systemctl reset-failed myapp.service
Step 2: Mine the Journal
journalctl -u gives you the full log for a single unit across all its invocations, not just the last burst shown in status.
Show all logs for the unit
journalctl -u myapp.service
Follow live output while you attempt a start
journalctl -u myapp.service -f &
systemctl start myapp.service
Limit to the current boot
journalctl -u myapp.service -b
Show kernel + service messages together (useful for hardware or cgroup issues)
journalctl -b -p err..alert
Look for lines tagged with the service name or its PID. Common tells:
- "No such file or directory" on the
ExecStartbinary path — the path in the unit is wrong or the package isn't installed. - "Permission denied" — a missing capability, SELinux/AppArmor denial, or wrong file ownership.
- "Failed to set up mount namespace" — a
PrivateTmporProtectSystemsandboxing option conflicting with the app's runtime paths. - D-Bus or socket activation errors — the companion
.socketor.targetunit is not running.
If journald itself has lost logs (persistent logging not configured), enable it once and retry:
mkdir -p /var/log/journal
systemd-tmpfiles --create --prefix /var/log/journal
systemctl restart systemd-journald
Step 3: Validate the Unit File Statically
systemd-analyze verify parses unit files and reports syntax errors, unknown options, missing dependencies, and dangerous combinations — without actually running anything. This is especially useful after you edit a unit.
systemd-analyze verify myapp.service
For units not yet installed system-wide, pass the file path directly:
systemd-analyze verify /etc/systemd/system/myapp.service
Sample output you might see (will vary):
/etc/systemd/system/myapp.service:12: Unknown key name 'ExecReload' in section 'Service', ignoring.
myapp.service: Unit configured to use KillMode=none. This is unsafe.
Warnings don't prevent the unit from loading, but errors do. Fix every error line before moving on. After any edit to a unit file, reload the daemon so systemd picks up the changes:
systemctl daemon-reload
Also useful: check what systemd actually resolved the unit to after all includes and dropins are merged:
systemctl cat myapp.service
Step 4: Apply Targeted Fixes with Override.conf
Editing the vendor-installed unit file in /lib/systemd/system/ is a bad idea — package upgrades overwrite it. Instead, use a dropin override. systemctl edit handles this automatically: it creates /etc/systemd/system/myapp.service.d/override.conf and opens your editor.
systemctl edit myapp.service
To override the full unit rather than just patch it (useful when the vendor unit is severely broken):
systemctl edit --full myapp.service
Common override patterns:
Fix an environment variable or binary path
[Service]
Environment="NODE_ENV=production"
ExecStart=
ExecStart=/usr/local/bin/myapp --config /etc/myapp/config.yaml
Note the blank ExecStart= line — it clears the original value before setting the new one. Without it, systemd appends to a list, which is usually not what you want.
Relax a sandboxing option that blocks a path
[Service]
ProtectSystem=false
Increase the start timeout for a slow service
[Service]
TimeoutStartSec=120
Add a missing capability
[Service]
AmbientCapabilities=CAP_NET_BIND_SERVICE
After saving, verify and reload:
systemd-analyze verify myapp.service
systemctl daemon-reload
systemctl restart myapp.service
Step 5: Verify the Service Is Running Correctly
systemctl is-active myapp.service
The command exits 0 and prints active on success. Pair it with a quick status check:
systemctl status myapp.service
For services that expose a port, confirm the socket is listening:
ss -tlnp | grep myapp
If you fixed a start-limit situation, also confirm the failure counter is clear:
systemctl show myapp.service | grep NRestarts
Troubleshooting Specific Error Patterns
SELinux / AppArmor denials
On Fedora, RHEL, Rocky Linux, and derivatives with SELinux enforcing, check the audit log:
ausearch -m avc -ts recent
On Ubuntu/Debian with AppArmor:
journalctl -b | grep apparmor
Use audit2allow (SELinux) or edit the AppArmor profile rather than disabling enforcement entirely.
Service starts manually but not at boot
The unit is probably not enabled, or it depends on a target that runs too early:
systemctl enable myapp.service
systemctl list-dependencies myapp.service
If the service needs the network, verify it declares After=network-online.target and Wants=network-online.target in its [Unit] section.
User services (--user scope)
Services running under a user slice use a separate journal and separate unit directories. Append --user to every command:
systemctl --user status myapp.service
journalctl --user -u myapp.service
Binary path issues on Arch Linux
Arch packages sometimes install to /usr/bin while the unit file references /usr/sbin. Confirm with:
which myapp
type myappFrequently asked questions
- Why does my service start fine with `systemctl start` but fail at boot?
- The unit probably lacks `After=network-online.target` or another dependency it needs at boot time. Check `systemctl list-dependencies myapp.service` and add the missing ordering directives in an override.conf.
- What does 'start-limit-hit' mean and how do I clear it?
- Systemd hit the restart burst limit defined by StartLimitIntervalSec and StartLimitBurst. Run `systemctl reset-failed myapp.service` to clear the counter, then investigate the root cause before starting again.
- When I write an override.conf to change ExecStart, do I need to clear the original value first?
- Yes. Add a blank `ExecStart=` line before your new value. ExecStart is a list-type directive, so without clearing it, systemd runs both the original and your new binary.
- Is it safe to edit files in /lib/systemd/system/ directly?
- No. Package upgrades overwrite those files silently. Always use `systemctl edit` to create a dropin in /etc/systemd/system/, or `systemctl edit --full` to create a full override that takes precedence.
- How do I debug a user-level systemd service (not a system service)?
- Append `--user` to all commands: `systemctl --user status`, `journalctl --user -u`, and `systemctl --user edit`. User services live under ~/.config/systemd/user/ and have their own journal slice.
Related guides
Back Up Linux with Borg or restic
Set up encrypted, deduplicated backups with BorgBackup or restic: local and remote repos, retention pruning, restoring files, and systemd timer scheduling.
How to Check Disk Health with SMART
Learn to use smartctl to read SMART attributes, run drive self-tests, and identify early warning signs of HDD and SSD failure before data loss occurs.
Linux Server Disaster Recovery Checklist
A practical Linux server disaster recovery checklist: what to back up, RTO/RPO planning, immutable off-site copies, automated restore drills, and verification.
How to Fix a Broken GRUB Bootloader
Fix a broken GRUB bootloader by booting from live media, chrooting into your installed system, reinstalling GRUB, and regenerating grub.cfg — covers BIOS/MBR and UEFI/GPT.