Table of Contents
- Understanding Systemd Units
- Anatomy of a Custom Unit File
- Advanced Unit File Features
- Beyond Unit Files: Targets, Timers, and Environment
- Debugging and Best Practices
- Conclusion
- References
1. Understanding Systemd Units
At its core, systemd manages units—resources that represent system components. Units are defined by plain-text configuration files (.service, .timer, .target, etc.) and control how services start, stop, and interact with the system.
Types of Units
Systemd supports dozens of unit types, but the most common are:
.service: Manages processes (e.g.,nginx.service,ssh.service)..timer: Schedules tasks (replaces cron for systemd-aware workflows)..target: Groups units (similar to SysVinit runlevels, e.g.,multi-user.target)..socket: Enables socket activation (starts services only when a connection is received)..mount/.automount: Controls filesystem mounting.
Where Units Live
Systemd loads units from three primary directories (in order of priority, highest first):
/etc/systemd/system/: Local administrator-defined units (custom units go here)./run/systemd/system/: Runtime-generated units (temporary, e.g., fromsystemctl edit)./usr/lib/systemd/system/: Distribution-provided units (default services likesshd).
2. Anatomy of a Custom Unit File
A custom unit file (e.g., myapp.service) is a plain-text file with sections (enclosed in [ ]) and directives (key-value pairs). Let’s break down the structure with a practical example: a service to run a custom Python script.
Example: A Simple Custom Service
Suppose we have a Python script at /opt/myapp/app.py that runs a web server. We’ll create a myapp.service unit to manage it. Here’s the full file:
[Unit]
Description=My Custom Python Web App
Documentation=https://example.com/myapp/docs
After=network.target # Start only after the network is up
Requires=network.target # Fail if network.target isn't available
[Service]
Type=simple # Process runs in foreground (no forking)
User=ubuntu # Run as non-root user "ubuntu"
Group=ubuntu
WorkingDirectory=/opt/myapp # Set working directory
ExecStart=/usr/bin/python3 /opt/myapp/app.py # Command to start the service
ExecStop=/bin/kill -TERM $MAINPID # Graceful stop (systemd sets $MAINPID)
Restart=on-failure # Restart if the service exits with non-zero status
RestartSec=5s # Wait 5 seconds before restarting
Environment="LOG_LEVEL=info" # Set environment variable
[Install]
WantedBy=multi-user.target # Start when the system reaches multi-user mode
Alias=myapp # Allow starting with `systemctl start myapp` (instead of myapp.service)
Key Sections Explained
[Unit]: Metadata and Dependencies
This section defines the unit’s purpose, documentation, and relationships with other units.
Description: Human-readable name (appears insystemctl status).Documentation: URLs or man pages for further info.After=X: Start after unit X has started (ordering, not a hard dependency).Requires=X: Declare a hard dependency—if X fails, this unit fails too.Wants=X: Soft dependency—best-effort start of X, but this unit proceeds even if X fails.Conflicts=X: Stop this unit if X starts, and vice versa.
[Service]: Service Behavior
This section configures how the service runs, stops, and restarts.
-
Type: Defines how systemd manages the process:simple(default): Process runs in the foreground; systemd tracks it directly.forking: Process forks a child and exits (e.g., traditional daemons likehttpd).oneshot: Process runs once and exits (e.g., a setup script).dbus: Process acquires a D-Bus name; systemd waits for this.notify: Process sends a signal to systemd when ready (usessd_notify()).
-
ExecStart: The command to start the service (absolute paths required). -
ExecStop: Command to stop the service (optional; systemd sendsSIGTERMby default). -
ExecReload: Command to reload configuration (e.g.,nginx -s reload). -
User/Group: Run the service as a specific user/group (critical for security—avoidroot!). -
WorkingDirectory: Set the process’s working directory. -
Restart: When to restart the service:no(default): Never restart.on-failure: Restart if exit code is non-zero, signal, or timeout.always: Restart even if the service exits successfully.on-abnormal: Restart only on crashes or timeouts (not clean exits).
-
Environment/EnvironmentFile: Set environment variables (useEnvironmentFile=/path/to/envfor files).
[Install]: Activation on Boot
This section defines how the unit is “installed” (i.e., enabled to start on boot).
WantedBy=X: When enabled, create a symlink inX.wants/(e.g.,multi-user.target.wants/).RequiredBy=X: Harder dependency—symlink inX.requires/; X fails if this unit is missing.Alias: Short name for the unit (e.g.,Alias=myapplets you usesystemctl start myapp).
3. Advanced Unit File Features
Dependencies: Fine-Grained Control
Systemd’s dependency system is powerful but nuanced. Beyond Requires and Wants, use these directives for precision:
Before=X: Start before unit X (reverse ofAfter=X).BindsTo=X: If X stops, this unit stops too (stronger thanRequires).PartOf=X: If X is stopped/restarted, this unit is too (but not the reverse).
Example: A database-dependent app:
[Unit]
Description=App That Depends on PostgreSQL
After=postgresql.service
BindsTo=postgresql.service # App stops if PostgreSQL stops
Template Units: Reusable Configurations
For managing multiple instances of the same service (e.g., multiple Node.js apps), use template units with @ in the filename (e.g., [email protected]). Templates use %i to reference the instance name.
Example: [email protected] (template):
[Unit]
Description=My App Instance %i
After=network.target
[Service]
Type=simple
User=ubuntu
WorkingDirectory=/opt/myapp/%i # Instance-specific directory
ExecStart=/usr/bin/node /opt/myapp/%i/server.js
Restart=on-failure
[Install]
WantedBy=multi-user.target
To start an instance named api:
systemctl start [email protected]
Systemd replaces %i with api, so the service runs from /opt/myapp/api/.
Instantiation and Special Variables
Template units support special variables to make configurations dynamic:
%i: Instance name (from@instancein the unit name).%N: Full unit name (e.g.,[email protected]).%p: Unit prefix (e.g.,myappfrom[email protected]).%m: Machine ID (from/etc/machine-id).
Example: Use %h (home directory of the user) in a user unit:
[Service]
WorkingDirectory=%h/myapp # Equivalent to /home/ubuntu/myapp for user "ubuntu"
Socket Activation (Advanced)
Socket activation starts a service only when a connection is received (reduces idle resource usage). It requires a .socket unit and a .service unit.
Example: myapp.socket (listens on port 8080):
[Unit]
Description=Socket for My App
[Socket]
ListenStream=8080 # TCP port 8080
Accept=false # Single connection; use "true" for multiple
[Install]
WantedBy=sockets.target
myapp.service (starts on connection):
[Unit]
Description=My App (Socket-Activated)
[Service]
Type=simple
ExecStart=/opt/myapp/server.py
StandardInput=socket # Read from the socket
Enable with systemctl enable --now myapp.socket. The service starts only when port 8080 is accessed!
4. Beyond Unit Files: Targets, Timers, and Environment
Targets: System States (Runlevels 2.0)
Targets group units to define system states (e.g., “multi-user” or “graphical”). They replace traditional runlevels but are more flexible.
Common targets:
multi-user.target: Text-based multi-user mode (default for servers).graphical.target: Multi-user mode with GUI (depends onmulti-user.target).rescue.target: Single-user mode for recovery.poweroff.target: Shutdown the system.
To set the default target (e.g., boot to text mode):
sudo systemctl set-default multi-user.target
To create a custom target (e.g., myapp.target for grouping app units):
[Unit]
Description=My App Target
Requires=network.target mydb.service
After=network.target mydb.service
AllowIsolate=yes # Let users switch to this target with `systemctl isolate`
Timers: Scheduling Tasks (Cron Alternative)
Systemd timers (*.timer) schedule units to run at specific times, offering more features than cron (e.g., calendar events, monotonic time, and dependency management).
Example: A weekly backup timer (backup.timer):
[Unit]
Description=Weekly Backup Timer
[Timer]
OnCalendar=weekly # Run every Sunday at 00:00 (default)
Persistent=true # Run missed jobs when system boots (if offline during schedule)
AccuracySec=1min # Allow 1-minute delay to avoid CPU spikes
[Install]
WantedBy=timers.target # Start timer when system boots
Pair with a backup.service (the actual task):
[Unit]
Description=Weekly Backup Service
[Service]
Type=oneshot # Run once and exit
ExecStart=/opt/backup/run.sh
Enable and start the timer:
sudo systemctl enable --now backup.timer
Check pending timers with:
systemctl list-timers --all
Environment Management
Systemd lets you set environment variables globally or per-unit:
- Per-unit: Use
EnvironmentFile=/path/to/envin[Service](file format:KEY=VALUE). - Global: Edit
/etc/environment(system-wide) or~/.config/environment.d/*.conf(user-specific). - Runtime variables: Use
systemctl set-environment KEY=VALUE(persists until reboot).
5. Debugging and Best Practices
Debugging Units
Systemd provides powerful tools to troubleshoot misbehaving units:
- Reload systemd: After editing a unit file, run
sudo systemctl daemon-reload. - Check status:
systemctl status myapp.service(shows logs, PID, and recent activity). - View logs:
journalctl -u myapp.service(add-fto follow live logs,-n 50for last 50 lines). - Verify syntax:
systemd-analyze verify myapp.service(catches typos or invalid directives). - Check dependencies:
systemctl list-dependencies myapp.service(shows all required units).
Best Practices
- Use absolute paths: Always specify full paths in
ExecStart,WorkingDirectory, etc. - Run as non-root: Use
User/Groupto avoid unnecessary privileges. - Limit dependencies: Only
Require/Wantcritical units to speed up boot. - Test with
oneshot: For scripts, useType=oneshotand test withsystemctl start. - Document units: Add
DescriptionandDocumentationdirectives for clarity. - Avoid
Restart=always: Useon-failureunless you need the service to restart even on success.
6. Conclusion
Systemd’s custom unit files are the cornerstone of modern Linux system management, offering unparalleled control over services, scheduling, and system states. By mastering unit file structure, dependencies, templates, and timers, you can automate workflows, secure services, and optimize system behavior.
While systemd’s complexity can feel overwhelming, the payoff is a more robust, flexible system. Start with simple services, experiment with timers, and gradually explore advanced features like socket activation. Your future self (and your servers) will thank you.