LabHub

Blog

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

한국어English日本語中文

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.

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.

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.

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.

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

Comments

No comments yet.

Sign in to leave a comment