Build a Reliable systemd Service Unit with Type=exec
You will create a service unit, validate it without starting anything, enable it for boot, and verify its process, logs and restart behaviour. Allow 15 to 20 minutes. You need a program that can run continuously, its absolute path, and administrative access for files under /etc/systemd/system and for systemctl operations.
The route
Jump straight to the step you need, or tick off Done means at the end.
The examples target systemd 255, installed here as package version 255.4-1ubuntu8.17. Replace example-worker and /usr/local/bin/example-worker with real values. The sample assumes the program stays in the foreground and accepts no special arguments.
1. Confirm the executable before writing the unit
Run these checks as your ordinary user. Do not use sudo yet. A service can fail simply because its path is wrong, the file is not executable, or it depends on a working directory that does not exist.
$ test -x /usr/local/bin/example-worker && echo executable
executable
$ /usr/local/bin/example-worker --version
example-worker 1.2.3
If the first command prints nothing, fix the installation or use the correct absolute path. systemd does not search your interactive shell's aliases or functions, and relying on a user-specific PATH makes a unit harder to audit.
2. Write a foreground service unit
Create /etc/systemd/system/example-worker.service with elevated privileges. This is a state-changing step, so check the unit name and command before pressing Enter:
[Unit]
Description=Example worker
After=network-online.target
Wants=network-online.target
[Service]
Type=exec
ExecStart=/usr/local/bin/example-worker
Restart=on-failure
RestartSec=5s
TimeoutStartSec=30s
TimeoutStopSec=30s
[Install]
WantedBy=multi-user.target
Type=exec is a useful default for a normal long-running program on systemd 255: systemd reports start failure if it cannot execute the binary or apply basic process setup. That is different from Type=simple, which can report success before the binary has been successfully executed. Keep the program in the foreground. A daemon that forks itself needs a different design, and Type=forking is discouraged where notify, notify-reload or a foreground process is possible.
Restart=on-failure covers an unexpected non-zero exit, signal termination and relevant timeouts, but does not restart the service after an intentional systemctl stop. The five-second delay helps prevent a tight restart loop. Do not add Restart=always without deciding how a deliberate clean exit should be handled.
3. Validate before starting
Ask systemd to parse the file. This command does not start the service:
$ sudo systemd-analyze verify /etc/systemd/system/example-worker.service
$
No output and exit status 0 means the unit passed this syntax and dependency check. Warnings are still worth reading. A common trap is using a relative executable path, adding shell syntax such as pipes to ExecStart, or putting multiple commands on one line. ExecStart is not run by a shell. If you need a shell, name it explicitly and understand the extra quoting and error handling.
Checkpoint: if verification reports an error, stop here. Edit the unit, run the same command again, and only continue after the error is gone.
4. Load, start and inspect the service
Reload the manager, then enable and start the unit. These commands change boot configuration and start a process, so inspect the name carefully:
$ sudo systemctl daemon-reload
$ sudo systemctl enable --now example-worker.service
Created symlink /etc/systemd/system/multi-user.target.wants/example-worker.service -> /etc/systemd/system/example-worker.service
$ systemctl is-enabled example-worker.service
enabled
$ systemctl is-active example-worker.service
active
The exact symlink message can vary, and an already-enabled unit may produce no creation message. The two final words are the useful checks. For fuller evidence, inspect the unit and recent journal entries:
$ systemctl status --no-pager example-worker.service
$ journalctl -u example-worker.service -n 30 --no-pager
Read the status output for the main PID, the loaded unit path and the last result. If it is not active, run systemctl status and journalctl before changing anything. The journal usually distinguishes an executable error, a permissions problem and an application-level failure.
5. Test failure and recovery safely
Do not test recovery by killing an important production process. Use a disposable test host or schedule a maintenance window. A controlled stop is also a useful undo path:
$ sudo systemctl stop example-worker.service
$ systemctl is-active example-worker.service
inactive
$ sudo systemctl start example-worker.service
$ systemctl is-active example-worker.service
active
A stop does not remove the unit or its boot enablement. To undo the installation completely, stop it first, disable it, then remove the unit file and reload systemd:
$ sudo systemctl disable --now example-worker.service
$ sudo rm /etc/systemd/system/example-worker.service
$ sudo systemctl daemon-reload
That last sequence is destructive to this unit file. Keep a copy in version control or make a backup before editing a working service. Removing the unit does not remove the program, its data or its journal entries.
6. Know when the simple pattern is insufficient
Use Type=oneshot for a task that should run and finish, normally with RemainAfterExit=yes if it should remain logically active afterwards. It is not a general replacement for a daemon. Use Type=notify only when the program sends systemd a READY=1 notification, because systemd waits for that signal before considering startup complete. Use Type=notify-reload when the program implements the matching reload protocol and should receive SIGHUP for reloads.
Do not use ExecStartPre for a long-running helper. systemd kills processes left by that preparatory command before the main service starts. Use it for short checks or setup, and use ExecStopPost for cleanup that must also run after a failed startup. If you add ExecStop, make it wait for the program to finish; merely sending an asynchronous signal can still lead to immediate service cleanup.
Done means
- The executable path was checked as the intended service user and is absolute.
systemd-analyze verifypasses before activation.- The unit uses a service type that matches the program's actual startup behaviour.
systemctl is-enabledreportsenabledandsystemctl is-activereportsactive.- Status and journal output have been checked, and the stop, start and full removal paths are understood.