A service won't start
A systemd service that refuses to start, crashes a couple of seconds after starting, or gets stuck in an endless restart loop is one of the most common VPS problems. The cause is almost always one of five things: a port already in use, a configuration error, wrong permissions, a dependency that isn't ready, or the service running out of memory — this article covers a generic diagnostic method using systemctl status and journalctl, then walks through each of the five causes with concrete examples.
What you'll need
- An mHost VPS running Ubuntu 22.04/24.04, Debian 11/12, or AlmaLinux/Rocky Linux 8/9.
- SSH access as
rootor a user withsudo— if you haven't connected to the server yet, start with "VNC console and first connection (SSH/RDP)". - The name of the service that won't start — for example
nginx,postgresql,myapp. To list every systemd service on the server:systemctl list-units --type=service --all.
Step 1. Check the status: systemctl status
systemctl status <service>Example output for a crashed service:
● myapp.service - My Application
Loaded: loaded (/etc/systemd/system/myapp.service; enabled; preset: enabled)
Active: failed (Result: exit-code) since Fri 2026-07-10 09:14:22 UTC; 38s ago
Process: 4021 ExecStart=/opt/myapp/venv/bin/python /opt/myapp/main.py (code=exited, status=1/FAILURE)
Main PID: 4021 (code=exited, status=1/FAILURE)Loaded also confirms systemd actually knows about this unit and where its file came from; a line like Unit myapp.service could not be found means a typo in the name, an uninstalled package, or a new unit file systemd hasn't picked up yet (fixed with systemctl daemon-reload).
The line that matters most is Active:
- `active (running)` — the service is up; if you're reading this article, this probably isn't what you're seeing.
- `activating (auto-restart)` — the service is crashing and systemd keeps automatically trying to restart it (the
Restart=setting in the unit). Check the log (Step 2) rather than waiting for the retries to give up. - `failed` — it either couldn't come up, or it crashed and isn't being restarted.
- `inactive (dead)` — simply stopped, nobody even tried to start it: maybe you just forgot to —
systemctl start <service>.
The Process/Main PID line with the code in parentheses decodes like this:
- `code=exited, status=N/...` (N ≠ 0) — the process started fine and then exited with an error on its own — look for the cause in the application's own output (Step 2).
- `code=exited, status=203/EXEC` — the application itself never even ran: systemd couldn't execute the file in
ExecStart— wrong path, the file isn't marked executable, or it's not accessible due to permissions. See "Wrong permissions" and "A missing dependency" below. - `code=killed, signal=KILL` — the process was killed with SIGKILL. Most often that's the kernel's OOM killer — see "Running out of memory (OOM)" below.
- `code=killed, signal=SEGV` (or
ABRT) — the process crashed on its own: looks like a bug in the application or an incompatible library version. - `Result: oom-kill` — on newer versions of systemd, this status is used specifically when the service's own cgroup got a kill notification from the kernel's OOM killer — essentially the same thing as
signal=KILLfrom memory pressure, but systemd names the cause explicitly.
Step 2. Read the log: journalctl
journalctl -u <service> -e-u filters by a specific unit; -e immediately jumps to the end of the output, to the most recent entries.
Useful variations on the command:
journalctl -xeu <service>— the same thing plus the-xflag: wherever possible, it adds an explanation to a system message from systemd's catalog. This is usually the exact commandsystemctlitself suggests when a service fails.journalctl -u <service> --no-pager -n 50— the last 50 lines without an interactive pager, handy when you need to copy the whole output.journalctl -u <service> -f— follow the log in real time, useful right after a restart (Step 4).journalctl -u <service> --since "10 min ago"— only entries from the last 10 minutes, if the unit's log is large.
Step 3. Find the cause among the most common ones
The error line from Step 2 almost always points straight at one of the five causes below.
A port is already in use
If the port a service is trying to listen on is already taken by another process, it can't bind to it and crashes immediately on startup — in the log this typically looks something like:
bind() to 0.0.0.0:80 failed (98: Address already in use)Find out who's actually holding the port:
ss -tlnp | grep :80-t— TCP only;-l— listening sockets only;-n— show ports and addresses as numbers, without resolving names;-p— which process holds the port (needssudo/root, otherwise the process name and PID are hidden).
Example output:
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("apache2",pid=1587,fd=6))Port 80 is held by apache2 — that's exactly why the second service (say, nginx) couldn't start. From here you have a choice:
- if you genuinely don't need the second service — stop it and disable its autostart:
systemctl disable --now apache2; - if you need both — change the port on whichever service you're currently configuring (for Nginx, that's the
listendirective in the vhost configuration, see "Nginx: install and your first site"), and don't forget to open the new port in the firewall.
A configuration syntax error
Most mature daemons can check their own configuration file's syntax without actually starting — use that before restart/reload so you don't take down a working process while testing a broken config:
nginx -t
apachectl configtest
sshd -tIf a service has no dedicated check flag, the most reliable way to find the error is still journalctl (Step 2): before exiting, the application almost always prints which file and line it stumbled on.
Separately from the application's own configuration, you can also check the systemd unit file itself — for typos in directives, dependency cycles, and the like:
systemd-analyze verify <service>If there are no problems, the command prints nothing; on error, it prints a warning naming the file and directive.
Wrong permissions
A service's unit file can explicitly set which user the process actually runs as — not necessarily root, even if you manage the service through sudo. Find out which user a service runs as:
systemctl show <service> -p User -p GroupAn empty value (User= with nothing after it) means the service runs as root by default. If a separate user is set, that user needs read (and sometimes write) access to the service's configuration, data, and working directory.
Check the owner and permissions:
ls -l /path/to/config
namei -l /path/to/confignamei -l shows, line by line, the permissions and owner of every directory in the path — from the root down to the file itself, not just the final file. That's useful when the file itself looks accessible (ls -l looks fine) but you still can't read it: access is blocked somewhere up the chain of parent directories.
Fix the owner and permissions (use the user and group the systemctl show command above actually returned — www-data here is just an example for Ubuntu/Debian):
chown www-data:www-data /path/to/config
chmod 640 /path/to/configausearch -m avc -ts recentIf the command isn't found, install the audit package (dnf install audit) and enable it with systemctl enable --now auditd. An example of restoring the SELinux context with semanage fcontext and restorecon for a directory outside the standard paths is in "Nginx: install and your first site".
A missing dependency
This comes in two flavors.
The first: the service starts before whatever it depends on is ready — for example, an application connects to a database right at startup, but the database on the same server hasn't come up yet after a reboot. The telltale symptom: run it manually a minute after reboot and it starts fine, but it crashes right at boot. Fix it with systemd dependencies — override the unit:
systemctl edit <service>The command opens an editor and saves your changes into a separate override file (/etc/systemd/system/<service>.service.d/override.conf), automatically reloading systemd's configuration once you save — no separate daemon-reload needed. Add:
[Unit]
After=postgresql.service
Requires=postgresql.service(replace postgresql.service with the actual unit your service depends on). After only sets startup order — it doesn't guarantee the dependency actually came up; Requires adds a hard dependency on top of that — if postgresql.service fails to start, systemd won't even try to start your service either (unlike the softer Wants=, which just ignores a dependency's failure).
The second flavor: something is missing at the system level — a library isn't installed, a Python virtual environment was never created, npm install was never run. In the log this usually shows up as a plain error from the application itself — something like ModuleNotFoundError: No module named 'foo' (Python) or Error: Cannot find module 'bar' (Node.js) — or that same status=203/EXEC if the file in ExecStart doesn't exist at all (say, the path to a venv is wrong). Check for missing libraries:
ldd $(which <binary>)Lines showing => not found tell you which .so libraries are missing — install the package that provides them.
Running out of memory (OOM)
If a service — on its own or together with everything else running on the server — used up all the available memory, the Linux kernel forcibly kills one of the processes to keep the server from locking up entirely. That's the OOM killer (out-of-memory killer). The process it kills isn't necessarily the biggest offender: the kernel picks based on a heuristic (oom_score), though it's usually the one that actually used the most.
The symptom in systemctl status is a crash with no clear reason on the application's own side:
Main PID: 4021 (code=killed, signal=KILL)or, as its own distinct status:
Active: failed (Result: oom-kill)Confirm it really was the OOM killer, and not a manual kill or the application crashing on its own:
dmesg -T | grep -i -E "out of memory|killed process"
journalctl -k -e-k shows kernel messages only (the same thing dmesg shows, but through the journal); dmesg's -T prints human-readable timestamps instead of seconds since boot. A typical line looks like:
Out of memory: Killed process 4021 (myapp) total-vm:2048000kB, anon-rss:1523000kB, file-rss:0kBCheck the overall memory and swap picture:
free -hWhat to do:
- quick fix — add swap, if there isn't any at all:
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab
swapon --show- more durable — lower the service's actual memory usage: fewer workers/threads, lower cache limits in its own configuration (this depends on the specific application);
- give the service a soft ceiling through systemd, so a memory leak only takes down its own cgroup instead of the whole server:
[Service]
MemoryMax=512M(added the same way, via systemctl edit <service>, as in the previous cause);
- if the service genuinely needs more memory than your plan has — increase the RAM allocation in the my.mhost.ee billing panel.
Step 4. Fix the cause and restart
If you edited the unit file by hand (rather than through systemctl edit), make sure to run daemon-reload before restarting — otherwise systemd keeps using the old version it already has loaded in memory:
systemctl daemon-reload
systemctl restart <service>
systemctl status <service>Don't rely solely on active (running) right after the command — wait a few seconds and watch the log, to catch a service that doesn't crash instantly but a couple of seconds after starting (for example, running out of memory under load):
journalctl -u <service> -f(Ctrl+C to exit follow mode)
What's next
If you were fixing a web server specifically, and the service itself is fine after restarting but the site still isn't reachable from outside, that's a separate topic covered in "My website won't load", including checking the firewall (see also "The ufw firewall: managing ports"). If journalctl complains about running out of disk space ("No space left on device") instead of any of the causes above, see "Disk is full"; if the service itself is fine but the server as a whole is sluggish, see "High server load". If nothing helps and you changed the configuration not long before this happened, it's often faster to roll back that specific change (or restore the configuration from a backup, if you have one) than to keep digging.
For diagnostics specific to a particular application, see that service's own article: "Nginx: install and your first site", "Apache: install and virtual hosts", "PostgreSQL: install and configure", "MySQL / MariaDB: install and configure", "Redis: install and configure".