A systemd service unit doesn’t just start a process; it defines a process’s entire lifecycle and its relationship to the rest of the system.
Let’s say you have a simple Python script, my_app.py, that you want to run as a background service.
# my_app.py
import time
import sys
def main():
with open("/tmp/my_app.pid", "w") as f:
f.write(str(os.getpid()))
while True:
print("My app is running...")
time.sleep(5)
if __name__ == "__main__":
import os
main()
First, place this script in /opt/my_app/my_app.py.
Now, create a systemd service file for it. The standard location for custom service units is /etc/systemd/system/. Let’s call our service my_app.service.
# /etc/systemd/system/my_app.service
[Unit]
Description=My Custom Python Application
After=network.target
[Service]
User=your_user
Group=your_group
WorkingDirectory=/opt/my_app
ExecStart=/usr/bin/python3 /opt/my_app/my_app.py
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
Let’s break this down.
The [Unit] section describes the service and its dependencies.
Description is a human-readable name.
After=network.target means this service should start after the network is up. This is crucial for applications that need network connectivity.
The [Service] section defines how the service is run.
User=your_user and Group=your_group specify the unprivileged user and group the service will run as. Never run services as root unless absolutely necessary. Replace your_user and your_group with an actual user and group on your system (e.g., nobody or a dedicated service user).
WorkingDirectory=/opt/my_app sets the current directory for the executed process. This is useful if your script expects to find files relative to its location.
ExecStart=/usr/bin/python3 /opt/my_app/my_app.py is the command that systemd will execute to start your service. Make sure the path to the interpreter (/usr/bin/python3) and your script are correct.
Restart=on-failure tells systemd to automatically restart the service if it exits with a non-zero status code. Other options include always, on-success, on-abnormal, etc.
RestartSec=5 sets a 5-second delay before attempting a restart. This prevents rapid restart loops if the service fails immediately.
The [Install] section defines how the service is enabled.
WantedBy=multi-user.target means that when you enable this service, it will be linked to the multi-user.target, which is the standard runlevel for a non-graphical, multi-user system.
After creating the file, you need to tell systemd to reload its configuration:
sudo systemctl daemon-reload
Now you can start your service:
sudo systemctl start my_app.service
To check its status and see output:
sudo systemctl status my_app.service
You should see output indicating the service is active and running. You can also check the logs using journalctl:
sudo journalctl -u my_app.service -f
The -f flag will follow the logs in real-time. You should see "My app is running…" printed every 5 seconds.
To make your service start automatically on boot:
sudo systemctl enable my_app.service
This creates a symbolic link in the appropriate wants directory for the multi-user.target.
Stopping the service:
sudo systemctl stop my_app.service
Disabling the service from starting on boot:
sudo systemctl disable my_app.service
The real power of systemd services comes from their ability to manage dependencies, control execution environments, and ensure reliability through automatic restarts. It’s not just about running a script; it’s about integrating that script as a robust component of the operating system.
Most people don’t realize that you can define complex dependencies between services, not just starting after a target. For example, if your my_app.service relied on another custom service, say my_database.service, you could add Requires=my_database.service and After=my_database.service to its [Unit] section. Requires ensures that if my_database.service fails to start or stops, my_app.service will also be stopped.
The next step is to explore more advanced options like Type=forking for daemons that fork, EnvironmentFile= for external configuration, and ExecStop= for custom cleanup actions.