Systemd is the backbone of most modern Linux distributions, managing services, daemons, and system resources with precision. Writing custom systemd units allows you to transform scripts into reliable, manageable services. In this guide, we’ll walk through creating custom systemd units, complete with real-world examples, to help you automate and manage your Linux tasks effectively. Whether you’re running a web server or a custom Python script, this post will make the process clear, SEO-friendly, and beginner-friendly.
What is a Systemd Unit?
Systemd units are configuration files that define how services, sockets, timers, and other resources behave. For this post, we’ll focus on service units, which manage daemons or scripts as services. These files, ending in .service
, live in /etc/systemd/system/
(for custom units) or /lib/systemd/system/
(for system-provided units).
By creating a custom systemd unit, you can:
- Start and stop scripts automatically.
- Ensure services restart on failure.
- Manage dependencies and execution order.
- Monitor and log service activity.
Let’s dive into crafting a custom systemd unit with practical examples.
Why Use Custom Systemd Units?
Custom systemd units bring structure to your scripts. Instead of manually running a Python script or server process, a systemd unit lets you:
- Automate startup: Launch services at boot.
- Handle failures: Restart services if they crash.
-
Simplify management: Use commands like
systemctl start
orsystemctl stop
. -
Improve logging: Integrate with
journalctl
for easy debugging.
This is especially useful for developers and sysadmins managing web apps, cron-like tasks, or background workers.
Step-by-Step: Writing a Custom Systemd Unit
Let’s create a systemd unit for a real-world example: a Python web server using Flask. We’ll then extend it to a more complex case, like a background worker.
Step 1: Write Your Script
First, create a simple Flask app. Save this as /home/user/myapp/app.py
:
pythonfrom flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return "Hello from my Flask app!"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080)
This script runs a web server on port 8080. Without systemd, you’d need to run it manually (python3 app.py
), and it wouldn’t restart if it crashes.
Step 2: Create the Systemd Unit File
Create a file named /etc/systemd/system/myapp.service
. Here’s a basic systemd unit for the Flask app:
ini[Unit]
Description=My Flask Web App
After=network.target
[Service]
ExecStart=/usr/bin/python3 /home/user/myapp/app.py
WorkingDirectory=/home/user/myapp
Restart=always
User=user
Environment="PYTHONUNBUFFERED=1"
[Install]
WantedBy=multi-user.target
Let’s break down the key directives:
-
[Unit] Section:
Description
: A human-readable description of the service.After
: Ensures the service starts after the network is up.
-
[Service] Section:
ExecStart
: The command to run the script (use the full path to Python).WorkingDirectory
: Sets the directory where the script runs.Restart
: Configures restart behavior (always
restarts on any failure).User
: Runs the service as a specific user for security.Environment
: Enables unbuffered output for better logging.
-
[Install] Section:
WantedBy
: Specifies when the service should start (e.g., at boot in multi-user mode).
Step 3: Enable and Start the Service
After creating the unit file, run these commands:
-
Reload systemd to recognize the new unit:
bashsudo systemctl daemon-reload
-
Enable the service to start at boot:
bashsudo systemctl enable myapp.service
-
Start the service:
bashsudo systemctl start myapp.service
-
Check the status:
bashsudo systemctl status myapp.service
You should see output confirming the service is running. Access http://your-server-ip:8080
to verify the Flask app is live.
Step 4: Monitor and Debug
Use journalctl
to view logs:
bashjournalctl -u myapp.service
This shows output from your Flask app, helping you debug issues like crashes or misconfigurations.
Real-World Example: Background Worker
Let’s try a more complex case: a Python script that processes tasks in the background. Save this as /home/user/worker/worker.py
:
pythonimport time
while True:
print("Processing task...")
time.sleep(10)
Create a systemd unit at /etc/systemd/system/worker.service
:
ini[Unit]
Description=My Background Worker
After=network.target
[Service]
ExecStart=/usr/bin/python3 /home/user/worker/worker.py
WorkingDirectory=/home/user/worker
Restart=on-failure
User=user
Environment="PYTHONUNBUFFERED=1"
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
Key differences:
-
Restart=on-failure
: Only restarts if the script exits with an error (not on normal exit). -
StandardOutput
andStandardError
: Sends output tojournalctl
for cleaner logging.
Run the same systemctl
commands to enable and start the service. Check logs with:
bashjournalctl -u worker.service -f
Advanced Tips for Systemd Units
-
Dependencies: Use
Requires
orWants
to specify other services (e.g.,Requires=postgresql.service
for a database-dependent app). -
Timeouts: Add
TimeoutStartSec=30
to limit startup time. -
Resource Limits: Use
MemoryMax=500M
orCPUQuota=50%
to restrict resource usage. -
Timers: Pair with a
.timer
unit for cron-like scheduling.
Example timer for running a script daily (/etc/systemd/system/daily-task.timer
):
ini[Unit]
Description=Run daily task
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
Pair it with a service unit to execute the task.
Common Pitfalls and Fixes
-
Permission Issues: Ensure the
User
has access to the script and working directory. -
Path Errors: Always use absolute paths in
ExecStart
andWorkingDirectory
. -
Logging: If logs aren’t showing, check
StandardOutput
or addEnvironment=PYTHONUNBUFFERED=1
. -
Service Crashes: Use
Restart=always
orRestart=on-failure
to recover automatically.
Conclusion
Writing custom systemd units is a game-changer for Linux automation. By turning scripts into services, you gain control, reliability, and seamless integration with systemd’s ecosystem. Whether it’s a Flask web app or a background worker, the process is straightforward: write your script, create a .service
file, and manage it with systemctl
.
Album of the day: