Introduction: The Init System That Runs Everything
If you manage Linux servers, you interact with systemd dozens of times a day. Every time you start a service, check a log, or schedule a task, systemd is involved. Yet most admins use only 20% of its capabilities — the basic start, stop, status trio.
Love it or hate it (and plenty of people have strong opinions), systemd is the backbone of every modern Ubuntu, Debian, RHEL, CentOS, Fedora, and Arch Linux installation. Understanding it deeply is no longer optional — it's a core competency for anyone who manages Linux servers professionally.
This guide covers everything you need to go from "I know the basics" to "I actually understand what's happening under the hood." We'll cover unit files, service management, timers as a cron replacement, journald for log management, security hardening directives, and a complete troubleshooting toolkit.
Looking for a first-pass overview instead? Start with systemd Explained and come back here when you are ready for unit internals, watchdog timers, and security hardening directives.
By the end, you'll be writing production-grade service files, replacing cron with timers, hunting down startup failures with confidence, and hardening services against privilege escalation — all using tools that ship with your OS.
1. What is systemd?
systemd is not just an init system. It's a suite of tools that manages the full lifecycle of a Linux system — from the moment the kernel hands off control to PID 1, through service startup, log collection, scheduled task execution, and system shutdown.
A Brief History: How We Got Here
SysVinit (the old way) used shell scripts in /etc/init.d/. Each script had to implement start, stop, restart, and status manually. Services started sequentially. A single slow service delayed everything after it. Parallel startup was a hack. Dependency management was manual and fragile.
Upstart (Ubuntu 2006–2014) improved things with event-based startup. But it still had quirks and never fully replaced SysVinit semantics.
systemd (Lennart Poettering, 2010) took a different approach: declarative unit files, socket activation, cgroups integration from day one, and genuine parallel startup with dependency tracking. Ubuntu switched to systemd in 2015 (15.04). Debian followed in Jessie (2015). RHEL/CentOS in version 7 (2014).
Why systemd Won
- Parallel startup — Services start concurrently unless dependencies require ordering, cutting boot times dramatically
- Dependency management — Declare what your service needs; systemd figures out the order
- Cgroups integration — Every service gets its own cgroup slice for resource tracking and isolation
- Socket activation — Services can start on-demand when a connection arrives, not at boot
- Consistent tooling — One tool (
systemctl) manages everything instead of scattered init scripts - Journald — Structured, indexed, binary logs with rich filtering — no more grepping through text files
The systemd Ecosystem Components
| Component | What It Does |
|---|---|
systemd | PID 1 — manages units, dependencies, boot sequence |
systemctl | Control interface — start, stop, enable, query units |
journald | Log collection daemon — collects from all sources, binary format |
journalctl | Log viewer — query and filter journald entries |
logind | User session management, seat management |
networkd | Network configuration (alternative to NetworkManager) |
resolved | DNS resolver with caching and mDNS support |
timedatectl | Time and timezone management (+ NTP sync via timesyncd) |
hostnamectl | Hostname and machine metadata management |
systemd-analyze | Boot performance analysis and unit validation |
2. Unit Files — The Building Blocks
Everything in systemd is a "unit" — a declarative configuration file that describes what to start, how to start it, and when. Understanding unit files is the foundation of everything else.
Unit Types
| Type | Extension | Purpose |
|---|---|---|
| Service | .service | Daemons and applications |
| Timer | .timer | Scheduled tasks (cron replacement) |
| Socket | .socket | Socket activation (start service on connection) |
| Mount | .mount | Filesystem mount points |
| Target | .target | Grouping and synchronization points (like runlevels) |
| Path | .path | Filesystem watch (trigger on file/directory changes) |
| Slice | .slice | Cgroup hierarchy for resource management |
| Scope | .scope | Externally created processes (runtime only) |
| Device | .device | Hardware devices from udev |
Where Unit Files Live
systemd loads unit files from multiple locations with a clear priority order:
| Path | Priority | Purpose |
|---|---|---|
/etc/systemd/system/ | Highest | Admin-created and customized units. These override everything else. |
/run/systemd/system/ | Medium | Runtime-generated units. Deleted on reboot. |
/usr/lib/systemd/system/ | Lowest | Package-provided defaults. Never edit these directly — updates will overwrite your changes. |
Rule of thumb: Always put your custom service files in/etc/systemd/system/. To override part of a package-provided unit, use a drop-in override (systemctl edit servicename) rather than editing the file in/usr/lib/systemd/system/.
Unit File Anatomy
Every service unit file has three sections. Here's a complete, well-commented example:
# /etc/systemd/system/myapp.service
[Unit]
# Human-readable description
Description=My Application Server
# Documentation URLs (man pages, web links)
Documentation=https://myapp.example.com/docs
# Ordering: start AFTER these units (but don't require them)
After=network.target
# Hard dependency: if postgresql fails, this unit fails too
Requires=postgresql.service
# Soft dependency: try to start redis if available, but don't fail if it's not
Wants=redis.service
[Service]
# How the process behaves (simple, forking, oneshot, notify, exec)
Type=simple
# Run as this user/group (NOT root)
User=myapp
Group=myapp
# Working directory for the process
WorkingDirectory=/opt/myapp
# The actual command to start the service
ExecStart=/opt/myapp/bin/server --config /opt/myapp/conf/app.yaml
# Command to reload config without full restart (optional)
ExecReload=/bin/kill -HUP $MAINPID
# Run this before ExecStart (setup tasks)
ExecStartPre=/opt/myapp/bin/preflight-check.sh
# Run this after the process exits (cleanup tasks)
ExecStopPost=/opt/myapp/bin/cleanup.sh
# When to restart: no, on-success, on-failure, on-abnormal, always
Restart=on-failure
# Wait this many seconds before restarting
RestartSec=5
# Maximum open files (ulimit -n equivalent)
LimitNOFILE=65535
# Environment variables
Environment=APP_ENV=production
Environment=LOG_LEVEL=info
# Or load from a file (keep secrets out of the unit file)
EnvironmentFile=/etc/myapp/env
# Timeout for start/stop (default: 90s)
TimeoutStartSec=30
TimeoutStopSec=30
[Install]
# Which target activates this service (multi-user.target = runlevel 3)
WantedBy=multi-user.target
3. Service Management with systemctl
The systemctl command is your primary interface to systemd. Here's a complete reference organized by task.
Start, Stop, Restart
# Start a service now
systemctl start nginx
# Stop a service
systemctl stop nginx
# Restart (stop + start)
systemctl restart nginx
# Reload config without downtime (if supported by the service)
systemctl reload nginx
# Reload OR restart (reload if supported, restart otherwise)
systemctl reload-or-restart nginx
Enable / Disable (Boot Behavior)
# Enable: create symlinks so service starts on boot
systemctl enable nginx
# Enable AND start now in one command
systemctl enable --now nginx
# Disable: remove boot symlinks (service stays installed)
systemctl disable nginx
# Disable AND stop now
systemctl disable --now nginx
# Mask: make the service completely unstartable (symlink to /dev/null)
systemctl mask nginx
# Unmask: undo masking
systemctl unmask nginx
enable vs start:startruns the service now.enablemakes it run on next boot. You usually want both. Useenable --nowas a shortcut.
Status and Inspection
# Full status with recent log lines
systemctl status nginx
# Quick boolean checks
systemctl is-active nginx # Prints "active" or "inactive"
systemctl is-enabled nginx # Prints "enabled" or "disabled"
systemctl is-failed nginx # Prints "failed" or "active"
# List all running services
systemctl list-units --type=service --state=running
# List all installed service files and their state
systemctl list-unit-files --type=service
# View the actual unit file content
systemctl cat nginx
# View ALL properties of a unit (verbose)
systemctl show nginx
# View only specific properties
systemctl show nginx --property=ActiveState,MainPID,Restart
Editing Unit Files
# Open a drop-in override file (creates /etc/systemd/system/nginx.service.d/override.conf)
# This is the CORRECT way to customize package-provided units
systemctl edit nginx
# Edit the full unit file (creates a copy in /etc/systemd/system/)
systemctl edit --full nginx
# ALWAYS run this after any manual unit file changes
systemctl daemon-reload
Listing and Filtering
# List failed services
systemctl --failed
# List all timers
systemctl list-timers
# List all sockets
systemctl list-units --type=socket
# List all units in a specific state
systemctl list-units --state=failed
systemctl list-units --state=activating
4. Service Types Explained
The Type= directive tells systemd how to determine when a service is "ready." Getting this wrong causes dependency failures, boot delays, and race conditions.
| Type | How Ready is Detected | Best For |
|---|---|---|
simple | Immediately after ExecStart launches (default) | Foreground processes, Go/Node.js/Python apps |
exec | After the binary successfully executes (post-exec) | Like simple, but waits to confirm binary exists and is executable |
forking | After the parent process exits (child is the daemon) | Traditional daemons that fork to background (Apache, nginx) |
oneshot | After the process exits successfully | One-time tasks, scripts, initialization jobs |
notify | After the process sends sd_notify("READY=1") | Apps that explicitly signal readiness (systemd-aware apps) |
dbus | After acquiring a D-Bus name | D-Bus services only |
idle | Like simple but waits for all jobs to complete first | Low-priority startup tasks |
Decision Guide
- Your app runs in the foreground and you wrote it → Use
Type=simple - Your app forks to background (old-style daemon) → Use
Type=forkingand setPIDFile= - You want precise readiness detection → Use
Type=notifyand addsd_notifycalls to your app - You're running a shell script or one-time task → Use
Type=oneshotwithRemainAfterExit=yesif you want it to stay "active"
Common mistake: UsingType=forkingfor a Go or Node.js binary that doesn't actually fork. These processes run in the foreground — useType=simple. WithType=forking, systemd waits for the parent to exit (which never happens), marking the service as failed.
5. Restart Policies and Watchdog
A service that crashes without an auto-restart policy leaves you scrambling. systemd's restart logic is powerful and highly configurable.
Restart Values
| Value | Restarts When… |
|---|---|
no | Never restarts (default) |
on-success | Only on clean exit (exit code 0) |
on-failure | On non-zero exit code, signal, or timeout |
on-abnormal | On signals, core dumps, watchdog timeout — NOT on clean exit or failure exit code |
on-abort | Only on unhandled signals (core dump) |
on-watchdog | Only when watchdog timeout expires |
always | Always restarts, no matter what (use carefully) |
Restart Rate Limiting
If your service keeps crashing, you don't want systemd restarting it infinitely. Use these to prevent restart storms:
[Service]
Restart=on-failure
RestartSec=5
# Allow max 5 restarts within a 30-second window
# After that, the service enters "failed" state and stops restarting
StartLimitIntervalSec=30
StartLimitBurst=5
To reset a service that hit its restart limit:
systemctl reset-failed myapp
systemctl start myapp
Watchdog: Catch Hanging Services
A service can be "running" (process alive) but completely hung (not responding). The watchdog catches this:
[Service]
Type=notify
WatchdogSec=30
Restart=on-watchdog
NotifyAccess=main
Your application must call sd_notify("WATCHDOG=1") at least every WatchdogSec seconds. If systemd doesn't hear from it, the service is killed and restarted. This is production-grade self-healing for critical services.
6. Dependencies and Ordering
Dependency management is where systemd really earns its keep — and where most admins get confused. There's an important distinction between ordering and requirements.
Ordering Directives (When, Not Whether)
# Start this service AFTER network.target completes
After=network.target
# Start this service BEFORE another (rarely used in service files)
Before=some-other.service
After and Before only control order. They don't create dependencies — if network.target fails, your service still starts, just after.
Requirement Directives (Whether)
# HARD dependency — if postgresql fails, this unit fails too
Requires=postgresql.service
# SOFT dependency — start postgresql if possible, but don't fail if it can't start
Wants=postgresql.service
# STRONGER than Requires — also deactivates this unit if postgresql is stopped
BindsTo=postgresql.service
# Cannot run at the same time as another service
Conflicts=apache2.service
# Stop/restart together (but don't require each other for initial start)
PartOf=myapp.target
Practical Dependency Pattern
For a web application that needs a database:
[Unit]
Description=My Web Application
After=network.target postgresql.service redis.service
Wants=redis.service
Requires=postgresql.service
This reads: "Start after network, PostgreSQL, and Redis are up. Fail if PostgreSQL isn't available. Redis is optional — try to start it but don't fail without it."
Visualizing Dependencies
# Generate a dependency graph (requires graphviz)
systemd-analyze dot nginx.service | dot -Tsvg > nginx-deps.svg
# Show what a service is pulling in
systemctl list-dependencies nginx.service
# Show reverse dependencies (what depends on this service)
systemctl list-dependencies --reverse nginx.service
7. Creating Custom Services: Three Real Examples
Example 1: Node.js Application
# /etc/systemd/system/nodeapp.service
[Unit]
Description=Node.js Web Application
Documentation=https://myapp.example.com
After=network.target
[Service]
Type=simple
User=nodeapp
Group=nodeapp
WorkingDirectory=/opt/nodeapp
Environment=NODE_ENV=production
Environment=PORT=3000
ExecStart=/usr/bin/node /opt/nodeapp/server.js
Restart=on-failure
RestartSec=3
StandardOutput=journal
StandardError=journal
SyslogIdentifier=nodeapp
[Install]
WantedBy=multi-user.target
Example 2: Go Binary with Graceful Shutdown
Go servers respond to SIGTERM with a graceful shutdown. Configure systemd to give them time:
# /etc/systemd/system/goapi.service
[Unit]
Description=Go REST API Server
After=network.target postgresql.service
Requires=postgresql.service
[Service]
Type=simple
User=goapi
Group=goapi
WorkingDirectory=/opt/goapi
# Load secrets from environment file (chmod 600, owned by root)
EnvironmentFile=/etc/goapi/env
ExecStart=/opt/goapi/bin/apiserver
ExecReload=/bin/kill -USR1 $MAINPID
# Give graceful shutdown 30 seconds before force-killing
TimeoutStopSec=30
KillMode=mixed
KillSignal=SIGTERM
Restart=on-failure
RestartSec=5
StartLimitIntervalSec=60
StartLimitBurst=3
LimitNOFILE=65535
LimitNPROC=4096
[Install]
WantedBy=multi-user.target
Example 3: Python Gunicorn with Socket Activation
Socket activation starts your service on-demand when the first connection arrives — not at boot. This saves resources and eliminates the "service not ready yet" race condition.
# /etc/systemd/system/gunicorn.socket
[Unit]
Description=Gunicorn WSGI Socket
[Socket]
ListenStream=/run/gunicorn.sock
SocketUser=www-data
SocketMode=0660
[Install]
WantedBy=sockets.target
# /etc/systemd/system/gunicorn.service
[Unit]
Description=Gunicorn WSGI Daemon
Requires=gunicorn.socket
After=network.target
[Service]
Type=notify
User=gunicorn
Group=www-data
RuntimeDirectory=gunicorn
WorkingDirectory=/opt/webapp
ExecStart=/opt/webapp/venv/bin/gunicorn \
--access-logfile - \
--workers 4 \
--bind unix:/run/gunicorn/gunicorn.sock \
webapp.wsgi:application
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true
[Install]
WantedBy=multi-user.target
# Enable the socket (service starts on-demand)
systemctl enable --now gunicorn.socket
8. systemd Timers — Modern Cron
systemd timers are the modern replacement for cron. They're more powerful, better integrated with the logging system, and support features cron never had.
Why Timers Beat Cron
| Feature | cron | systemd timer |
|---|---|---|
| Logging | Email or /dev/null | Full journald integration |
| Dependencies | None | Full unit dependency system |
| Randomized delay | Hack with sleep | RandomizedDelaySec built-in |
| Catch-up missed runs | Never | Persistent=true |
| Resource limits | None | Full cgroup integration |
| Status / last run | None | systemctl list-timers |
| On-demand trigger | Workaround needed | systemctl start job.service |
Timer File Structure
A timer always pairs with a service file of the same name. The timer triggers the service.
# /etc/systemd/system/daily-backup.timer
[Unit]
Description=Daily Database Backup
[Timer]
# Run every day at 2:00 AM
OnCalendar=*-*-* 02:00:00
# If the system was off at 2 AM, run as soon as it boots
Persistent=true
# Randomize start time up to 5 minutes (spread load across servers)
RandomizedDelaySec=300
[Install]
WantedBy=timers.target
# /etc/systemd/system/daily-backup.service
[Unit]
Description=Daily Database Backup Job
[Service]
Type=oneshot
User=backup
ExecStart=/opt/scripts/backup-databases.sh
StandardOutput=journal
StandardError=journal
# Enable and start the timer
systemctl enable --now daily-backup.timer
# Check status of all timers
systemctl list-timers
OnCalendar Syntax Reference
| Expression | Meaning |
|---|---|
hourly | Every hour at :00 |
daily | Every day at 00:00 |
weekly | Every Monday at 00:00 |
monthly | 1st of every month at 00:00 |
*-*-* 02:30:00 | Every day at 2:30 AM |
Mon *-*-* 09:00:00 | Every Monday at 9:00 AM |
*-*-1 00:00:00 | 1st of every month at midnight |
*-*-* *:00:00 | Every hour on the hour |
*-*-* *:0/15:00 | Every 15 minutes |
Validate your OnCalendar expression before deploying:
systemd-analyze calendar "*-*-* 02:00:00"
systemd-analyze calendar "Mon *-*-* 09:00:00"
Monotonic Timers (Relative Time)
[Timer]
# Run 5 minutes after boot
OnBootSec=5min
# Run 1 hour after the service last activated
OnUnitActiveSec=1h
9. journald — Centralized Log Management
journald collects logs from every systemd service, the kernel, and applications that write to stdout/stderr. It stores logs in a binary, indexed format at /var/log/journal/ — enabling fast, powerful queries that text-based log files can't match.
Basic journalctl Usage
# Show all logs (newest last)
journalctl
# Follow new messages (like tail -f syslog)
journalctl -f
# Show logs for a specific service
journalctl -u nginx
# Follow logs for a specific service
journalctl -u nginx -f
# Show only the last 50 lines
journalctl -u nginx -n 50
Time Filtering
# Logs from the last hour
journalctl -u nginx --since "1 hour ago"
# Logs from yesterday
journalctl -u nginx --since yesterday
# Specific time range
journalctl -u nginx --since "2026-03-01 00:00:00" --until "2026-03-02 00:00:00"
# Current boot only
journalctl -b
# Previous boot (useful when debugging crashes)
journalctl -b -1
# Second-to-last boot
journalctl -b -2
# List available boots
journalctl --list-boots
Priority Filtering
journald uses syslog priority levels:
# Show only errors and above (err, crit, alert, emerg)
journalctl -p err
# Show only critical and above
journalctl -p crit
# Priority range: warnings to errors only
journalctl -p warning..err
# Priority levels (0=emerg, 1=alert, 2=crit, 3=err, 4=warning, 5=notice, 6=info, 7=debug)
journalctl -p 0..3
Output Formats
# Short default format
journalctl -u nginx
# JSON output (for log shipping, automation)
journalctl -u nginx -o json
# JSON pretty-printed
journalctl -u nginx -o json-pretty
# Cat format (message only, no metadata)
journalctl -u nginx -o cat
# Verbose format (all fields)
journalctl -u nginx -o verbose
Disk Usage and Maintenance
# Check how much disk space journals use
journalctl --disk-usage
# Rotate logs immediately (archive current, start fresh)
journalctl --rotate
# Remove old journals, keep only last 500MB
journalctl --vacuum-size=500M
# Remove journals older than 7 days
journalctl --vacuum-time=7d
# Keep only last 3 months
journalctl --vacuum-time=3months
Configuring journald
Edit /etc/systemd/journald.conf to control retention and storage:
[Journal]
# Maximum disk space to use for journals
SystemMaxUse=2G
# Maximum file size per journal file
SystemMaxFileSize=100M
# Keep journals for max 30 days
MaxRetentionSec=30day
# Rate limiting (prevent log floods)
RateLimitInterval=30s
RateLimitBurst=10000
# Store to disk (default: auto — disk if /var/log/journal/ exists)
Storage=persistent
# Compress journal files
Compress=yes
After editing, restart journald:
systemctl restart systemd-journald
journald and rsyslog Coexistence
On most distributions, both journald and rsyslog run. rsyslog reads from journald via the imjournal module and writes traditional text logs to /var/log/. You keep the benefits of both — journald's structured queries and rsyslog's text files for legacy tools.
10. Security Hardening with systemd
systemd provides a rich set of security directives that sandbox services without requiring AppArmor profiles or manual seccomp filters. These are among the most underused features in production environments.
Filesystem Isolation
[Service]
# Mount / as read-only except for /proc, /dev, etc.
ProtectSystem=strict
# Block access to /home, /root, /run/user
ProtectHome=yes
# Give service a private /tmp (not shared with host)
PrivateTmp=yes
# Allow writing only to specific paths (requires ProtectSystem=strict)
ReadWritePaths=/opt/myapp/data /var/log/myapp
# Read-only access to specific paths
ReadOnlyPaths=/etc/myapp
Privilege Isolation
[Service]
# No new privileges via setuid/setgid (crucial for security)
NoNewPrivileges=yes
# No access to hardware devices (/dev/)
PrivateDevices=yes
# Private network namespace (service sees only loopback)
# Use only if the service doesn't need real network
PrivateNetwork=no
# Prevent writing to kernel tunables
ProtectKernelTunables=yes
# Prevent loading kernel modules
ProtectKernelModules=yes
# Protect kernel log
ProtectKernelLogs=yes
# Read-only access to control groups
ProtectControlGroups=yes
Capability Dropping
[Service]
# Remove ALL capabilities
CapabilityBoundingSet=
# Or keep only what's needed (example: bind to port < 1024)
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
# Ambient capabilities (grant to unprivileged user)
AmbientCapabilities=CAP_NET_BIND_SERVICE
System Call Filtering
[Service]
# Whitelist a predefined set of safe syscalls for typical services
SystemCallFilter=@system-service
# Add specific syscalls on top
SystemCallFilter=@system-service add_key
# Block specific dangerous syscalls
SystemCallFilter=~@privileged @resources
Common syscall groups: @system-service, @network-io, @file-system, @io-event, @process, @privileged, @resources
Security Audit Tool
systemd can score your service's security configuration:
systemd-analyze security nginx
This gives you a score from 0 (UNSAFE) to 10 (SAFE) and a detailed breakdown of what's missing. Run it on your production services — the results are often eye-opening.
11. Debugging and Troubleshooting
When a service fails to start, systemd gives you everything you need to diagnose the problem — if you know where to look.
The Debugging Workflow
# Step 1: Check service status
systemctl status myapp
# Step 2: View full logs with context
journalctl -xe -u myapp
# Step 3: Check for syntax errors in unit file
systemd-analyze verify /etc/systemd/system/myapp.service
# Step 4: Try running the ExecStart command manually as the service user
sudo -u myapp /opt/myapp/bin/server
Boot Performance Analysis
# Overall boot time breakdown
systemd-analyze
# Which services took longest to start
systemd-analyze blame
# Critical path through the boot sequence
systemd-analyze critical-chain
# Critical path for a specific service
systemd-analyze critical-chain nginx.service
Common Errors and Solutions
| Error Message | Cause | Solution |
|---|---|---|
status=203/EXEC | ExecStart binary not found or not executable | Check path, permissions, and architecture of the binary |
Unit is masked | Service was masked with systemctl mask | Run systemctl unmask servicename |
Start request repeated too quickly | Hit StartLimitBurst | Run systemctl reset-failed myapp, then start again |
Dependency failed | A required dependency failed to start | Check journalctl -xe for the dependency's failure |
Main process exited, code=killed, signal=KILL | OOM killer or watchdog killed the process | Check memory limits (MemoryMax) and watchdog timeout |
Failed to set up mount namespace | PrivateTmp/ProtectSystem with older kernel | Ensure kernel 4.x+ or disable the namespace feature |
Permission denied on socket/file | Process user lacks permission | Check User= directive and file ownership in the service directory |
Real-Time Debugging
# Watch journal in real-time while testing
journalctl -f &
# Now try starting the service
systemctl start myapp
# Or: test the binary directly
sudo -u serviceuser /path/to/binary --args
# Check open files and sockets
ss -tlnp | grep :3000
lsof -p $(systemctl show myapp --property=MainPID --value)
Quick Reference Cheat Sheets
systemctl Commands
| Command | Description |
|---|---|
systemctl start svc | Start service now |
systemctl stop svc | Stop service now |
systemctl restart svc | Stop + start |
systemctl reload svc | Reload config (no downtime) |
systemctl reload-or-restart svc | Reload if supported, else restart |
systemctl enable svc | Enable on boot |
systemctl disable svc | Disable on boot |
systemctl enable --now svc | Enable on boot + start now |
systemctl mask svc | Block service completely |
systemctl unmask svc | Unblock service |
systemctl status svc | Status + recent logs |
systemctl is-active svc | Check if running |
systemctl is-enabled svc | Check if enabled on boot |
systemctl is-failed svc | Check if in failed state |
systemctl cat svc | View unit file |
systemctl edit svc | Create drop-in override |
systemctl daemon-reload | Reload unit files (after editing) |
systemctl --failed | List failed services |
systemctl reset-failed svc | Clear failed state |
systemctl list-units | List loaded units |
systemctl list-unit-files | List installed unit files |
systemctl list-timers | List timers and next run time |
systemctl list-dependencies svc | Show dependencies |
journalctl Commands
| Command | Description |
|---|---|
journalctl -u svc | Logs for a service |
journalctl -u svc -f | Follow service logs |
journalctl -u svc -n 100 | Last 100 lines |
journalctl -u svc -p err | Errors only |
journalctl -u svc --since "1h ago" | Last hour |
journalctl -u svc -b | Current boot only |
journalctl -u svc -b -1 | Previous boot |
journalctl -xe | Latest errors with context |
journalctl -f | Follow all logs |
journalctl -k | Kernel messages only |
journalctl --disk-usage | Journal disk usage |
journalctl --vacuum-size=500M | Clean journals to 500MB |
journalctl --vacuum-time=7d | Remove logs older than 7 days |
journalctl -o json | JSON output |
journalctl --list-boots | List available boots |
Frequently Asked Questions
Should I edit files in /usr/lib/systemd/system/ directly?
No. Package updates overwrite that directory. Put custom units in /etc/systemd/system/, or use systemctl edit servicename to create a drop-in override for a package-provided unit without touching the original file.
What is the difference between Requires and Wants?
Requires is a hard dependency — if the required unit fails, your unit fails too. Wants is a soft dependency — systemd tries to start the wanted unit but does not fail your service if it cannot. Most unit files should use Wants unless the dependency is truly mandatory.
Do systemd timers replace cron entirely?
For most scheduled tasks, yes — timers integrate with journald logging, support Persistent=true to catch up on missed runs after downtime, and inherit full cgroup resource limits. Cron is still simpler for a quick one-off script on a system where you do not want to write two files (a .timer and a .service) for one job.
How do I debug a service that fails immediately after systemctl start?
Run journalctl -xe -u servicename for the full error context, then systemd-analyze verify /etc/systemd/system/servicename.service to catch unit file syntax errors. If both look clean, try running the ExecStart command manually as the service's configured user to see the raw error output.
Panelica and systemd
Panelica runs 20+ managed services — nginx, Apache, PHP-FPM (multiple versions), PostgreSQL, MySQL, Redis, BIND, Postfix, Dovecot, ProFTPD, ClamAV, Fail2ban, Prometheus, Grafana, and more — all as isolated systemd services.
Every service gets its own systemd unit in /etc/systemd/system/, its own cgroup slice for resource enforcement, and its own log stream in journald. When you click "Restart" on a service in the Panelica dashboard, you're triggering a systemctl restart. When you view service logs in real time, you're reading from journald.
Understanding systemd means understanding what's happening when Panelica manages your infrastructure — and being able to diagnose issues that go deeper than the panel UI.
Conclusion
systemd is a large surface area, but the core you'll use day-to-day is manageable: service unit files, the systemctl control interface, timers for scheduled tasks, and journalctl for log analysis. Layer in the security hardening directives and you've covered 95% of what matters in production.
The investment pays off quickly. Consistent service management across every server, logs that are actually queryable, scheduled tasks with real visibility, and security sandboxing that doesn't require a separate tool — all built into the OS you're already running.
Spend an hour with systemd-analyze security on your most important services. The results will tell you exactly where to focus next.