Hello, Community!
In August, the VyOS development branch saw work in a few directions at once. Contributors outside the core team landed new firewall capabilities, a dynamic DNS overhaul, NTP options, and fixes for bugs that only turned up once someone ran a configuration nobody had tried before.
Interface naming used to be decided from a single per-device udev event, which fires before all the hardware has finished enumerating. Whatever had managed to announce itself by that moment determined the outcome, so on multi-vendor PCIe systems, interfaces could be lost or misnamed. You would meet it whenever names were being allocated with nothing to match against - on first boot, or after a NIC replacement - and also on hardware where a card is simply slow to initialize, where a late-arriving NIC could lose the race on an otherwise ordinary reboot and come back reordered or with a second hardware-id entry against its name. On hardware that comes up promptly, a card VyOS has already seen keeps the name pinned to its MAC address, which is why most users will never have run into any of this.
Naming is now decided in one authoritative pass, run once the configuration is available during router startup. It waits for the configured hardware, applies every hardware-id binding you have set, and names whatever is left by PCIe distance from the root complex and MAC address - producing the same result on every boot. If a card your configuration expects is simply not there anymore - pulled, failed, or moved to another slot - boot now reports that interface as unresolved, instead of leaving you to work it out later from whatever stops working because of it.
NIC replacement is handled conservatively, and deliberately so. An interface whose hardware ID has been deleted inherits its previous configuration only when it is the sole pending node of its type that boots and exactly one candidate exists. Anything more ambiguous is left pending and reported rather than guessed: with only a MAC address and a PCIe position to go on, there is no way to tell which of several unconfigured NICs was genuinely the one you replaced, and a wrong binding writes itself back and becomes permanent on every later boot. A candidate that is not matched this way is not lost - it still receives an ordinary bootstrap name, it simply never inherits another node's settings.
More details: https://vyos.dev/T3871
Two smaller platform changes belong here as well. The kernel now includes the driver for MANA, Azure's own paravirtual network device, which is effectively a requirement for Azure images. And on Azure with Accelerated Networking, the helper that names virtual-function interfaces now runs from initramfs. Deployments with more than one accelerated NIC could previously end up with an interface that received no traffic and no address at all, or with two interfaces claiming the same MAC - the same story as above, with the association between hardware and name being settled too late.
More details: https://vyos.dev/T9138, https://vyos.dev/T8329
Remote groups - the firewall groups whose contents are downloaded from a URL - can now be given their own update interval:
set firewall group remote-group <name> interval <value>
The value accepts plain seconds or a suffix of s, m, h, d, or w, so 4h is valid, with a range from 60 seconds to four weeks. Previously, every remote group followed the global firewall global-options resolver-interval, which also drives domain-group and FQDN resolution - a single knob for two quite different jobs. A blocklist that its publisher updates daily and an FQDN that changes every few minutes no longer have to share a schedule. Groups with no interval set keep following the global value, so nothing changes until you ask for it.
More details: https://vyos.dev/T9076
Protocol values in rules are now normalized to protocol names before validation, which fixes a genuinely confusing failure. Writing a protocol by its IANA number worked on its own, but combining it with a port, TCP flags, GRE options, or synproxy was rejected - protocol 6 with a destination port produced "Protocol must be tcp, udp, or tcp_udp when specifying a port", because the checks compared against names, while you had given a number. Numbers and names now behave identically, and a number with no known name is kept as it is. Rule validation errors also became more informative, and now tell you which rule number failed - a small thing that stops being small when a rule set has two hundred rules in it.
More details: https://vyos.dev/T8247
A cluster of improvements landed around PKI, and the most consequential one fixes an ACME certificate quietly losing its chain.
When you use ACME - Let's Encrypt and similar services - the certificate you receive comes together with an intermediate CA certificate, and a client needs both of them to decide that your certificate is trustworthy. VyOS finds that intermediate and adds it to your configuration for you.
The problem was one of timing inside a single commit. HAProxy and the HTTPS server are set up in the same commit that fetches the certificate, but they were reading a copy of the configuration made just before the intermediate was added. So they never saw it, and served your certificate on its own, which is exactly the situation where a client that does not already know the intermediate refuses to connect. Renewals skipped the step that adds it altogether, so a certificate left in that state stayed that way. VyOS now re-reads the configuration after adding the intermediate, so the services configured afterward can see it.
The rest of the ACME work is about what happens when things go wrong, which is usually at renewal time rather than when you first set everything up:
On the operational side, you can now inspect a stored CA or certificate in human-readable form with
show pki ca <name> text
show pki certificate <name> text
Both commands complete the names of the CAs and certificates you actually have, instead of requiring you to remember them. Importing a CA certificate from a remote URL is supported, and a file:// URL now resolves correctly.
The ddclient integration received a substantial overhaul, contributed by a community member, together with an upgrade to ddclient 4.0.0. Porkbun is supported as a provider, luadns and desec can now be used for dual-stack configurations, and Gandi requires a personal access token, following the provider's own change. Protocol configuration was reworked, unsupported protocols are blocked at commit time rather than failing later, and two providers that no longer exist - googledomains and woima - were removed along with a set of legacy configuration properties that no longer did anything.
More details: https://vyos.dev/T6981
The set-dscp option for shaper policies has existed in the CLI for some time, but nothing implemented it - no traffic control commands were generated, so packets were never remarked. It works now, using the correct field for each protocol, preserving ECN bits, recalculating the IPv4 header checksum afterward, and skipping non-IP traffic so that packets such as ARP are not corrupted.
Alongside it, two QoS bugs were fixed. A shaper class matching the Ether protocol with anything other than the default - arp, for example, crashed the commit outright. And applying an egress policy to an interface silently removed that interface's ingress redirect: a download shaper built by redirecting to an ifb device stopped shaping altogether, and traffic ran at full line rate, while the commit reported success. This one was a regression - it arrived in rolling on 2026-06-23, with the change that re-applies QoS configuration on dynamic interfaces. If you shape download traffic that way and see it quietly stop working, that is why.
More details: https://vyos.dev/T8996, https://vyos.dev/T9134, https://vyos.dev/T9080
The SBOM files shipped with rolling images now describe their contents accurately, which they previously did not. The tool that generates them works out which packages are installed by first identifying the operating system. VyOS reports itself as vyos and never mentions that it is built on Debian, so the tool did not know to look for Debian packages, and the list it produced was wrong. The build now tells it, and the inventory matches what is really in the image. Each document also names the image and version it was generated from, which starts to matter as soon as you keep more than one of them. The CycloneDX output is pinned to schema 1.6, so your tooling knows what it is parsing. The CycloneDX file is considerably smaller now, with per-file detail dropped, while the SPDX file keeps full file-level cataloging - so you can feed each to whatever suits it. And if SBOM generation fails, the image build now fails with it, instead of quietly producing an image without one.
The kernel packaging was also reworked to use real Debian packaging rather than fpm. This is invisible from the CLI, but it makes the kernel packages behave like every other package in the system.
More details: https://vyos.dev/T9203, https://vyos.dev/T9181
The following command now offers to carry your bash history across, alongside the existing prompts for configuration and SSH host keys:
add system image
Previously, it was silently discarded, and every upgrade left operators with an empty history.More details: https://vyos.dev/T9021, https://vyos.dev/T9140, https://vyos.dev/T9093, https://vyos.dev/T9184, https://vyos.dev/T9159
This period was a strong one for community contributions - much of what is described above came from outside the core team. A few of them are worth telling properly:
Every firewall accumulates rules nobody is willing to delete, because nobody can prove they are unused. Rules can now be marked with:
set firewall ... rule <n> last-used
The detailed rule output gains a column showing when the rule last matched traffic. It is a small addition that answers a question every operator of a long-lived rule set eventually has to ask.
Silence is the worst way for a feature to fail. When flow-accounting moved from pmacct to the ipt_NETFLOW kernel module, system flow-accounting vrf quietly stopped having any effect: pmacct was a daemon and could be wrapped in ip vrf exec, but NetFlow is now emitted by the kernel, and nothing bound the module's export socket to the VRF. A collector reachable only inside a VRF - the usual out-of-band management design - received nothing at all, and the commit reported no error. Configurations produced by the flow-accounting migration script were affected, too, so an upgrade could stop your flow export without saying so. The export socket is now bound to the VRF device, with a per-server source-interface taking precedence where one is set. One caveat worth checking before you upgrade: a configuration that names a VRF but whose collector is actually reached through the default routing table works today only because the setting was ignored, and will stop working once it is honored.
One related change comes with it: a VRF can no longer be named where a source interface is expected. Neither Cisco nor Juniper sources a flow exporter from a VRF - both use a routed interface - and VyOS now works the same way, rejecting it with a clear message instead of the misleading "is not a member of VRF" error it used to produce. The VRF itself is still configured under the system flow-accounting VRF, as before.
Some bugs only appear on the second of something. Committing a second NHRP tunnel with redirect enabled failed outright with "Failed to apply NHRP tunnel firewall rules". The redirect chain is rendered per tunnel in a loop, but the nftables meter name inside it was hardcoded - so the second tunnel declared a meter that already existed, nftables rejected the whole atomic ruleset load, and the commit failed. The meter name is now derived from the loop index. Single-tunnel setups are unchanged, which is why nobody noticed until someone built the second tunnel.
A subtle one, and a good illustration of why timing bugs are hard to catch. While a commit held the lock, the netlink daemon slept instead of reading its socket, so kernel messages queued up rather than being consumed. When the commit finished, an event that was no longer true could be processed as if it had just arrived: an interface DOWN from a disable that had already been reversed. The reporter hit it on a VRRP pair whose transition scripts disable and re-enable the WAN interfaces about twenty seconds apart, and the stale event killed the DHCP client on an interface that was by then enabled and up - a silent WAN outage on the HA master, with nothing in the system able to notice or recover from it. The daemon now keeps draining the socket during a commit and discards what it reads, so nothing stale outlives the commit that masked it.
Thank you to everyone who contributed this period - the code, the reports, and the research behind them.