How to create a systemd target unit

A custom systemd target lets you name a reusable operating state and start several units as one coordinated checkpoint. That is useful when an application stack, maintenance bundle, or local workflow should come up together instead of being started service by service by hand.

In systemd, a target unit does not run commands itself. Current upstream systemd.target documentation states that target units only use the generic [Unit] and [Install] sections, while current systemd.unit install rules use WantedBy= to create a *.wants/ symlink from the target to each member unit when that member is enabled. Administrator-created system units still belong under /etc/systemd/system, which current load-path rules search before /usr/local/lib/systemd/system and /usr/lib/systemd/system.

The example below creates demo-stack.target and attaches a one-shot service that writes a line to /var/log/demo-stack.log so the target has an obvious proof of effect. Root access is required for /etc/systemd/system and /usr/local/bin, target unit files must not be empty, and AllowIsolate= is intentionally omitted because this guide is about defining a custom target, not switching the live system into it with systemctl isolate.

Steps to create a systemd target unit:

  1. Create the helper script that a member service will run when the target is reached.
    #!/usr/bin/env bash
    printf 'demo-stack target reached %s\n' "$(date --iso-8601=seconds)" >> /var/log/demo-stack.log

    A target unit only coordinates other units. It does not have an ExecStart= of its own, so a member service, socket, mount, or similar unit must do the real work.

  2. Make the helper script executable.
    $ sudo chmod 0755 /usr/local/bin/demo-stack-ready.sh

    Use sudo install -m 0755 source-file /usr/local/bin/demo-stack-ready.sh if you want to copy the script into place and set the mode in one command.

  3. Create the member service that should be pulled in by the target.
    [Unit]
    Description=Demo stack member service for demo-stack.target
     
    [Service]
    Type=oneshot
    ExecStart=/usr/local/bin/demo-stack-ready.sh
    RemainAfterExit=yes
     
    [Install]
    WantedBy=demo-stack.target

    Current upstream install rules state that WantedBy=demo-stack.target causes systemctl enable demo-stack-ready.service to create a symlink under /etc/systemd/system/demo-stack.target.wants/, which adds a Wants= dependency from the target to this service.

    Type=oneshot fits a short action that exits after success, and RemainAfterExit=yes keeps the unit in active (exited) state so the target membership is easier to inspect afterward.

  4. Create the target unit file itself.
    [Unit]
    Description=Demo application stack target
    Documentation=man:systemd.target(5)
     
    [Install]
    WantedBy=multi-user.target

    Current upstream systemd.target rules say target units use only [Unit] and [Install] sections. There is no [Target] section and no target-specific option set.

    The file must not be empty or systemd will treat it like a masked unit. A simple Description= and optional Documentation= entry are the recommended minimum.

    WantedBy=multi-user.target makes this custom target part of a normal non-graphical boot when it is enabled. If the target should be manual only, omit the [Install] section and start it explicitly instead.

  5. Verify both unit files before reloading the manager.
    $ sudo systemd-analyze verify /etc/systemd/system/demo-stack.target /etc/systemd/system/demo-stack-ready.service

    No output is the ideal result. On the current Ubuntu 24.04.4 validation host, this command also printed an unrelated warning from prltoolsd.service, which is a reminder that systemd-analyze verify can report issues from other loaded units as well as the new files you specify.

  6. Reload the systemd manager so the new unit names and install metadata are visible from the live dependency tree.
    $ sudo systemctl daemon-reload

    Current upstream systemctl documentation states that daemon-reload reruns generators, reloads unit files, and recreates the dependency tree.

  7. Enable the member service so it becomes part of the target.
    $ sudo systemctl enable demo-stack-ready.service
    Created symlink /etc/systemd/system/demo-stack.target.wants/demo-stack-ready.service → /etc/systemd/system/demo-stack-ready.service.

    This install symlink is what makes the target pull in the service later. Disabling the service removes the symlink and detaches that member from the target.

  8. Enable the target for future multi-user boots and reach it immediately.
    $ sudo systemctl enable --now demo-stack.target
    Created symlink /etc/systemd/system/multi-user.target.wants/demo-stack.target → /etc/systemd/system/demo-stack.target.

    enable –now both installs the target into multi-user.target for later boots and starts it in the current session. If the target should only be reached on demand, use sudo systemctl start demo-stack.target instead.

  9. Confirm that the target is active and that systemd attached the member service as a direct dependency.
    $ systemctl status --no-pager --full demo-stack.target | head -n 6
    ● demo-stack.target - Demo application stack target
         Loaded: loaded (/etc/systemd/system/demo-stack.target; enabled; preset: enabled)
         Active: active since Mon 2026-04-13 21:32:05 +08; 2min 57s ago
           Docs: man:systemd.target(5)
    
    Apr 13 21:32:05 host systemd[1]: Reached target demo-stack.target - Demo application stack target.
    
    $ systemctl show -p Wants -p After demo-stack.target
    Wants=demo-stack-ready.service
    After=demo-stack-ready.service

    The success state for the target itself is Active: active together with the expected member unit in Wants=. Current upstream target-unit rules also automatically add the matching After= ordering dependency for Wants= and Requires= relationships unless default dependencies are disabled.

  10. Confirm that the member service actually ran when the target was reached.
    $ tail -n 1 /var/log/demo-stack.log
    demo-stack target reached 2026-04-13T21:32:05+08:00

    A member unit's own log, status output, socket, mount, or other application-specific proof is a better validation than the target state alone because it shows the units behind the target really executed.

    If the target activates but the member service does not do its work, inspect the service journal with sudo journalctl -u demo-stack-ready.service -n 20 –no-pager and confirm the install link still exists under /etc/systemd/system/demo-stack.target.wants/.