A systemd target sounds harmless, until someone isolates it on a live box and takes the login session with it. This walks you through building one safely: group two existing services under a named target, order it after them, enable it without touching those services, and verify the dependency graph before you ever isolate anything. The examples match systemd 255, installed here as 255.4-1ubuntu8.17.
Allow about 20 minutes if the services already exist. You need a systemd host, a shell, and root access for files under /etc/systemd/system. The inspection commands are ordinary user commands unless marked sudo.
Warning: do not test isolation on a remote production host without console access. systemctl isolate stops units outside the new transaction, and that can include your own connection.
A target is a named grouping and synchronisation point, not a process. It can pull in units through Wants= or Requires=, and it can impose ordering with After= or Before=. Those are two separate decisions: a target that lists a service in Wants= does not, by that fact alone, wait for the service to start.
This guide uses a local maintenance mode called app-maintenance.target. Replace the two example services with units that really exist on your host, and check them first:
$ systemctl list-unit-files 'example-api.service' 'example-worker.service'
$ systemctl cat example-api.service
$ systemctl cat example-worker.service
Checkpoint: stop here if either unit is absent, generated by another tool, or unsafe to start together. A target does not make an incomplete service deployment correct.
Create the unit as root. The target file must not be empty; systemd treats an empty target as masked. The [Unit] section is the useful part, because target units have no separate [Target] section.
$ sudoedit /etc/systemd/system/app-maintenance.target
Paste this file, changing the service names if necessary:
[Unit]
Description=Application Maintenance Mode
Documentation=man:systemd.target(5)
Wants=example-api.service example-worker.service
After=example-api.service example-worker.service
AllowIsolate=yes
Wants= asks systemd to start the services when the target starts, but their failure does not normally invalidate the target. Use Requires= only when the target should fail or stop along with those units, following the requirement dependency rules. Neither directive substitutes for After=.After= is what actually makes the target a synchronisation point that comes after both services.AllowIsolate=yes lets an administrator make this target the active transaction with systemctl isolate. Leave it out if isolation is not an intentional operation; the target can still be started normally, isolation just gets refused.Ask systemd to parse the unit and its dependencies without starting anything:
$ systemd-analyze verify /etc/systemd/system/app-maintenance.target
Success is no output and exit status 0. Warnings about missing example services are expected only if you deliberately used placeholders; replace them before continuing. A misspelled directive or an unsupported section is a configuration error, not something to shrug off because systemd kept parsing anyway.
Reload the manager once the file passes verification:
$ sudo systemctl daemon-reload
$ systemctl cat app-maintenance.target
[Unit]
Description=Application Maintenance Mode
Documentation=man:systemd.target(5)
Wants=example-api.service example-worker.service
After=example-api.service example-worker.service
AllowIsolate=yes
Checkpoint: the displayed unit must be the file you edited. If it is not, inspect the load path and any drop-ins with systemctl status app-maintenance.target and systemctl show -p FragmentPath app-maintenance.target.
Before changing the running system, look at the transaction systemd would actually build:
$ systemctl list-dependencies --all app-maintenance.target
app-maintenance.target
* example-api.service
* example-worker.service
$ systemd-analyze dot app-maintenance.target | grep -E 'app-maintenance|example-(api|worker)'
The exact graph output varies, and the service names are host-specific. The check that matters is that both services show up as dependencies of the target. To see ordering properties directly:
$ systemctl show app-maintenance.target -p Wants -p Requires -p After -p Before
Wants=example-api.service example-worker.service
Requires=
After=example-api.service example-worker.service ...
Before=shutdown.target ...
Target units pick up default dependencies too: your configured Wants= and Requires= get complemented with After= ordering, and the target is conflicted with and ordered before shutdown.target. Do not treat that as a reason to drop your explicit ordering; it documents the synchronisation point and makes the unit's intent obvious to the next person.
Start the target as a normal unit first. This changes service state, so do it in a maintenance window if either service is production-facing:
$ sudo systemctl start app-maintenance.target
$ systemctl is-active app-maintenance.target
active
$ systemctl is-active example-api.service example-worker.service
active
active
If a service fails, inspect it before retrying:
$ systemctl status --no-pager example-api.service
$ journalctl -u example-api.service -b --no-pager
Starting the target is reversible: sudo systemctl stop app-maintenance.target undoes it. But stopping a target does not necessarily stop every unit it wanted. If you need a group whose members stop together, design that dependency relationship deliberately and test it on a non-critical host; do not assume Wants= gives you that lifecycle guarantee for free.
A target's [Install] section is optional. Add one only if this target should be pulled in by a boot target or another enablement point:
[Install]
WantedBy=multi-user.target
With that section saved, enable it as root:
$ sudo systemctl enable app-maintenance.target
Created symlink /etc/systemd/system/multi-user.target.wants/app-maintenance.target -> /etc/systemd/system/app-maintenance.target
$ systemctl is-enabled app-maintenance.target
enabled
Enabling changes a symlink and affects future boots; it does not start the target now. sudo systemctl disable app-maintenance.target undoes it. If this target is meant for an occasional maintenance mode, do not enable it just because it worked fine when started manually.
Isolation replaces the active transaction with the target's transaction. It can stop your login session, networking, or other services. Before using it, confirm console access and keep a second administrative session open.
$ systemctl list-dependencies --all app-maintenance.target
$ sudo systemctl isolate app-maintenance.target
Afterwards, verify the result:
$ systemctl is-active app-maintenance.target
active
$ systemctl list-units --state=failed
To leave the mode, isolate a known-good target for your host, commonly multi-user.target or graphical.target:
$ systemctl get-default
graphical.target
$ sudo systemctl isolate graphical.target
Recovery: use the value systemctl get-default actually returned, not the example, and confirm it exists before isolating it. If you only started the custom target rather than isolating it, sudo systemctl stop app-maintenance.target is enough.
[Unit] section and no invented [Target] section.Wants= or Requires= exists and was chosen on purpose.After= expresses the ordering you actually need, and you are not confusing dependency with ordering.systemd-analyze verify passes and systemctl cat shows the file you intended.