Sometimes, we need to create a custom systemd service by hand to get our application up and running. For example, imagine you’ve just downloaded a shiny new Prometheus Node Exporter in a tarball. It doesn’t include a systemd unit file—so it’s time to create one manually.
Creating a custom systemd service allows you to automate the management of your own applications or scripts as native Linux services. In this article, we’ll walk through two common scenarios: a long-running application using Type=simple, and a one-time initialization task using Type=oneshot. We’ll also briefly revisit other service types for completeness.
Custom systemd Service Example 1: Type=simple
This type is suitable for programs that run in the foreground and do not fork into the background.
Use Case
Running a simple Python HTTP server.
Service Unit: /etc/systemd/system/simple-http.service
[Unit]
Description=Simple HTTP Server
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/python3 -m http.server 8080 --directory /var/www
Restart=always
User=www-data
[Install]
WantedBy=multi-user.target
Explanation
Type=simple:systemdconsiders the service started as soon as the process is launched.ExecStart: runs the HTTP server directly.Restart=always: restarts the service automatically if it crashes.
Enable and start the service:
sudo systemctl enable --now simple-http.service
Custom systemd Service Example 2: Type=oneshot
This type is used for short-lived scripts or commands that run once and then exit.
Use Case
Running a system initialization script to configure firewall rules.
Service Unit: /etc/systemd/system/setup-firewall.service
[Unit]
Description=Configure Firewall Rules
Before=network-pre.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/setup-firewall.sh
RemainAfterExit=true
[Install]
WantedBy=multi-user.target
Explanation
Type=oneshot:systemdwaits for the command to complete before considering the service started.RemainAfterExit=true: keeps the service marked as active even after the script exits.
Why? While not strictly necessary, this setting makessystemctl status setup-firewall.servicedisplay the service as active (in a calm green color), which is a nice touch.
Enable and start the service:
sudo systemctl enable --now setup-firewall.service
Understanding the Difference: simple vs oneshot
| Feature | Type=simple | Type=oneshot |
|---|---|---|
| Runs in background? | Yes (long-running) | No (short task) |
| Considered started | When the process starts | When the command finishes |
| Restarts? | Can be restarted (if configured) | Not restarted (unless triggered by a timer) |
| Typical use | Web servers, daemons | Scripts, initialization tasks |
Other Service Types Available
To gain a complete understanding of systemd, it helps to know the other available service types.
Type=forking
Used when the service daemonizes itself (i.e., forks and runs in the background).
[Service]
Type=forking
ExecStart=/usr/sbin/mysqld
PIDFile=/var/run/mysqld.pid
PIDFileis required sosystemdknows which process to monitor, as the original one forks.
Type=notify
Used when a service needs time to initialize and must signal when it’s ready using systemd-notify.
[Service]
Type=notify
ExecStart=/usr/local/bin/complex-app
NotifyAccess=all
Type=dbus
Used for services that register on D-Bus. The service is considered started once the specified BusName is acquired.
[Service]
Type=dbus
BusName=org.freedesktop.NetworkManager
ExecStart=/usr/sbin/NetworkManager
Final Thoughts
Choosing the correct Type= for your systemd service ensures that systemd can properly manage the lifecycle of your application or script. Use simple for persistent, foreground applications, and oneshot for short-lived setup tasks. Other types exist for legacy daemons, asynchronous services, or D-Bus-integrated components.
Designing your services with the right type in mind leads to smoother system integration, easier debugging, and greater reliability.
** Related documentation can be found here.