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.
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 withjournalctl
You can verify systemd is running on your system:
ps -p 1 -o comm=
# systemdUnit 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:
[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
# 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=failedThe 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):
sudo useradd --system --no-create-home --shell /bin/false myapp
sudo chown -R myapp:myapp /opt/myappCreate the unit file:
sudo nano /etc/systemd/system/myapp.service[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.targetReload systemd so it sees the new file, then start:
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
sudo systemctl status myappNote: 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:
# 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 errService 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:
[Unit]
After=network.target postgresql.service
Requires=postgresql.serviceIf you only need the network to be up (not a specific service):
[Unit]
After=network-online.target
Wants=network-online.targetWants= 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:
sudo journalctl -u myapp -n 30 --no-pagerUsually a path error in ExecStart=, missing environment variable, or permission issue.
"Unit not found"
You forgot daemon-reload after creating the file:
sudo systemctl daemon-reloadService 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:
systemctl status <service>— first command when something is brokenjournalctl -u <service> -f— following logs during deploymentsystemctl restart <service>— after config changessystemctl enable <service>— making sure services survive reboots- 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
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
Build a Kubernetes Cluster with kubeadm from Scratch (2026)
Step-by-step guide to building a real multi-node Kubernetes cluster using kubeadm — no managed services, no shortcuts.
How to Set Up Ansible from Scratch (Complete Beginner Guide 2026)
Learn Ansible from zero — install it, configure SSH, write your first playbook, use variables and loops, and automate real server tasks step by step.
How to Set Up GitLab CI/CD from Scratch (2026 Complete Tutorial)
A practical step-by-step guide to setting up GitLab CI/CD pipelines from zero — covering runners, pipeline stages, Docker builds, deployment to Kubernetes, and best practices.