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.

Want structured learning?

Take the full Systemd course →