How to Manage Linux Services with systemd: Start, Stop, and Keep Them Alive

Why systemd?

Every Linux machine that matters runs systemd. It’s the init system — the first process that boots, the parent of all other processes, and the thing responsible for starting, stopping, and supervising services. If you’ve ever run sudo systemctl restart nginx, you’ve already used it. But most people only scratch the surface.

In this tutorial, you’ll learn how to manage services with systemd like a pro: starting, stopping, enabling, debugging, and even writing your own service files so your applications stay alive after crashes and reboots.

Prerequisites

  • Any modern Linux distribution (Ubuntu 18.04+, Debian 10+, Fedora, CentOS 7+, Arch, etc.)
  • A terminal and sudo access
  • Basic familiarity with the command line

The Essentials: systemctl

systemctl is your primary tool for interacting with systemd. Think of it as the remote control for every service on your machine.

Check the status of a service

sudo systemctl status nginx

This tells you whether the service is running, when it started, recent log lines, and whether it’s set to start on boot. It’s the first thing you should run when something isn’t working.

Start and stop services

# Start a service immediately
sudo systemctl start nginx

# Stop a service
sudo systemctl stop nginx

# Restart a service (stop + start)
sudo systemctl restart nginx

# Reload configuration without restarting (zero-downtime)
sudo systemctl reload nginx

# Reload-or-restart (graceful fallback)
sudo systemctl reload-or-restart nginx

Why reload vs restart? Reload tells the running process to re-read its configuration file without killing it. If the service supports it, this means no downtime. Nginx, Apache, and PostgreSQL all support graceful reloads. When in doubt, use reload-or-restart.

Enable and disable services (boot behavior)

# Start service on boot (enable a symlink)
sudo systemctl enable nginx

# Disable auto-start on boot
sudo systemctl disable nginx

# Do both in one step: start now AND enable on boot
sudo systemctl enable --now nginx

What “enable” actually does: It creates a symlink from the service file in /lib/systemd/system/ to a directory called a “want” (like multi-user.target.wants/). Nothing more. It doesn’t start the service — it just tells systemd “when you reach this boot target, start me too.”

See all running services

# List all active (running) units
systemctl list-units --type=service --state=running

# List all loaded service units (running, stopped, or failed)
systemctl list-units --type=service

# List all installed service files (enabled/disabled)
systemctl list-unit-files --type=service

Reading Unit Files

Every systemd service is defined in a unit file, usually found at /lib/systemd/system/<service>.service (for system packages) or /etc/systemd/system/<service>.service (for your custom overrides).

Let’s look at the Nginx unit file:

systemctl cat nginx

You’ll see something like this:

[Unit]
Description=A high performance web server and a reverse proxy server
After=network.target

[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t -q -g 'daemon on; master_process on;'
ExecStart=/usr/sbin/nginx -g 'daemon on; master_process on;'
ExecReload=/usr/sbin/nginx -g 'daemon on; master_process on;' -s reload
PrivateTmp=true

[Install]
WantedBy=multi-user.target

Three sections matter here:

  • [Unit] — metadata and dependencies (After=network.target means “don’t start until networking is up”)
  • [Service] — how to start, stop, and supervise the process
  • [Install] — when to start (which “target” to attach to)

Writing Your Own Service File

This is where systemd gets powerful. Got a Python script, a Node.js app, or a bot that should always be running? Write a service file for it.

Example: A simple Python bot

Say you have a Python script at /home/noodly/bot/main.py and you want it to start automatically after boot, restart if it crashes, and only run after networking is available.

Create a new unit file:

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

Paste in:

[Unit]
Description=My Python Bot
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=noodly
WorkingDirectory=/home/noodly/bot
ExecStart=/usr/bin/python3 /home/noodly/bot/main.py
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

Let’s break down the key options:

Option What it does
Type=simple The process runs in the foreground. systemd considers it “started” as soon as the process is running. Use this for most apps.
Type=forking The process forks (daemonizes) itself. systemd watches for the PID file. Use for nginx, apache, etc.
User=noodly Run as this user, not root. Always use this for security.
Restart=on-failure Restart if the process exits with a non-zero code.
RestartSec=10 Wait 10 seconds before restarting (prevents rapid restart loops).
After=network-online.target Don’t start until the network is actually connected (not just the network service started).

After creating the file, reload systemd’s configuration and start the service:

# Reload systemd to pick up the new unit file
sudo systemctl daemon-reload

# Start the service now
sudo systemctl start mybot

# Enable it for boot
sudo systemctl enable --now mybot

# Check it's running
sudo systemctl status mybot

Debugging with journalctl

When a service fails, journalctl is your best friend. It reads the structured logs that systemd collects from every service.

# View all logs for a specific service
sudo journalctl -u mybot

# Follow logs in real-time (like tail -f)
sudo journalctl -u mybot -f

# Show only logs since last boot
sudo journalctl -u mybot -b

# Show logs from the last 30 minutes
sudo journalctl -u mybot --since '30 min ago'

# Show logs between two timestamps
sudo journalctl -u mybot --since '2026-06-14 09:00' --until '2026-06-14 10:00'

# Show only error-level and above
sudo journalctl -u mybot -p err

Pro tip: Always check journalctl -u <service> before anything else when debugging. It almost always tells you exactly what went wrong — missing config file, permission denied, port already in use, etc.

Masking and Overriding Services

Masking (permanently disable)

If you want to make absolutely sure a service can never be started — even by accident or by another service’s dependency:

# Mask a service (creates a symlink to /dev/null)
sudo systemctl mask servicename

# Unmask it later
sudo systemctl unmask servicename

Example: you don’t need a Bluetooth daemon on a headless server:

sudo systemctl mask bluetooth.service

Overriding without modifying the original

Package updates will overwrite unit files in /lib/systemd/system/. To make persistent customizations, use a drop-in:

# Create an override directory
sudo systemctl edit nginx

This opens an editor with a file at /etc/systemd/system/nginx.service.d/override.conf. Add only the sections you want to change:

[Service]
Restart=always
RestartSec=5

Save, then sudo systemctl daemon-reload and you’re done. The original file stays untouched.

Timers: cron’s Modern Replacement

systemd timers can replace cron jobs. They offer better logging, dependency management, and retry logic.

Create /etc/systemd/system/backup.service:

[Unit]
Description=Daily backup script

[Service]
Type=oneshot
ExecStart=/home/noodly/scripts/backup.sh
User=noodly

Create /etc/systemd/system/backup.timer:

[Unit]
Description=Run backup daily at 2am

[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true

[Install]
WantedBy=timers.target

Persistent=true means: if the machine was off at 2am, run the backup as soon as it boots. Enable the timer (not the service):

sudo systemctl enable --now backup.timer

# List all active timers
systemctl list-timers

Common Pitfalls

  • Forgot daemon-reload: After editing any unit file, always run sudo systemctl daemon-reload before restarting. systemd won’t see your changes otherwise.
  • Using Type=forking for a foreground app: If your app doesn’t daemonize, use Type=simple. systemd will think the service failed because it expects a fork.
  • Running as root unnecessarily: Always set User= and Group= unless your service genuinely needs root privileges.
  • No ExecStop needed for Type=simple: systemd sends SIGTERM by default when stopping. Use ExecStop only if you need custom shutdown behavior.
  • Chaining too many After= dependencies: Keep dependencies minimal. Use After=network-online.target instead of listing every service individually — one target should cover it.

Quick Reference Card

# Management
systemctl start|stop|restart|reload|status <service>
systemctl enable|disable|mask|unmask <service>
systemctl enable --now <service>          # start + enable

# Information
systemctl is-active <service>            # exit code 0 if running
systemctl is-enabled <service>           # exit code 0 if enabled
systemctl is-failed <service>            # exit code 0 if failed
systemctl show <service> --property=MainPID

# Logs
journalctl -u <service> -f              # follow
journalctl -u <service> -p err          # errors only
journalctl -u <service> --since today

# Timers
systemctl list-timers                    # all scheduled tasks
systemctl start <timer>.timer           # trigger a timer

Wrapping Up

systemd isn’t glamorous, but it’s the backbone of every modern Linux system. Once you can write a service file and read the logs, you stop worrying about “what happens when the server restarts” and start treating crashes as a non-event. That’s a big deal.

Start with one service — a bot, a script, anything — and get comfortable with the cycle: write unit file → daemon-reload → start → enable → check status → read logs. Once that clicks, you’ll reach for systemd instinctively.

Your services deserve to stay alive. systemd will keep them that way.

Leave a Reply

Your email address will not be published. Required fields are marked *