- Published on
systemd v261 — Staged Rollout Comes to PID 1, Cloud IMDS Absorption, and a Finished dlopen Migration
- Authors

- Name
- Youngju Kim
- @fjvbn20031
- Introduction — Every Three Months, and This Time It's fleet's Turn
- ConditionFraction= — Staged Rollout Arrives in the Unit File
- This Is Not a Canary Deployment System
- The IMDS Subsystem — systemd Steps Into cloud-init's Yard
- The dlopen Migration Is Complete — and How It Broke rsyslog
- Pre-Upgrade Checklist — the Things That Bite Quietly
- Other Things Worth Watching
- So Who Should Care, and When
- Closing
- References
Introduction — Every Three Months, and This Time It's fleet's Turn
These days systemd ships a major release nearly every quarter. Carrying the release footers straight from the raw NEWS file: v258 landed 2025-09-17, v259 on 2025-12-17, v260 on 2026-03-17, and v261 on 2026-06-19. As of this writing, the Arch Linux core repository is already shipping 261.1, Ubuntu has 261.1 in its 26.10 development branch (stonking), and 26.04 LTS stays on 259.5. In other words, rolling-release users are meeting it now, and LTS users probably won't until next year.
Judging by the version number alone this looks like just another quarterly release, but reading the NEWS file start to finish reveals one clear direction in this release: PID 1 has begun absorbing fleet-operations primitives beyond being a single machine's init. ConditionFraction=, which turns a unit on for only a portion of the fleet; a tag system that groups machines into rings; and a new subsystem that integrates cloud instance metadata (IMDS) into the boot process — all of it landed in this one release. Alongside it, the dependency-isolation work that has been running since 2024 — converting external libraries to dlopen() — finally wrapped up, and the "arc of removal" that began with v258's cgroup v1 removal continues as well.
This post goes in order of operator-relevant importance, not sales-pitch order. The focus is on distinguishing what each item is, what it's good for, and — just as important — what it is not. If systemd units and condition expressions themselves are new to you, read systemd Service Management, Unit File Configuration, and Troubleshooting first.
ConditionFraction= — Staged Rollout Arrives in the Unit File
This is v261's most interesting new feature. Here's the summary based on the systemd.unit man page source at the v261 tag.
ConditionFraction= activates a unit only on a "stable pseudo-random subset of the fleet." You deploy the same unit (or drop-in) to the entire fleet, but the condition is true only on the configured fraction of machines. The determination happens entirely locally — the machine ID is hashed together with a tag string into a 32-bit integer, and it's true if that value is below the configured percentage of 2^32 (per NEWS's description). There's no central coordinator, no network call, and the same machine always gets the same verdict.
The syntax is a single percentage, or a tag-and-percentage combination. Percentages allow up to two decimal places.
# /etc/systemd/system/telemetry-v2.service.d/rollout.conf
# this unit turns on for only about 10% of the fleet
[Unit]
ConditionFraction=telemetry-v2 10%
Prefixing an exclamation mark gives you the complement — carrying the man page's own example, !myrollout 30% is true on the remaining roughly 70% of machines. You can use this directly to split old and new versions into mutually exclusive groups.
# the old-version unit: turns on only on the complement of the same tag
[Unit]
ConditionFraction=!telemetry-v2 10%
What the tag does here matters. A tag is an arbitrary string (no whitespace) mixed into the hash derivation, and different tags select mutually independent subsets. Put the other way around, all ConditionFraction= conditions without a tag pick the exact same set of machines — run two untagged 10% rollouts and the two experiments overlap on exactly the same machines. The man page's guidance is: always use different tags for unrelated rollouts, and share a tag only when you want to bind multiple units to the same set of machines. If the machine ID can't be read, the condition fails (is false).
You can also check the verdict on a specific machine ahead of deployment. The systemd-analyze condition command evaluates a condition string on the spot.
systemd-analyze condition 'ConditionFraction=telemetry-v2 10%'
This Is Not a Canary Deployment System
Time to be honest. The sentence "canary rollouts have arrived in PID 1" is only half true. What ConditionFraction= provides is population selection, and nothing else — the rest of what makes a canary deployment a canary deployment (observation, judgment, abort) it provides not at all.
- No health gate. Even if the error rate spikes on the 10%, systemd knows nothing about it. Watching the metrics and deciding to stop the rollout is still entirely on you, outside systemd.
- No automatic rollback. The condition is a static predicate evaluated at unit-activation time. Reverting a bad deployment means editing the drop-in and redeploying, and that is ultimately a job for your configuration-management tool.
- Changing the fraction is a redeploy. To go from 10% to 50% you have to change the drop-in file and push it to the fleet again. If you want the experience of turning a central dial, this feature is not the answer.
- The percentage is approximate. The man page itself says "approximately the configured fraction." Because it's hash-based, the deviation is large for a small fleet. On 20 machines, 10% could be 0 machines or it could be 4.
So what is it actually for? Exactly the use the man page names: staged exposure for image-based systems that deploy the same artifact to the whole fleet. In environments like OSTree or UKI images, where you can't (or don't want to) ship a different file per machine, "deploy to everyone, but turn it on for only some" is effectively the only rollout mechanism available. Behaving deterministically without central coordination is, in that environment, a requirement rather than a downside. Conversely, in environments where Kubernetes or a configuration-management tool already gives you per-machine deployment control, this feature opens almost no new doors.
If you need deliberate ring separation instead of a random fraction, use the new tag system. Starting with v261, a machine can carry a list of tags in the TAGS= field of /etc/machine-info, queried and replaced with hostnamectl tags per the hostnamectl man page source (a tag is 1 to 255 ASCII alphanumeric characters, hyphens, and dots). On the unit side, ConditionMachineTag= checks tags against a shell glob pattern.
# turns on only on machines tagged ring0 (the internal dogfood ring)
[Unit]
ConditionMachineTag=ring0
AssertMachineTag= was added alongside it, and per NEWS, systemd-firstboot can plant tags at provisioning time via a command-line argument or a credential. Put together: explicit rings via tags, a random fraction within a ring via fraction, and observation and judgment via your monitoring. This division of labor is the closest reading to the design intent.
The IMDS Subsystem — systemd Steps Into cloud-init's Yard
v261 ships a whole new subsystem for handling the cloud instance metadata service (IMDS). There are quite a few pieces to it.
- hwdb.d/40-imds.hwdb — a hardware DB that identifies the cloud from SMBIOS information and describes that cloud's IMDS endpoint address, token URL, and header format. Per NEWS it recognizes nine: Amazon EC2, Microsoft Azure, Google Compute Engine, Hetzner, Oracle Cloud, Scaleway, Tencent Cloud, Alibaba ECS, and Vultr. It also holds the per-cloud mapping of "well-known keys" such as hostname, region, zone, public IPv4/IPv6, SSH public key, and user-data.
systemd-imdsd@.service— per the man page, a local daemon that answers IMDS field queries over Varlink IPC at/run/systemd/io.systemd.InstanceMetadata.systemd-imdsd-early-network.service— generates the early-boot network configuration (.networkfiles) needed to reach the IMDS endpoint.systemd-imds— the client tool. Per NEWS it imports the fields IMDS provides as system credentials, which later services then consume. It's also explicitly stated that the data is measured into the TPM before it's imported.systemd-imds-generator— automatically pulls these services into the boot transaction once a supported cloud is detected. The goal NEWS states is "a genuinely universal image" — a single image that uses IMDS if it's there and quietly skips it if it isn't.
The part operators need to watch is network locking. The kernel command line systemd.imds.network= takes three values — off, locked, unlocked. In locked mode, a prohibit route is laid down to the IMDS endpoint, blocking direct access by unprivileged processes, so all access has to go through systemd-imdsd. Given that IMDS credential theft has been a go-to path for cloud breaches, the direction itself makes sense. The catch is a sentence NEWS writes about itself — this lock is recommended for security, but it "typically conflicts" with traditional clients like cloud-init that assume direct IMDS access.
As for what the default is, the documentation gives two different answers. The meson_options.txt imds-network option has no explicit default and lists its choices in the order unlocked, locked, which means unlocked is the default per the upstream source (a meson combo option defaults to its first entry) — but the man page's description of the kernel switch states that locked is the default. What the default actually is on your system depends on what your distribution chose at build time, so if you're running an image alongside cloud-init, it's safer to check the distro package's build options.
One thing needs to be stated clearly. This is not a replacement for cloud-init — not yet, at least. What systemd-imds does stops at pulling metadata fields in as credentials; provisioning logic like cloud-config parsing, package installation, and user creation is out of scope (neither NEWS nor the man page claims otherwise). Still, the fact that hostname, SSH keys, and user-data have started entering the credential system reads naturally as a first move toward a future where simple provisioning needs are handled by stock systemd parts alone. How cloud-init coexists with this lock mode is something to watch.
The dlopen Migration Is Complete — and How It Broke rsyslog
The March 2024 xz-utils backdoor (CVE-2024-3094) used distributions' habit of linking libsystemd into sshd as its channel — liblzma rode into the sshd process as a transitive dependency of libsystemd. Starting with v256, which shipped three months later in June 2024, systemd began converting five libraries including that very liblzma (liblz4, libzstd, liblzma, libkmod, libgcrypt) from ordinary linking to a dlopen()-based approach, and it has widened the target list with every release since.
v261 is the finish line of that work. With libgnutls, libmicrohttpd, libcurl, libcrypto, libssl, libfdisk, and libcryptsetup converted this time, NEWS declares it in a literal boxed callout: direct linking to external libraries has now been entirely replaced with dlopen(), with the sole exception of libc. Which dlopen dependencies exist is declared through an ELF dlopen metadata note embedded in each binary, so packaging tools can mechanically discover the optional dependencies.
The upside of this direction is clear — less code riding along in a process, more libraries you can drop from a minimal image, and a narrower transitive-dependency attack surface for xz-style incidents. But the conversion also creates a trap running in the opposite direction: the libraries libsystemd used to pull in on other software's behalf disappear.
The real case recorded in the v261 NEWS is exactly this. Once the guarantee that libsystemd links against libm (the math library) disappeared, it exposed a preexisting bug in libfastjson, which used libm's symbols without actually linking libm — and as a result, rsyslog crashes on startup. It's a bug that had been hidden all along, only because libsystemd happened to pull in libm. NEWS offers a linker flag as a stopgap workaround until the real fix — libfastjson linking libm directly — lands.
-Wl,--push-state,--no-as-needed,-lm,--pop-state
The lesson isn't limited to rsyslog. If you build or package software that links against libsystemd, now is the moment to check whether you've been leaning on a transitive dependency that just "happened to be linked." The principle of linking directly against the symbols you use is now, quite literally, starting to be enforced.
Pre-Upgrade Checklist — the Things That Bite Quietly
Pulled from NEWS's list of incompatible changes, here are only the ones that actually touch operations.
- udev DB v0 support removed. A live upgrade (swapping the daemon without a reboot) from a version below v247 straight to v261 or later is no longer supported. If you've been running a very old system, you'll need to route through an intermediate version or plan for a reboot.
- nspawn's
--user=renamed to--uid=. The short option-uand the old spelling still work but now emit a warning, and--userwith no argument has been repurposed as the switch that selects the user service manager scope. If you have scripts using--user, check whether its meaning has shifted under you. - TPM/CC measurement values changed. A bug where systemd-stub measured devicetree, the initrd, ucode add-ons, and UKI profiles into the TPM only, and skipped the hardware CC registers (e.g., Intel TDX RTMR), has been fixed. It's a fix in the right direction, but register expected values change — if you're running RTMR-based attestation, verification breaks without a policy update. The newly added
systemd-pcrosseparator.servicealso adds separator measurements into PCR 0-7, 9, and 12-14, which affects any TPM policy that references those PCRs. - Varlink io.systemd.Unit's enum notation changed. Fields that used to be exposed as strings became enum types, changing how values are notated on the wire (for example, tty-force becomes tty_force, and kmsg+console becomes kmsg_console). If you have a tool parsing this interface, it needs updating.
- MinimumUptimeSec= now defaults to 15 seconds. To prevent a boot loop where the system keeps auto-shutting-down right after boot, if shutdown is requested while uptime is under 15 seconds, that much delay is inserted at the last step of shutdown (right before the reboot() syscall). Per the systemd-system.conf man page source, this doesn't apply in containers and can be turned off by setting it to 0. If you have a test pipeline that cycles boot-and-shutdown rapidly, this default can add to your wall-clock time.
- Minimum musl version is now 1.2.6. Relevant to distributions maintaining musl-based builds.
- Heads-up for v262. Support for
/run/boot-loader-entries/and the experimental systemd-sysupdated D-Bus API are slated for removal in the next release (the latter replaced by direct Varlink). If you depend on either now, you'll need to migrate within a quarter.
For context, this list is a continuation of the removal trend that started with v258. v258 (2025-09) removed cgroup v1 and System V state control (runlevel, telinit, init 3), and v260 (2026-03) finished off System V service-script support (systemd-sysv-generator, rc-local.service). If you still have an in-house daemon shipped as a SysV script, it's already failing to come up at boot on v260-line distributions.
Other Things Worth Watching
Kept brief for space, but depending on your environment these can matter more than everything above.
- FD store surviving across kexec. On systems with the kernel's Live Update Orchestration (LUO) / Kexec Handover (KHO), the FD store of a unit with
FileDescriptorStorePreserve=yesis preserved across a kexec reboot. As NEWS puts it, "only memfd is supported at the time of writing," but this is where the userspace half of the story for zero-downtime kernel updates — swapping the kernel while keeping userspace state — begins.systemctl kexecnow using the kexec_file_load() syscall directly without kexec-tools, and the new--kernel-cmdline=option, are in the same vein. - Extended cgroup control.
CPUSetPartition=lets a unit set the cpuset partition type (root, isolated, member) directly. If you've been handling CPU isolation via scripts, you can move it into unit configuration. On the PSI side,CPUPressureWatch=/IOPressureWatch=-family settings let a service receive CPU and IO pressure notifications (previously only memory pressure was available). - systemd-oomd rulesets. There's now a structure for defining OOM policy rules under
/etc/systemd/oomd/rules.d/and selecting them per unit withOOMRule=. - PID 1 with no unit files. The manager now embeds a core set of units like basic.target and multi-user.target directly in the binary, so systemd can run as PID 1 even in a container with no unit files installed at all.
- networkd DHCP relay overhaul. With the new sd-dhcp-relay backend, the four relay-related settings that used to live in the
[DHCPServer]section are deprecated and have moved to a[DHCPRelay]section. If you're using those settings, they need migrating. - resolved's static-record drop-ins. You can define DNS records for local resolution via JSON drop-ins under
/etc/systemd/resolve/static.d/and similar. Think of it as a generalization of /etc/hosts. - Software TPM fallback.
systemd-tpm2-swtpm.servicehas been added, which automatically falls back to running IBM's swtpm on systems without a physical TPM (opt-in via a kernel command-line switch). NEWS itself is explicit that this is not an equivalent security posture to a hardware TPM, so it's for situations that need something better than "no TPM," not a substitute for one. - Kernel 7.1's reason codes in coredumps. systemd-coredump now collects the COREDUMP_CODE field that kernel 7.1 provides, and coredumpctl decodes it. Kernel 7.1 itself is covered in sched_ext Sub-Schedulers — cgroup-Level Scheduling That Only Half-Arrived in Kernel 7.1.
- One small fact — the v261 contributor list includes "Claude Opus 4.6" among the human names. A patch from an AI getting merged and recorded as a contributor has, by now, become an everyday line item in the release notes even for core infrastructure like systemd.
So Who Should Care, and When
Summed up by audience, it looks like this.
Right now — anyone verifying RTMR/PCR-based attestation in confidential computing (the measurements change), packagers of software that links against libsystemd (transitive dependencies need cleaning up), and anyone building rsyslog themselves. If you're on a rolling-release distro, v261 has already arrived.
Worth factoring into design — teams running an image-based fleet. ConditionFraction= and machine tags push staged exposure, which you've so far been faking with external tools, down into unit-file syntax. But design around the premise, stated above, that observation, judgment, and rollback are still on you. The IMDS subsystem is starting to become a genuine option for the minimal-image camp that has wanted to reduce its cloud-init dependency.
Still fine for now — most server operators staying on an LTS distribution. Ubuntu 26.04 is on v259, and most enterprise distributions are further behind than that. That said, the removal trend since v258 (cgroup v1, SysV scripts, udev DB v0) will all arrive at once on your next major upgrade, so if you have legacy dependencies, it's worth at least drafting a list now.
Closing
If you boil v261 down to one sentence, it's this — systemd is no longer just software that boots a single machine; it now presents itself as the minimal unit of a fleet. ConditionFraction= turns rollout population selection, tags turn ring separation, and the IMDS subsystem turns importing cloud context, into the syntax of PID 1. None of these replace your existing orchestration, but all three shrink, a little, the list of things that used to absolutely require an external tool.
At the same time, this release shows systemd applying the same principle to itself. The completed dlopen conversion is a two-year answer to the transitive-dependency problem the xz incident exposed, and the rsyslog case is the bill that answer sends to the ecosystem. The upgrade itself should be as painless as any other quarterly release, but expected attestation values and transitive dependencies are exactly the two kinds of change that bite a team that didn't read the release notes. I hope this post has served as a stand-in for that reading.
References
- systemd NEWS (raw, the project's official release notes)
- systemd v261 release (2026-06-19)
- systemd.unit man page source @ v261 — ConditionFraction=, ConditionMachineTag=
- systemd-imdsd man page source @ v261
- hwdb.d/40-imds.hwdb @ v261 — recognized clouds and IMDS key mapping
- systemd-system.conf man page source @ v261 — MinimumUptimeSec=
- meson_options.txt @ v261 — the imds-network build option
- ELF dlopen metadata spec (uapi-group)
- CVE-2024-3094 — the xz-utils backdoor
- Arch Linux core/systemd package · Ubuntu systemd source package (Launchpad)
- systemd Service Management, Unit File Configuration, and Troubleshooting (related post)
- Mastering systemd Timers — Time to Graduate from cron (related post)