Creating a systemd unit file is how a local service, timer, socket, path watcher, mount, slice, or target becomes a managed object that the init system can load, validate, and activate predictably. That matters when the job should survive reboots, follow dependency ordering, and show up through systemctl instead of living as an ad-hoc shell command or boot-script fragment.

A unit file is a small declarative file with a type-specific extension such as .service, .timer, .socket, .path, .mount, .slice, or .target. Current systemd load-path rules still prioritize administrator-owned system unit locations such as /etc/systemd/system ahead of vendor directories, while each unit type needs its own section like [Service], [Timer], [Socket], [Path], [Mount], or [Slice] and may also rely on a companion unit with the same basename.

The usual flow is to write the unit under the correct local directory, validate it with systemd-analyze verify, reload the manager with systemctl daemon-reload, and only then enable or start it if that unit type supports direct activation. Use /etc/systemd/system plus sudo for system units, use /~/.config/systemd/user plus systemctl –user for per-user units, and keep absolute executable paths, realistic dependencies, and the correct unit type in place before the file is loaded.

JobTypical file or filesType-specific sectionFocused guide
Run a command or daemon name.service [Service] How to create a systemd service unit
Schedule a service name.timer plus matching .service [Timer] How to create a systemd timer
Activate a service on demand from a listener name.socket plus matching service [Socket] How to create a systemd socket unit
React to file or directory changes name.path plus matching service [Path] How to create a systemd path unit
Mount a filesystem through systemd path-name.mount [Mount] How to create a systemd mount unit
Constrain resources with a cgroup slice name.slice [Slice] How to create a systemd slice unit
Group other units into a named checkpoint name.target no type-specific section beyond [Unit] and optional [Install] How to create a systemd target unit

systemd unit file creation checklist:

  1. Confirm the local system-unit search path if the host uses linked unit directories or vendor-specific packaging.
    $ systemd-analyze unit-paths | sed -n '5,11p'
    /etc/systemd/system
    /etc/systemd/system.attached
    /run/systemd/system
    /run/systemd/system.attached
    /run/systemd/generator
    /usr/local/lib/systemd/system
    /usr/lib/systemd/system

    Current systemd still checks administrator-owned unit paths before vendor directories, so /etc/systemd/system remains the normal destination for locally created system units. Per-user units belong under /~/.config/systemd/user instead.

  2. Create the new unit file under the correct local directory with the extension and section that match the job.
    [Unit]
    Description=Run a one-shot demo task
     
    [Service]
    Type=oneshot
    ExecStart=/usr/bin/printf demo-task-ran\\n
     
    [Install]
    WantedBy=multi-user.target

    This example is a minimal *.service unit because a service is the simplest working template for a first local unit. Change the filename extension and replace [Service] with the correct type-specific section when the real job is a timer, socket, path, mount, slice, or target.

    Static helper units can omit [Install], while many timer, socket, and path units rely on a companion service with the same basename unless Unit= points elsewhere.

  3. Verify that the new unit file parses cleanly before the manager tries to load it.
    $ sudo systemd-analyze verify /etc/systemd/system/demo-task.service

    No output is the ideal result. On current systemd 255 validation, this file verified successfully and systemd-analyze verify still followed referenced units while checking dependencies, so any warning should be read against the filename it mentions before assuming the new unit is wrong.

  4. Reload the systemd manager after the file is written or changed on disk.
    $ sudo systemctl daemon-reload

    Current systemctl behavior reruns generators, rereads unit files, and rebuilds the dependency tree, which is why a new local unit does not become visible to the manager until this step completes.

  5. Activate the unit with the command that matches its type instead of assuming every unit should be enabled the same way.
    $ sudo systemctl enable --now demo-task.service

    Services, timers, sockets, paths, mounts, and many targets can usually be enabled or started directly through their own unit name, while helper units without [Install] often stay static and are pulled in by another unit.

    For matching pairs, activate the object that provides the entry point: enable the *.timer, *.socket, or *.path unit rather than its helper service, and enable the member service rather than the target when building a custom target stack.

  6. Confirm that systemd now reads the unit from the expected local path.
    $ systemctl show -p FragmentPath -p LoadState -p UnitFileState demo-task.service
    FragmentPath=/etc/systemd/system/demo-task.service
    LoadState=loaded
    UnitFileState=enabled

    The success state for a locally created system unit is FragmentPath=/etc/systemd/system/… together with LoadState=loaded. UnitFileState=static is normal when the unit has no [Install] section and is meant to be pulled in by another unit.