/commands/systemd/create-systemd-service
Create a systemd service for a script or app (.service file)
- tool
- systemd
- versions
- systemd 236+
- os
- linux
To run a script or application as a systemd service, create a unit file such as /etc/systemd/system/myapp.service with a [Service] section whose ExecStart= is the absolute path of the command, run sudo systemctl daemon-reload, then sudo systemctl enable --now myapp. systemd then starts it at boot, restarts it if you set Restart=, and collects its output in the journal (journalctl -u myapp). The program must stay in the foreground; systemd does the backgrounding.
A minimal service
[Unit]
Description=My app API server
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=myapp
WorkingDirectory=/opt/myapp
Environment=PORT=8080
ExecStart=/opt/myapp/venv/bin/python /opt/myapp/server.py
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload
sudo systemctl enable --now myapp
systemctl status myapp
journalctl -u myapp -f/opt/myapp, the myapp user and the Python command stand for your own application. Create the user first (sudo useradd --system --home /opt/myapp myapp) or remove the User= line to run as root, which is rarely a good idea.
Main settings
| Setting | Section | Meaning |
|---|---|---|
| Description= | Unit | Name shown by systemctl status |
| After= / Wants= | Unit | Order and soft dependency, for example on network-online.target |
| Type=simple | Service | Default: the ExecStart process is the service and stays in the foreground |
| Type=exec | Service | Like simple, but start fails if the binary cannot be executed (systemd 240+) |
| Type=oneshot | Service | Runs to completion; combine with RemainAfterExit=yes for setup tasks |
| Type=forking | Service | For classic daemons that fork into the background; use PIDFile= |
| ExecStart= | Service | Command to run; absolute path, no shell syntax |
| User= / Group= | Service | Account the process runs as |
| WorkingDirectory= | Service | Current directory of the process |
| Environment= / EnvironmentFile= | Service | Variables, inline or from a file |
| Restart=on-failure | Service | Restart after a crash or a non-zero exit |
| WantedBy=multi-user.target | Install | Start at boot in the normal multi-user state when enabled |
Pitfalls
- Relative paths and PATH. systemd does not read your shell profile. Use absolute paths in
ExecStart=and set variables withEnvironment=. - Forgetting
daemon-reload. After creating or editing a unit, systemd still uses the old copy and printsWarning: The unit file, source configuration file or drop-ins of myapp.service changed on disk. Run 'systemctl daemon-reload' to reload units. - Script without a shebang or execute bit.
ExecStart=/opt/myapp/run.shfails withExec format errororPermission denied(status 203/EXEC). Add#!/bin/bashandchmod +x. - Restart loops. With
Restart=alwaysand a program that exits at once, systemd stops after five starts within ten seconds withStart request repeated too quickly. - Editing packaged units. Use
sudo systemctl edit nginxto create a drop-in override instead of changing files under/usr/lib/systemd/system/.
Check the unit file
systemd-analyze verify /etc/systemd/system/myapp.service reports unknown settings and missing executables before you start it. After starting, systemctl status myapp shows active (running), the main PID and the last log lines; if the start fails, journalctl -u myapp -n 50 shows the program's own error.
FAQ
Where do I put my own unit files?
System services you write go in /etc/systemd/system/. /usr/lib/systemd/system/ (or /lib/systemd/system/) belongs to packages and is overwritten by updates. User services go in ~/.config/systemd/user/ and are managed with systemctl --user.
Can ExecStart use shell syntax like pipes or &&?
No. systemd runs the command directly, without a shell, so pipes, redirections and && are passed as literal arguments. Wrap it, ExecStart=/bin/sh -c 'cmd1 && cmd2', or move the logic into a script.
Why does my service stop right after it starts?
With Type=simple systemd expects the process to keep running. A script that starts a program in the background and exits makes systemd think the service ended, and it stops the rest. Run the program in the foreground, or use Type=forking with PIDFile= for daemons that fork.