systemd Service Management Linux: Complete Guide

by David Park
systemd Service Management Linux: Complete Guide

What You'll End Up With

By the end of this guide you'll be able to create, control, inspect, and troubleshoot systemd services on Ubuntu 24.04. You'll know how to write a unit file from scratch, manage service state across reboots, read journal logs, and recover from common failure modes — all from the command line without touching a GUI.

Prerequisites:

  • Ubuntu 24.04 LTS (commands work on any systemd-based distro with minor path differences)
  • A non-root user with sudo privileges
  • Basic familiarity with a terminal text editor (nano or vim)
  • systemd version 255 (ships with Ubuntu 24.04 — verify with systemctl --version)

1. Understand the Core systemd Concepts

Before touching any command, get the vocabulary straight.

| Term | Meaning | |---| | Unit | Any resource systemd manages (service, socket, timer, mount) | | Service unit | A .service file describing a daemon or one-shot process | | Target | A group of units, roughly equivalent to SysV runlevels | | Journal | systemd's binary log store, queried with journalctl |

System-wide unit files live in /lib/systemd/system/. Local overrides go in /etc/systemd/system/. Files in /etc/ always win over /lib/.


2. Control Existing Services

These are the commands you'll use every day for systemd service management on Linux.

Start a service:

sudo systemctl start nginx

Sends SIGCONT / activates the unit immediately; does not persist across reboots.

Stop a service:

sudo systemctl stop nginx

Sends the configured stop signal (default SIGTERM, then SIGKILL after TimeoutStopSec).

Restart a service:

sudo systemctl restart nginx

Stops then starts. Use reload instead when the daemon supports in-place config reload:

sudo systemctl reload nginx

Enable a service at boot:

sudo systemctl enable nginx

Creates a symlink in the appropriate .wants/ directory so the unit starts on the next boot.

Disable a service from starting at boot:

sudo systemctl disable nginx

Removes the symlink; does not stop a currently running instance.

Enable and start in one command:

sudo systemctl enable --now nginx

Check current status:

sudo systemctl status nginx

Expected output (truncated):

● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/lib/systemd/system/nginx.service; enabled; preset: enabled)
     Active: active (running) since Mon 2025-01-06 10:22:14 UTC; 3min ago
    Process: 1234 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on;
   Main PID: 1235 (nginx)

The Active: line is your first diagnostic signal. active (running) means the process is alive. failed means the last run exited non-zero.


3. Write a Custom Service Unit File

This is where systemd service management on Linux becomes genuinely useful. You'll create a unit for a fictional Python app at /opt/myapp/app.py running as the myapp user.

Create the system user:

sudo useradd --system --no-create-home --shell /usr/sbin/nologin myapp

System users get a UID below 1000 and no login shell — correct for daemons.

Write the unit file:

sudo nano /etc/systemd/system/myapp.service

Paste the following content:

[Unit]
Description=My Python Application
After=network.target
Requires=network.target

[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/python3 /opt/myapp/app.py
Restart=on-failure
RestartSec=5s
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapp

# Hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ReadWritePaths=/opt/myapp/data

[Install]
WantedBy=multi-user.target

Key directives explained:

  • After=network.target — wait for network stack before starting; does not enforce a hard dependency by itself.
  • Requires=network.target — hard dependency; if network fails, this unit fails.
  • Type=simple — systemd considers the service started as soon as ExecStart forks. Use Type=notify if your app calls sd_notify().
  • Restart=on-failure — restarts only on non-zero exit or signal; does not restart on clean exit (code 0).
  • RestartSec=5s — waits 5 seconds between restart attempts to avoid rapid-fire respawn loops.
  • ProtectSystem=strict — mounts the entire filesystem read-only for the service; ReadWritePaths carves out exceptions.

4. Load and Start Your New Service

After writing a new unit file or editing an existing one, you must reload the daemon before systemd sees the changes.

Reload the systemd manager configuration:

sudo systemctl daemon-reload

This re-reads all unit files from disk. No services are restarted.

Enable and start the new service:

sudo systemctl enable --now myapp.service

Expected output:

Created symlink /etc/systemd/system/multi-user.target.wants/myapp.service → /etc/systemd/system/myapp.service.

Check it's running:

sudo systemctl status myapp.service

5. Read and Filter Logs with journalctl

All output from StandardOutput=journal and StandardError=journal lands in the systemd journal.

Stream live logs:

sudo journalctl -u myapp.service -f

The -f flag tails the journal, equivalent to tail -f on a log file.

Show logs since last boot:

sudo journalctl -u myapp.service -b

Show logs for a specific time window:

sudo journalctl -u myapp.service --since "2025-01-06 10:00:00" --until "2025-01-06 11:00:00"

Show only error-level and above:

sudo journalctl -u myapp.service -p err

Priority levels: emerg, alert, crit, err, warning, notice, info, debug.

Show the last 50 lines:

sudo journalctl -u myapp.service -n 50 --no-pager

6. Verify It Works

Run through this checklist after setting up any service.

1. Confirm the unit is active and enabled:

sudo systemctl is-active myapp.service
sudo systemctl is-enabled myapp.service

Expected output:

active
enabled

2. Verify the unit file has no syntax errors:

sudo systemd-analyze verify /etc/systemd/system/myapp.service

No output means no errors.

3. Simulate a reboot without rebooting:

sudo systemctl stop myapp.service
sudo systemctl start myapp.service
sudo systemctl status myapp.service

This confirms the service can cold-start from a stopped state.

4. Test the restart policy:

sudo kill -9 $(systemctl show -p MainPID --value myapp.service)
sleep 6
sudo systemctl status myapp.service

After 5 seconds (RestartSec=5s), systemd should have restarted the process. The Active: line should read active (running) and the restart count in the status output should increment.

5. Check the full dependency tree:

sudo systemctl list-dependencies myapp.service

7. Troubleshooting

Service fails to start with status=203/EXEC The ExecStart binary path is wrong or not executable.

ls -la /usr/bin/python3
which python3

Correct the path in the unit file, run daemon-reload, then restart.

Service enters failed state immediately Read the exact exit code:

sudo systemctl status myapp.service
sudo journalctl -u myapp.service -n 30 --no-pager

Look for code=exited, status=1 or a Python traceback in the journal.

daemon-reload has no effect You edited the wrong file. Confirm the path:

sudo systemctl cat myapp.service

The first comment line shows the file systemd is actually reading.

Service starts but crashes in a restart loop Check StartLimitBurst and StartLimitIntervalSec. By default, systemd stops trying after 5 failures in 10 seconds and sets the unit to failed. Override in [Unit]:

[Unit]
StartLimitBurst=10
StartLimitIntervalSec=60s

Then daemon-reload and reset-failed to clear the failure state:

sudo systemctl reset-failed myapp.service
sudo systemctl start myapp.service

Permission denied errors in journal The User=myapp account doesn't own the working directory or data path.

sudo chown -R myapp:myapp /opt/myapp

Unit file changes not picked up after edit You forgot daemon-reload. Always run it after any unit file edit:

sudo systemctl daemon-reload

Next Steps

With systemd service management on Linux covered, these are the natural extensions:

  • systemd timers — replace cron jobs with OnCalendar= timer units for better logging and dependency control.
  • Drop-in overrides — use sudo systemctl edit nginx to create a /etc/systemd/system/nginx.service.d/override.conf that survives package upgrades instead of editing the vendor unit directly.
  • Socket activation — define a .socket unit so systemd holds the port open and spawns the service only on first connection, cutting idle memory use. For a deeper dive into system reliability, consider reading about backup strategy for development database to understand how to protect critical application state.
  • Resource limits — add CPUQuota=50%, MemoryMax=512M, and TasksMax=64 to the [Service] block to prevent any single daemon from starving the host.

All of the above use the same daemon-reloadenablestatusjournalctl workflow you've already practiced here.