Table of Contents
- What is systemd?
- Understanding Systemd Unit Files
- Anatomy of a Systemd Unit File
- Types of Systemd Unit Files
- Creating and Modifying Unit Files
- Managing Services with
systemctl - Troubleshooting Unit Files
- Best Practices for Writing Unit Files
- Conclusion
- References
What is systemd?
Systemd is a system and service manager designed to initialize and manage system processes on Linux. Introduced in 2010, it replaced legacy init systems like SysVinit and Upstart, offering features such as:
- Parallel service startup: Reduces boot time by launching independent services simultaneously.
- On-demand activation: Starts services only when needed (e.g., via socket or path activation).
- Dependency management: Explicitly defines relationships between services to ensure proper ordering.
- Centralized logging: Integrates with
journaldfor unified log management.
At its core, systemd relies on unit files to describe these system resources and their behavior.
Understanding Systemd Unit Files
A unit file is a plaintext configuration file that defines a “unit”—a system resource managed by systemd. Units can represent services, sockets, timers, mount points, and more. Unit files dictate how units are started, stopped, and interact with other units.
Where Are Unit Files Stored?
Systemd looks for unit files in predefined directories, ordered by priority (highest to lowest):
| Directory | Purpose |
|---|---|
/etc/systemd/system/ | Custom/user-defined unit files (highest priority—overrides others). |
/run/systemd/system/ | Runtime-generated unit files (temporary, lost on reboot). |
/usr/lib/systemd/system/ | Default unit files (shipped with systemd or installed by packages). |
For example, a package like nginx installs its .service file in /usr/lib/systemd/system/nginx.service, but you can override it by placing a custom file in /etc/systemd/system/nginx.service.
Anatomy of a Systemd Unit File
Unit files follow a simple INI-like structure with sections (enclosed in [ ]) and directives (key-value pairs). The most common type of unit file is the .service file, so we’ll use it as a template to explore the structure.
The [Unit] Section
This section defines metadata and dependencies for the unit. It applies to all unit types (not just services).
| Directive | Purpose | Example |
|---|---|---|
Description | A human-readable description of the unit. | Description=NGINX Web Server |
Documentation | Links to documentation (URLs or man pages). | Documentation=man:nginx(8) https://nginx.org |
After | Ensures the unit starts after the specified units. | After=network.target mysql.service |
Before | Ensures the unit starts before the specified units. | Before=php-fpm.service |
Requires | Strong dependency: If the required unit fails, this unit is stopped. | Requires=network.target |
Wants | Weak dependency: Encourages the required unit to start but doesn’t fail if it doesn’t. | Wants=mysql.service |
Conflicts | Units that cannot run simultaneously with this unit. | Conflicts=apache2.service |
The [Service] Section
This section is specific to .service units and defines how the service is executed and managed.
Core Directives:
| Directive | Purpose | Example |
|---|---|---|
Type | Defines the service’s process startup type (critical for behavior). | Type=simple (default) |
ExecStart | Command to start the service (required for .service units). | ExecStart=/usr/sbin/nginx -g 'daemon off;' |
ExecStop | Command to stop the service. | ExecStop=/usr/sbin/nginx -s stop |
ExecReload | Command to reload the service configuration. | ExecReload=/usr/sbin/nginx -s reload |
Restart | When to restart the service (e.g., on failure). | Restart=on-failure |
RestartSec | Delay (in seconds) before restarting the service. | RestartSec=5 |
Advanced Directives:
| Directive | Purpose | Example |
|---|---|---|
User/Group | Run the service as a specific user/group (improves security). | User=nginx Group=nginx |
WorkingDirectory | Set the working directory for the service process. | WorkingDirectory=/var/www/html |
Environment | Define environment variables for the service. | Environment="PATH=/usr/local/bin:$PATH" |
PIDFile | Path to the PID file (required for Type=forking services). | PIDFile=/run/nginx.pid |
Service Type Values:
The Type directive determines how systemd interacts with the service process:
simple(default): The service starts immediately, andExecStartis the main process. Systemd considers the service “started” onceExecStartruns.forking: The service forks a child process, and the parent exits. Systemd waits for the parent to exit before considering the service “started” (requiresPIDFile).oneshot: The service runs a single command and exits. Useful for one-time tasks (e.g.,mkfs).dbus: The service acquires a D-Bus name. Systemd waits until the name is acquired before proceeding.notify: The service sends a signal to systemd viasd_notify()when it’s ready (e.g.,systemd-notify --ready).
The [Install] Section
This section defines how the unit is “installed” (i.e., enabled/disabled) and linked to target units (systemd’s equivalent of runlevels).
| Directive | Purpose | Example |
|---|---|---|
WantedBy | Target units that “want” this unit. When the target is activated, the unit is started. | WantedBy=multi-user.target |
RequiredBy | Target units that “require” this unit. If the target is activated, the unit must start (fails otherwise). | RequiredBy=graphical.target |
Alias | Alternative names for the unit (e.g., Alias=webserver.service). | Alias=nginx-server.service |
Example: A Basic .service File
Here’s a simplified nginx.service file illustrating the sections above:
[Unit]
Description=NGINX Web Server
Documentation=https://nginx.org/en/docs/
After=network.target remote-fs.target nss-lookup.target
[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStart=/usr/sbin/nginx -c /etc/nginx/nginx.conf
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID
User=nginx
Group=nginx
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
Types of Systemd Unit Files
Systemd supports over a dozen unit types, each tailored to manage specific resources. Here are the most common:
| Unit Type | Extension | Purpose |
|---|---|---|
| Service | .service | Manages a system service (e.g., sshd.service, mysql.service). |
| Socket | .socket | Defines a network or Unix socket. Enables socket activation (starts the service when the socket is accessed). |
| Target | .target | Groups units to simulate “runlevels” (e.g., multi-user.target = runlevel 3). |
| Timer | .timer | Schedules units to run at specific times (replaces cron for systemd services). |
| Mount | .mount | Defines a mount point (e.g., /mnt/data.mount). |
| Automount | .automount | Automatically mounts a filesystem when accessed (on-demand). |
| Path | .path | Starts a service when a file/directory is modified (e.g., monitor /var/log/). |
Example: .timer Unit for Scheduling
A .timer unit can schedule a .service to run daily at 3 AM. For example, backup.timer:
[Unit]
Description=Daily Backup Timer
[Timer]
OnCalendar=*-*-* 03:00:00 # Run daily at 3 AM
Persistent=true # Run missed jobs on startup
Unit=backup.service # The service to trigger
[Install]
WantedBy=timers.target
Pair this with backup.service (a oneshot service to run the backup script), and enable with systemctl enable --now backup.timer.
Creating and Modifying Unit Files
Let’s walk through creating a custom .service file for a simple Python web server.
Step 1: Write the Service Script
Create a script ~/myapp.py that runs a basic HTTP server:
#!/usr/bin/env python3
from http.server import HTTPServer, SimpleHTTPRequestHandler
server_address = ('', 8000) # Listen on port 8000
httpd = HTTPServer(server_address, SimpleHTTPRequestHandler)
print(f"Starting server on port 8000...")
httpd.serve_forever()
Make it executable:
chmod +x ~/myapp.py
Step 2: Create the Unit File
Create /etc/systemd/system/myapp.service (custom unit files go here):
[Unit]
Description=My Custom Python Web Server
After=network.target # Start after the network is up
[Service]
Type=simple
User=ubuntu # Run as non-root user
WorkingDirectory=/home/ubuntu # Serve files from this directory
ExecStart=/home/ubuntu/myapp.py # Path to the script
Restart=always # Restart if the service crashes
RestartSec=5 # Wait 5 seconds before restarting
[Install]
WantedBy=multi-user.target # Start when the system reaches multi-user mode
Step 3: Load and Manage the Service
Tell systemd to reload unit files:
sudo systemctl daemon-reload
Enable the service to start on boot:
sudo systemctl enable myapp.service
Start the service immediately:
sudo systemctl start myapp.service
Check its status:
systemctl status myapp.service
Overriding Unit Files
To modify a packaged unit file (e.g., nginx.service), never edit the original in /usr/lib/systemd/system/. Instead, create an override file:
sudo systemctl edit nginx.service
This opens an editor for /etc/systemd/system/nginx.service.d/override.conf, where you can override specific directives (e.g., ExecStart or User). Systemd merges this with the original unit file.
Managing Services with systemctl
The systemctl command is used to interact with systemd units. Here are the most common operations:
| Command | Purpose |
|---|---|
systemctl start <unit> | Start a unit immediately. |
systemctl stop <unit> | Stop a unit immediately. |
systemctl restart <unit> | Restart a unit. |
systemctl reload <unit> | Reload a unit’s configuration (without restarting). |
systemctl enable <unit> | Enable a unit to start on boot. |
systemctl disable <unit> | Disable a unit from starting on boot. |
systemctl status <unit> | Show detailed status of a unit (logs, PID, dependencies). |
systemctl daemon-reload | Reload systemd to detect new/modified unit files. |
systemctl list-units --type=service | List all active service units. |
systemctl list-unit-files --type=service | List all installed service unit files (and their enable status). |
Troubleshooting Unit Files
If a unit fails to start, use these tools to diagnose issues:
1. Check Unit Status
systemctl status myapp.service
This shows:
- Whether the unit is active/failed.
- The last few log lines.
- Key details like PID, ExecStart path, and restart policy.
2. Validate Unit File Syntax
Use systemd-analyze to check for errors:
systemd-analyze verify myapp.service
3. Inspect Logs with journalctl
Systemd logs all unit activity to the journal. Use journalctl to filter logs for your unit:
journalctl -u myapp.service # Show all logs for myapp.service
journalctl -u myapp.service -f # Follow real-time logs
journalctl -u myapp.service --since "10 minutes ago" # Logs from the last 10 minutes
Common Issues
ExecStartpath incorrect: Ensure the path to the executable is absolute (e.g.,/home/ubuntu/myapp.py, not~/myapp.py).- Permission denied: The service user (e.g.,
ubuntu) may lack access to files/directories. - Dependency failures: Check
After/Requiresdirectives if the service fails due to missing dependencies.
Best Practices for Writing Unit Files
To ensure reliable and secure unit files:
- Use absolute paths: Always specify full paths for
ExecStart,WorkingDirectory, etc. - Run as non-root: Use
User/Groupto drop privileges (avoidrootunless necessary). - Limit
Restartusage: PreferRestart=on-failureoveralwaysto avoid infinite restart loops. - Document thoroughly: Use
DescriptionandDocumentationto clarify the unit’s purpose. - Override, don’t edit: Never modify vendor unit files in
/usr/lib—use/etc/systemd/system/<unit>.d/override.conf. - Test with
systemd-analyze: Validate syntax before deploying.
Conclusion
Systemd unit files are the backbone of modern Linux service management. By defining how services start, stop, and interact with the system, they enable granular control over system behavior. Whether you’re creating a custom service, scheduling tasks with timers, or troubleshooting a misbehaving daemon, mastering unit files is essential for efficient Linux administration.
With the structure, examples, and best practices outlined here, you’re well-equipped to harness the power of systemd unit files.