Table of Contents
- Understanding Systemd Unit Files
- Types of Unit Files
- Anatomy of a Unit File
- Key Directives and Sections
- Crafting Your First Unit File: A Practical Example
- Advanced Configuration Techniques
- Debugging and Validation
- Best Practices
- Conclusion
- References
1. Understanding Systemd Unit Files
A unit file is a plaintext configuration file that tells systemd how to manage a “unit”—a resource systemd is responsible for. Units can be services, sockets, timers, mounts, or even targets (groups of units). Unit files dictate what a unit does, when it runs, and how it interacts with other units.
Where Are Unit Files Stored?
Systemd looks for unit files in three primary directories (processed in this order, with later directories overriding earlier ones):
/usr/lib/systemd/system/: Default units provided by the OS or installed packages (e.g.,sshd.service,nginx.service). Do not edit these directly—package updates may overwrite them./etc/systemd/system/: Custom units created by the system administrator. This is where you should store your own configurations./run/systemd/system/: Runtime-generated units (temporary, lost on reboot).
2. Types of Unit Files
Systemd supports dozens of unit types, each with a specific purpose. Here are the most common:
| Extension | Purpose | Example Use Case |
|---|---|---|
.service | Manages a daemon or application process (most common type). | nginx.service, mysql.service |
.socket | Controls network or IPC sockets; enables “socket activation” (start service only when a connection arrives). | sshd.socket, docker.socket |
.timer | Schedules jobs (replaces cron for systemd-aware tasks). | backup.timer (triggers backup.service daily). |
.mount | Defines mount points (replaces /etc/fstab entries for systemd). | mnt-data.mount (mounts /dev/sdb1 to /mnt/data). |
.target | Groups units to define system states (e.g., “multi-user” or “graphical” mode). | multi-user.target, graphical.target |
.path | Monitors file/directory changes and triggers actions. | watch-config.path (restarts a service when configs change). |
3. Anatomy of a Unit File
Unit files are structured into sections, each marked by [SectionName], and contain directives (key-value pairs) that configure behavior. For service units (.service), the most critical sections are:
[Unit]: Metadata and dependencies (e.g., “start after networking”).[Service]: Execution details (e.g., how to start/stop the service, user to run as).[Install]: Installation targets (e.g., “enable this service when booting into multi-user mode”).
4. Key Directives and Sections
Let’s dissect each section and its most important directives.
The [Unit] Section
This section defines metadata and inter-unit relationships.
| Directive | Purpose | Example |
|---|---|---|
Description | Human-readable name for the unit. | Description=My Custom Web Server |
Documentation | Links to docs (man pages, URLs). | Documentation=man:myservice(1) https://example.com/docs |
After | Start after these units finish starting. (Soft dependency) | After=network.target mysql.service |
Before | Start before these units. | Before=nginx.service |
Requires | Hard dependency: If this unit fails, the dependent unit is stopped. | Requires=network.target (service needs network). |
Wants | Soft dependency: Encourage starting the dependent unit, but don’t fail if it’s missing. | Wants=logging.service (optional logging). |
Conflicts | Units that cannot run simultaneously. | Conflicts=apache2.service (nginx vs apache). |
The [Service] Section
This section is unique to .service units and controls how the service runs.
Service Type
The Type directive defines how systemd interacts with the service process. Choose carefully:
simple(default): Service runs in the foreground. Systemd considers it started immediately afterExecStart.- Use for apps that don’t fork (e.g.,
node app.js).
- Use for apps that don’t fork (e.g.,
forking: Service forks a child process and exits. Systemd waits for the parent to exit.- Use for traditional daemons (e.g.,
sshd).
- Use for traditional daemons (e.g.,
oneshot: Runs once and exits. Systemd waits for it to finish (use withRemainAfterExit=yesto keep it “active”).- Use for scripts (e.g.,
backup.service).
- Use for scripts (e.g.,
notify: Service sends a signal to systemd when ready (viasd_notify()).- Use for apps that need to report readiness (e.g.,
systemd-notify --ready).
- Use for apps that need to report readiness (e.g.,
dbus: Service acquires a D-Bus name; systemd waits for this.- Use for D-Bus services (e.g.,
org.freedesktop.NetworkManager).
- Use for D-Bus services (e.g.,
Execution Directives
These define how to start, stop, or reload the service:
ExecStart=/path/to/command [args]: Primary command to start the service.- Example:
ExecStart=/usr/bin/python3 /opt/myapp/app.py
- Example:
ExecStartPre=/path/to/script: Command to run beforeExecStart.- Example:
ExecStartPre=/bin/mkdir -p /var/log/myapp
- Example:
ExecStartPost=/path/to/script: Command to run afterExecStart.ExecReload=/path/to/script: Command to reload configuration (e.g.,nginx -s reload).ExecStop=/path/to/script: Command to stop the service.ExecStopPost=/path/to/script: Command to run after the service stops (e.g., cleanups).
Lifecycle and Restart
Restart: When to restart the service if it exits. Common values:no(default): Never restart.on-success: Restart only if it exits with 0 (success).on-failure: Restart if it exits with non-zero (error), signal, or timeout.always: Restart regardless of exit status (use for critical services).
RestartSec=seconds: Delay before restarting (default: 100ms).- Example:
RestartSec=5(wait 5s before restarting).
- Example:
User, Group, and Environment
User=username: Run the service as this user (never run asrootunless necessary!).Group=groupname: Run as this group.WorkingDirectory=/path: Set the working directory for the service.Environment=KEY=VALUE: Set environment variables (single line).- Example:
Environment=PORT=8080 DB_PATH=/data/db
- Example:
EnvironmentFile=/path/to/file: Load environment variables from a file (one per line:KEY=VALUE).
Logging and Output
StandardOutput=file:/var/log/myapp.log: Redirect stdout to a file.StandardError=journal: Send stderr to the systemd journal (view withjournalctl).
The [Install] Section
This section defines how the unit is “installed” (enabled) with systemctl enable.
| Directive | Purpose | Example |
|---|---|---|
WantedBy=target | Enable the service when target is activated (soft dependency). | WantedBy=multi-user.target (boot into CLI mode). |
RequiredBy=target | Enable the service and require it for target to start (hard dependency). | RequiredBy=graphical.target |
Alias=name.service | Alternative names for the unit (e.g., myapp.service → webapp.service). | Alias=webapp.service |
5. Crafting Your First Unit File: A Practical Example
Let’s create a unit file for a simple Python web app. We’ll assume:
- The app is a Flask script at
/opt/myapp/app.py. - It runs on port 5000 and requires Python 3.
Step 1: Create the Unit File
Create /etc/systemd/system/myapp.service with the following content:
[Unit]
Description=My Flask Web Application
Documentation=https://example.com/myapp-docs
After=network.target # Start after networking is ready
[Service]
Type=simple # App runs in foreground (no forking)
User=appuser # Dedicated user for security
Group=appuser # Dedicated group
WorkingDirectory=/opt/myapp # Where the app lives
Environment="FLASK_APP=app.py" "FLASK_ENV=production" # Env vars
ExecStart=/usr/bin/python3 -m flask run --host=0.0.0.0 --port=5000 # Start command
Restart=on-failure # Restart on errors
RestartSec=5 # Wait 5s before restarting
StandardOutput=journal # Log stdout to journal
StandardError=journal+console # Log stderr to journal and console
[Install]
WantedBy=multi-user.target # Enable when booting to multi-user mode
Step 2: Enable and Start the Service
# Reload systemd to detect the new unit file
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 status
sudo systemctl status myapp.service
Step 3: Verify and Debug
- Check logs:
journalctl -u myapp.service -f(-ffollows live logs). - Restart the service:
sudo systemctl restart myapp.service. - Disable on boot:
sudo systemctl disable myapp.service.
6. Advanced Configuration Techniques
Template Unit Files
For services that need multiple instances (e.g., multiple websites), use template units with @ in the filename (e.g., [email protected]). The %i placeholder replaces the instance name.
Example template (/etc/systemd/system/[email protected]):
[Unit]
Description=App Instance %i
[Service]
Type=simple
User=appuser
WorkingDirectory=/opt/apps/%i # %i = instance name (e.g., "site1")
ExecStart=/usr/bin/python3 app.py --port=%i # Port from instance name
Restart=on-failure
[Install]
WantedBy=multi-user.target
Start an instance for port 8080:
sudo systemctl start [email protected]
Dependency Management with Targets
Targets group units to define system states. Use WantedBy=multi-user.target (CLI) or graphical.target (GUI) for most services. To create custom targets, define a .target file and use Requires/Wants to include units.
Security Hardening
Lock down services with these directives in [Service]:
PrivateTmp=yes: Isolate /tmp for the service (prevents tampering with global /tmp).ProtectSystem=full: Make/usrread-only; only/varis writable.ReadWritePaths=/opt/myapp/data: Allow writes only to specific paths.NoNewPrivileges=yes: Prevent the service from gaining new privileges (e.g., viasetuid).CapabilityBoundingSet=CAP_NET_BIND_SERVICE: Limit Linux capabilities (e.g., only allow binding to low ports).
7. Debugging and Validation
Even small mistakes (e.g., typos in ExecStart) can break a unit file. Use these tools:
- Validate syntax:
sudo systemd-analyze verify myapp.service. - Check status:
sudo systemctl status myapp.service(shows errors like “failed to start”). - View logs:
journalctl -u myapp.service --since "5 minutes ago". - Check dependencies:
systemctl list-dependencies myapp.service. - Profile startup:
systemd-analyze blame(shows which units slow down boot).
8. Best Practices
- Avoid root: Run services as non-root users with minimal privileges.
- Use
/etc/systemd/system/: Store custom units here (never edit/usr/lib/). - Prefer
WantsoverRequires: Soft dependencies make services more resilient. - Document: Add
DescriptionandDocumentationdirectives for clarity. - Test restart behavior: Ensure
Restart=on-failureworks as expected for crashes. - Harden security: Use
PrivateTmp,ProtectSystem, andNoNewPrivileges.
9. Conclusion
Systemd unit files are the backbone of service management in modern Linux. By mastering their structure, directives, and best practices, you can create reliable, secure, and maintainable services. Start with simple .service files, experiment with advanced features like templates and timers, and always validate and test your configurations.