Where unit files live
Three directories, priority bottom to top (lower wins):
/usr/lib/systemd/system/ ← from packages (canonical, /lib/systemd/system - symlink)
/run/systemd/system/ ← runtime, generated (systemd-fstab-generator and others)
/etc/systemd/system/ ← local edits by the SysAdmin, overrides
If a file with the same name exists in /etc/ and in /usr/lib/, the
/etc/ one wins entirely. For a partial edit, use a drop-in (see systemd-drop-ins).
Full list of types
| Extension | Who writes it | Purpose |
|---|---|---|
.service | SysAdmin | Ordinary daemon (sshd, nginx, postgres) |
.socket | SysAdmin | Listen on a socket, activate a service on demand |
.timer | SysAdmin | Cron replacement (see systemd-timers) |
.mount | SysAdmin / auto | Mount point (generated from mount-and-fstab) |
.automount | SysAdmin | Lazy mount: mounts on first access |
.swap | SysAdmin / auto | Swap device or file |
.target | SysAdmin / dist | Group of units (see systemd-targets) |
.path | SysAdmin | Trigger a service on a file/directory change |
.slice | SysAdmin / auto | A node in the cgroups hierarchy for limits |
.scope | systemd | Externally created process group (sessions) |
.device | udev → systemd | A /dev/* appeared, automatic, usually no file |
.service: the most common
Manages a daemon. The key directives are Type= and ExecStart=.
# /etc/systemd/system/myapp.service
[Unit]
Description=My App
After=network-online.target postgresql.service
Requires=postgresql.service
[Service]
Type=simple
ExecStart=/usr/local/bin/myapp
Restart=on-failure
User=myapp
[Install]
WantedBy=multi-user.target
Type=simple is the most common. The binary does not fork. For the alternatives, see systemd.
.socket: socket activation
The core idea: systemd listens on the socket INSTEAD of the service. On the first
connection it starts the paired .service and hands it the already open fd.
# /etc/systemd/system/myapp.socket
[Unit]
Description=My App socket
[Socket]
ListenStream=8080
Accept=no # one service for all connections (the default)
[Install]
WantedBy=sockets.target
Why:
- Lazy start means the service uses no RAM until someone connects.
- Parallel boot lets clients write to the socket BEFORE the service has actually started; systemd buffers it.
- Zero-downtime restart means a service restart does not drop connections.
A real-world example: cups.socket, pcscd.socket, docker.socket.
.timer: a schedule
See systemd-timers. The paired .service runs on the schedule.
.mount: a mount point
The file name is the mount point with / replaced by -. /var/data → var-data.mount.
# /etc/systemd/system/var-data.mount
[Unit]
Description=Data partition
[Mount]
What=/dev/disk/by-uuid/abc-123
Where=/var/data
Type=ext4
Options=defaults,noatime
[Install]
WantedBy=multi-user.target
In practice you rarely write .mount units by hand. systemd-fstab-generator
parses mount-and-fstab and creates them in /run/systemd/system/. Your own
unit is needed when you require specific Requires=/After=.
.automount: mount on demand
An .automount paired with a .mount. The filesystem itself is not mounted at boot,
only on the first access to the directory. For rarely used NFS or removable disks.
# /etc/systemd/system/mnt-nfs.automount
[Unit]
Description=NFS automount
[Automount]
Where=/mnt/nfs
TimeoutIdleSec=600 # unmount after 10 min idle
[Install]
WantedBy=multi-user.target
.path: react to a file
Triggers the paired service when a file appears or changes. Inotify under the hood.
# /etc/systemd/system/upload-watcher.path
[Path]
PathExistsGlob=/var/uploads/*.zip
Unit=process-upload.service
[Install]
WantedBy=multi-user.target
This works only if the paired service is Type=oneshot (or simply short-lived).
.slice and .scope: cgroup nodes
.sliceis a hierarchical container that groups services under shared limits. The system defaults:system.slice(daemons),user.slice(users),machine.slice(VMs and containers)..scopeis created programmatically by systemd for externally launched process groups: SSH sessions (session-1.scope),systemd-run, containers from Docker or Podman.
You write your own slice to pin a limit on a group of services:
# /etc/systemd/system/heavy.slice
[Slice]
CPUQuota=200%
MemoryMax=4G
And in the services: Slice=heavy.slice. See cgroups.
Debug: what is on the system
systemctl list-unit-files # ALL installed units
systemctl list-unit-files --type=service # services only
systemctl list-units --type=socket # active sockets
systemctl list-units --type=mount --all # all mount units, including stopped ones
systemctl cat docker.socket # the full contents of a unit
systemctl show -p TriggeredBy nginx.service # what triggers this service
States in the STATE column:
enabledmeans there is a symlink in*.target.wants/(starts at boot)disabledmeans there is no symlinkstaticmeans there is no [Install] section, you cannot enable it, it is pulled in only through dependenciesmaskedmeans/etc/systemd/system/<unit>→/dev/null(cannot be started at all)