# update-systemd-resolved points OpenVPN DNS at systemd-resolved over DBus

> A Bash helper that gives an OpenVPN tunnel its own DNS servers through the systemd-resolved DBus API instead of rewriting /etc/resolv.conf, with a Nix flake, generated polkit rules and documented fallbacks when optional helpers are missing.

**jonathanio/update-systemd-resolved** — Helper script for OpenVPN to directly update the DNS settings of a link through systemd-resolved via DBus.

- Repository: https://github.com/jonathanio/update-systemd-resolved
- Stars: 807 · Forks: 98
- Language: Shell
- License: NOASSERTION
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/jonathanio-update-systemd-resolved

## The problem: one global resolv.conf cannot serve both the tunnel and the host

OpenVPN normally pushes DNS information to the client by editing a global file, which changes name resolution for the whole machine while the tunnel is up and restores it afterwards. That approach collides with systemd-resolved, which keeps its own view of which resolver belongs to which link, and it also collides with any other service that expects to own that file. update-systemd-resolved exists to route around both problems. It is described as a helper script designed to integrate OpenVPN with the systemd-resolved service via DBus instead of trying to override /etc/resolv.conf or manipulate systemd-networkd configuration files. The audience is narrow and specific: administrators of Linux hosts where systemd-resolved is already the DNS manager and where an OpenVPN tunnel needs to resolve names through the resolver the VPN hands out. If your host resolves DNS some other way, this script has no role to play, and the documentation offers no alternative path.

## How the link-level DBus call replaces a resolv.conf rewrite

The mechanism is a single well-defined interface rather than a collection of file edits. Since systemd-229, the systemd-resolved service has an API available via DBus which allows directly setting the DNS configuration for a link. The script makes use of busctl from systemd to send DBus messages to systemd-resolved to update the DNS for the link created by OpenVPN. The target is the SetLinkDNS() function on the org.freedesktop.resolve1.Manager D-Bus interface, and that call does not accept plain textual addresses: numerical labels like 1.2.3.4 have to be translated into the byte arrays that function expects. This is why an address parser matters here, and why the project lists python or sipcalc as optional dependencies rather than treating them as decoration. Because the change is scoped to one link, the host resolver keeps its own configuration and nothing has to be restored when the tunnel goes down. The project also depends on resolvectl from systemd, alongside busctl, which suggests the script checks link state and resolver status through the standard command line tools.

## Three installation routes, including a Nix flake and a NixOS module

Packaging is unusually thorough for a single Bash script, which tells you the project is taken seriously by at least three distributions and one declarative ecosystem. The README says the simplest route on a distribution that uses the Arch User Repository is the openvpn-update-systemd-resolved package, because it takes care of updates through the package manager. Debian and Ubuntu provide a .deb package in their distributions. The fallback for everything else is a manual install from a checkout, and the repository ships a Makefile whose default target is install plus info, installing the script into libexec under a prefix of /usr/local. The third route is Nix, where the project exposes a flake that provides the update-systemd-resolved package for several Linux architectures and a NixOS module. A manual build from a checkout is three commands, and the resulting binary lands in libexec under the prefix chosen by the Makefile:

```bash
git clone https://github.com/jonathanio/update-systemd-resolved.git
cd update-systemd-resolved
make
```

Running make performs the install and then prints where the script was placed, together with the lines to add to /etc/nsswitch.conf and to the OpenVPN configuration. For a Nix workflow, the flake goes into your inputs and the NixOS module is listed in your system modules:

```nix
{
  inputs = {
    # Other inputs here...

    update-systemd-resolved.url = "github:jonathanio/update-systemd-resolved";
    update-systemd-resolved.inputs.nixpkgs.follows = "nixpkgs"; # optional
  };

  # Etc.
}
```

The same flake also exposes a NixOS module named update-systemd-resolved, and the project keeps its options in a separate document under docs, so the details live there rather than in the README.

## Enabling systemd-resolved and pointing NSS at it

Before anything useful happens, systemd-resolved itself has to be running, which the README treats as a prerequisite rather than an optional extra:

```bash
systemctl enable systemd-resolved.service
systemctl start systemd-resolved.service
```

Two files then decide whether queries reach it: the service state, and /etc/nsswitch.conf, which controls how the name service switch looks up hosts. The three orderings offered cover hosts that should keep using the classic resolver first, hosts that should prefer systemd-resolved, and hosts that should ignore /etc/resolv.conf entirely:

```conf
# Use /etc/resolv.conf first, then fall back to systemd-resolved
hosts: files dns resolve myhostname
# Use systemd-resolved first, then fall back to /etc/resolv.conf
hosts: files resolve dns myhostname
# Don't use /etc/resolv.conf at all
hosts: files resolve myhostname
```

The file is read as soon as it is saved, so no restart is required. One caveat is worth flagging before you edit anything: some Linux distributions manage /etc/nsswitch.conf themselves, so manual edits may disappear on the next package upgrade, and the project sends you to the distribution documentation for the correct procedure. The same applies to the OpenVPN side, where the install output asks for script-security 2 plus up and down hooks with up-restart and down-pre semantics, so the script is run in a restricted environment and the tunnel is restarted rather than left half configured.

## Polkit rules decide which accounts may make the DBus calls

This is where the security story lives, and the project is unusually explicit about it. If the OpenVPN client runs as an unprivileged user, that user needs polkit rules authorizing the DBus calls that the script makes. Some installation methods bundle these rules. On Arch Linux, where openvpn-client@<name>.service instances run as the unprivileged openvpn user, the AUR package ships suitable rules in the file /etc/polkit-1/rules.d/10-update-systemd-resolved.rules. When you generate them yourself, the script prints rules that authorize the users or groups you name, and the documentation encourages you to review the generated polkit rules before installing them to avoid security risks. The generator writes to standard output, so the normal workflow is to redirect it into a file and install that file with the right ownership and mode, 0640, under rules.d:

```bash
update-systemd-resolved print-polkit-rules --polkit-allowed-user some-user --polkit-allowed-user another-user > ./10-custom-update-systemd-resolved.rules
sudo install -Dm0640 ./10-custom-update-systemd-resolved.rules /etc/polkit-1/rules.d/10-custom-update-systemd-resolved.rules
```

That grants the two named users permission to make the calls the script makes. The Makefile applies the same idea by default, generating rules with the options --polkit-allowed-user=openvpn and --polkit-allowed-group=network.

## Optional helpers for IP parsing, logging and JSON, each with a Bash fallback

The dependency list reads like a soft dependency matrix, and the behaviour when a helper is absent is documented rather than left to discovery. For IP parsing and validation, python or sipcalc will be used if available; otherwise the script uses native Bash routines. The same pattern covers logging: if the logger command from the util-linux distribution is available it is used, and otherwise all logs go to standard error using Bash's printf builtin. For polkit rules generation, jq or perl or python will be used for serializing the names of the users and groups allowed to call the DBus methods into JSON lists for use within the generated rules, and the script falls back to native Bash routines for generating these lists when none of them is present. In practice this means a minimal container with coreutils or busybox, iproute2 and systemd can still run the script, with degraded formatting rather than a hard failure. It also means behaviour can differ between two hosts with different tool sets, which is worth checking when a rule looks different from the one you expected.

## Maintenance signals, licensing and the cases where this is the wrong tool

The last push to the repository was on 2026-07-03, and the release history shows three tagged versions: v2.0.0 on 2025-03-23, v1.3.0 on 2019-05-19 and v1.2.7 on 2017-11-12. The gap between 2019 and 2025 is wide, and the changelog entry for v1.3.0 mentions DNS6, DOMAIN and Cache changes while v1.2.7 added support for ADAPTER_DOMAIN_SUFFIX, so the recent version is a large jump rather than a drip of small fixes. The project is published under a custom licence that GitHub cannot classify, and the LICENSE file is the place to read the actual terms, which means you cannot assume MIT or Apache terms apply. Limitations worth knowing: the documentation does not describe a rollback procedure for DNS settings that were pushed to a link, it requires a systemd host running systemd-resolved since version 229, and it offers nothing for NetworkManager, dnsmasq or plain resolv.conf setups. The custom licence and the undocumented rollback path are the two things to raise before deploying this in production.

## Conclusion

Adopt it if you run OpenVPN on a systemd host where systemd-resolved already owns DNS and the tunnel needs its own resolvers without touching /etc/resolv.conf; skip it if you rely on NetworkManager, dnsmasq or a plain resolv.conf, since nothing in the docs covers those setups. Before installing, confirm that systemd-resolved.service is enabled, that busctl and resolvectl are present, and that the polkit rules the script prints are reviewed by you before they are installed under /etc/polkit-1/rules.d.

## FAQ

### How do I install update-systemd-resolved on a Linux system?

On Arch-based systems the openvpn-update-systemd-resolved AUR package is the simplest route because it handles updates through the package manager, while Debian and Ubuntu ship a .deb package. Otherwise you can build it from a checkout with git clone, cd and make, or pull it in through the Nix flake.

### Does update-systemd-resolved need polkit rules when OpenVPN runs as an unprivileged user?

Yes. The unprivileged user must be authorized to make the DBus calls the script performs, and you can generate those rules with update-systemd-resolved print-polkit-rules, naming users with --polkit-allowed-user and groups with --polkit-allowed-group, then install the output under /etc/polkit-1/rules.d.

### Does update-systemd-resolved rewrite /etc/resolv.conf?

No, that is the point of the project. It integrates OpenVPN with systemd-resolved via DBus instead of trying to override /etc/resolv.conf or manipulate systemd-networkd configuration files, and it sets the DNS configuration for the link created by OpenVPN.

### What happens if python, jq or logger are not installed?

The script falls back to native Bash routines. For IP parsing it uses Bash routines instead of python or sipcalc, for logging it sends output to standard error with Bash's printf builtin instead of the logger command from util-linux, and for polkit rules it generates the lists with Bash routines instead of jq, perl or python.

## Sources

- [Issues](https://github.com/jonathanio/update-systemd-resolved/issues)
- [jonathanio/update-systemd-resolved on GitHub](https://github.com/jonathanio/update-systemd-resolved)
- [README](https://github.com/jonathanio/update-systemd-resolved/blob/master/README.md)
- [Releases](https://github.com/jonathanio/update-systemd-resolved/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/jonathanio-update-systemd-resolved
