Authoring systemd Units: Custom Services, Timers, and Socket Activation

Once you can read service units, the next step is creating your own. systemd gives you reliable execution, restart policies, and scheduling tools that are usually cleaner than ad hoc shell wrappers.

Table of Contents

This post walks through custom units, timers, socket activation, and validation workflows.

Custom Services

Timers and Activation

Series Navigation

Create a custom service unit

Start with a simple service that runs a script as a dedicated user.

# /etc/systemd/system/hello-app.service
[Unit]
Description=Hello app background worker
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=app
Group=app
WorkingDirectory=/opt/hello-app
ExecStart=/opt/hello-app/bin/worker.sh
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target

This unit starts after network readiness and automatically retries on failure.

Install and start the unit

After adding the file, reload metadata and activate it.

sudo systemctl daemon-reload
sudo systemctl enable --now hello-app.service
systemctl status hello-app.service

If configuration changes later, run daemon-reload again before restart or reload actions.

Validate and troubleshoot

Validate syntax and inspect logs before promoting changes.

sudo systemd-analyze verify /etc/systemd/system/hello-app.service
journalctl -u hello-app.service -b -n 100

Quick troubleshooting checklist:

  • Confirm executable paths and permissions for ExecStart
  • Confirm user and group exist on the host
  • Confirm required network or filesystem dependencies are declared

Replace cron-style jobs with timers

systemd timers can replace many cron jobs and provide stronger unit state visibility.

Service file:

# /etc/systemd/system/db-backup.service
[Unit]
Description=Run database backup

[Service]
Type=oneshot
ExecStart=/usr/local/bin/db-backup.sh

Timer file:

# /etc/systemd/system/db-backup.timer
[Unit]
Description=Schedule database backup

[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true

[Install]
WantedBy=timers.target

Enable timer:

sudo systemctl daemon-reload
sudo systemctl enable --now db-backup.timer
systemctl list-timers --all

Persistent=true runs missed executions after downtime, which is often better than basic cron behavior.

Understand socket activation basics

Socket activation allows systemd to listen on a socket and start the backing service only when traffic arrives.

Socket unit:

# /etc/systemd/system/demo.socket
[Unit]
Description=Demo socket listener

[Socket]
ListenStream=127.0.0.1:9090

[Install]
WantedBy=sockets.target

Service unit:

# /etc/systemd/system/demo.service
[Unit]
Description=Demo on-demand service

[Service]
ExecStart=/usr/local/bin/demo-server

Benefits:

  • Lower idle resource usage for infrequent workloads
  • Faster perceived startup because listeners are ready early
  • Consistent unit visibility through systemd state

Use path units for file-triggered execution

Path units watch filesystem paths and trigger services when files appear or change.

# /etc/systemd/system/import.path
[Unit]
Description=Watch import inbox

[Path]
PathChanged=/var/lib/import/inbox

[Install]
WantedBy=multi-user.target

Path units are useful for lightweight event-driven automation without custom polling loops.

Mount and automount pointers

systemd can also manage mount lifecycle through mount and automount units, which is useful for network filesystems and delayed mount behavior.

Use this pattern when:

  • Startup should not block waiting for slow remote mounts
  • Access should trigger mount on demand
  • Mount behavior should be managed with explicit dependencies

Series navigation

You now have the tools to author and schedule custom workloads, and the next step is networking with systemd-networkd.

Next in this series

Next, we configure networking declaratively with systemd-networkd, including interface matching, static addresses, and DHCP.

systemd-networkd: Declarative Network Configuration on Linux