🎉 DevOps Interview Prep Bundle is live — 1000+ Q&A across 20 topicsGet it →
All Articles

What is systemd and Linux Services? Explained Simply

Learn what systemd is, how Linux services work, key systemctl commands, how to create a custom service, and how to read logs with journalctl — practical guide for DevOps engineers.

DevOpsBoys5 min read
Share:Tweet

Every DevOps engineer works with Linux servers. And on every modern Linux server — Ubuntu, Debian, RHEL, CentOS, Amazon Linux — the first process that runs when the system boots is systemd. Understanding it takes about an hour and saves you hours of debugging.

What is systemd?

systemd is the init system for Linux. The init system is PID 1 — the first process started by the kernel, responsible for starting everything else: network services, SSH, your application, cron jobs, everything.

Before systemd, Linux used SysV init (pronounced "System Five") — shell scripts in /etc/init.d/ that ran sequentially. Boot was slow because services started one after another. Dependency management was fragile.

systemd replaced this with:

  • Parallel startup — services that do not depend on each other start simultaneously
  • Unit files — declarative configuration instead of shell scripts
  • Automatic restart — processes that crash can be restarted automatically
  • Unified logging — all service output goes to journald, queryable with journalctl

You can verify systemd is running on your system:

bash
ps -p 1 -o comm=
# systemd

Unit Files

systemd manages "units" — a unit is anything systemd knows about: a service, a timer, a socket, a mount point. Each has a unit file.

Service unit files live in:

  • /lib/systemd/system/ — system-installed services (owned by packages)
  • /etc/systemd/system/ — your custom services (put yours here)

A unit file has three sections:

ini
[Unit]
Description=My Application
After=network.target
 
[Service]
Type=simple
User=myapp
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/python3 /opt/myapp/app.py
Restart=always
RestartSec=5
 
[Install]
WantedBy=multi-user.target
  • [Unit] — metadata and dependencies
  • [Service] — what to run and how
  • [Install] — when to start (at which system target/runlevel)

Essential systemctl Commands

bash
# Start a service
sudo systemctl start nginx
 
# Stop a service
sudo systemctl stop nginx
 
# Restart a service
sudo systemctl restart nginx
 
# Reload config without full restart (if service supports it)
sudo systemctl reload nginx
 
# Check if running
sudo systemctl status nginx
 
# Enable at boot (creates symlink so it starts automatically)
sudo systemctl enable nginx
 
# Disable at boot
sudo systemctl disable nginx
 
# Enable AND start immediately (one command)
sudo systemctl enable --now nginx
 
# List all running services
sudo systemctl list-units --type=service --state=running
 
# List all failed services
sudo systemctl list-units --type=service --state=failed

The status command gives you the most useful output:

● nginx.service - A high performance web server
     Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
     Active: active (running) since Sun 2026-06-29 10:23:45 UTC; 2h 15min ago
    Process: 1234 ExecStartPre=/usr/sbin/nginx -t (code=exited, status=0/SUCCESS)
   Main PID: 1238 (nginx)
      Tasks: 3 (limit: 4702)
     Memory: 6.4M
        CPU: 45ms
     CGroup: /system.slice/nginx.service
             ├─1238 "nginx: master process /usr/sbin/nginx -g daemon off;"
             └─1239 "nginx: worker process"

Creating a Custom Service

Let us run a Python Flask app as a systemd service. The app is at /opt/myapp/app.py.

First, create a user for the app (never run app services as root):

bash
sudo useradd --system --no-create-home --shell /bin/false myapp
sudo chown -R myapp:myapp /opt/myapp

Create the unit file:

bash
sudo nano /etc/systemd/system/myapp.service
ini
[Unit]
Description=My Flask Application
After=network.target postgresql.service
Requires=postgresql.service
 
[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
Environment=FLASK_ENV=production
Environment=DATABASE_URL=postgresql://localhost/myapp
ExecStart=/opt/myapp/venv/bin/python app.py
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal
 
[Install]
WantedBy=multi-user.target

Reload systemd so it sees the new file, then start:

bash
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
sudo systemctl status myapp

Note: daemon-reload is required any time you create or edit a unit file. Without it, systemd uses the cached version.

Reading Logs with journalctl

All service output goes to the journal. journalctl is how you read it:

bash
# All logs for a service
sudo journalctl -u myapp
 
# Last 50 lines
sudo journalctl -u myapp -n 50
 
# Follow in real time (like tail -f)
sudo journalctl -u myapp -f
 
# Logs since last boot
sudo journalctl -u myapp -b
 
# Logs in a time range
sudo journalctl -u myapp --since "2026-06-29 10:00" --until "2026-06-29 11:00"
 
# Show only errors
sudo journalctl -u myapp -p err

Service Dependencies: After= and Requires=

After= means "start after this unit is active, but do not fail if it is not present." Requires= means "this unit is required — if it fails, stop my service too."

Common pattern for a web app that needs a database:

ini
[Unit]
After=network.target postgresql.service
Requires=postgresql.service

If you only need the network to be up (not a specific service):

ini
[Unit]
After=network-online.target
Wants=network-online.target

Wants= is softer than Requires= — it attempts to start the dependency but does not fail if it cannot.

Common Errors and Fixes

"Failed to start" with status=1

Check the journal immediately:

bash
sudo journalctl -u myapp -n 30 --no-pager

Usually a path error in ExecStart=, missing environment variable, or permission issue.

"Unit not found"

You forgot daemon-reload after creating the file:

bash
sudo systemctl daemon-reload

Service starts then immediately exits

Set Type=simple and make sure your ExecStart= command stays in the foreground. If your app daemonizes itself, use Type=forking instead.

"Permission denied" in logs

Check the User= directive. The user must have read access to the working directory and execute permission on the binary.

What DevOps Engineers Need to Know Day-to-Day

In practice, most of your systemd work involves:

  1. systemctl status <service> — first command when something is broken
  2. journalctl -u <service> -f — following logs during deployment
  3. systemctl restart <service> — after config changes
  4. systemctl enable <service> — making sure services survive reboots
  5. Writing simple unit files for custom apps or scripts

You will not need to write complex timer units or socket activation in most DevOps roles, but understanding the basic unit file structure means you can read and debug any service configuration on any Linux system.

🔧

Today I Fixed

Short real fixes from production — posted daily

Browse fixes
Newsletter

Stay ahead of the curve

Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam — just practical engineering content.

Related Articles

Comments