> ## Content Index
> Fetch the complete content index at: https://www.frank.be/llms.txt
> Use this file to discover other available public pages before exploring further.

# Running a real local DNS server next to systemd-resolved
- URL: https://www.frank.be/systemd-resolved-local-dns-server/
- Published: 2026-06-22T19:16:46.000Z
- Updated: 2026-06-22T19:17:24.000Z
- Author: Frank Louwers
- Tags: dns

💡

Post in English, because of the Google-juice :) Can never find the answer myself, so hope this post helps people looking for an answer.

I keep forgetting this, and every time I need it again I end up searching for the same thing. It is surprisingly hard to find the exact *correct* answer, so I am documenting it here. Mostly for future me, but maybe also for future you.

The situation: you have a modern Linux system with `systemd-resolved` installed, but you want to run a proper local DNS server as well. listening locally on `127.0.0.1:53`.

And then `systemd-resolved` enters the room and creates hours of debugging frustrations.

By default, `systemd-resolved` runs its own local DNS stub listener on loopback, typically at `127.0.0.53:53`. That is useful for many systems, but it can get in the way when you want your own DNS daemon to own local port 53.

The naive fix is to disable `systemd-resolved`, fight NetworkManager, rewrite `/etc/resolv.conf`, maybe even `chattr +i` it to make sure nobody touches it, and slowly build a nightmare for future-you.

Do not do that. Use the system instead of fighting it.

Use `systemd-resolved`, but point it at your local DNS server and turn off only its stub listener.

Create a drop-in such as:

```
# /etc/systemd/resolved.conf.d/90-local-dns.conf
[Resolve]
DNS=127.0.0.1
Domains=~.
DNSStubListener=no
```

Then restart resolved:

```
sudo systemctl restart systemd-resolved
```

That is the whole trick.

## What this does

`DNS=127.0.0.1` tells `systemd-resolved` to use your local DNS server as its system DNS server.

`DNSStubListener=no` disables the built-in stub listener on `127.0.0.53:53`, so your own DNS server can bind to port 53 without fighting resolved.

This setup does not throw away systemd's DNS routing model. It still lets `systemd-resolved` be the central resolver. Applications that use glibc lookups via NSS still work. Applications that talk to `systemd-resolved` over D-Bus or Varlink still work. Tools that inspect resolved's state still see a coherent DNS setup.

And split DNS can still work.

For example, Tailscale's MagicDNS integrates with `systemd-resolved` by adding DNS routing for the tailnet. That route is more specific than `~.`, so those names can still go where Tailscale wants them to go, while ordinary public DNS goes through your local resolver.

## A note about /etc/resolv.conf

If your distribution uses `nss-resolve`, normal libc lookups such as `getent hosts ...` go through `systemd-resolved` and keep the split-DNS behaviour.

Some older tools bypass NSS and read `/etc/resolv.conf` directly. If you disabled the stub listener, make sure `/etc/resolv.conf` is not still pointing only at `127.0.0.53`. Either point it at systemd's non-stub file, usually `/run/systemd/resolve/resolv.conf`, or make sure those direct DNS clients can reach your local DNS server on `127.0.0.1`.

The important bit: split DNS needs the query to go through `systemd-resolved`. A tool that bypasses resolved is also bypassing resolved's routing logic. That is not a bug in this setup; it is just that tool being old-fashioned in the usual inconvenient way.

## Make sure glibc actually uses systemd-resolved

One extra thing: install the NSS module for systemd-resolved.

```sh
sudo apt install libnss-resolve

```

That package makes normal glibc lookups go through systemd-resolved via NSS. In other words, applications using `getaddrinfo()` get the same DNS routing behaviour as tools that talk to resolved over D-Bus or Varlink.

This matters when other software plugs into systemd-resolved. Tailscale MagicDNS is the obvious example: Tailscale automatically adds the right split-DNS route to resolved, but your normal applications only benefit from that route if their libc lookups actually go through resolved.

Without `libnss-resolve`, some systems still appear to work for simple public DNS while silently bypassing the interesting part: resolved's per-link and split-DNS routing.

## Quick checks

After restarting things, check resolved:

```
resolvectl status
```

You should see `127.0.0.1` as a DNS server and `~.` as a routing domain.

Then test both the resolved path and the normal libc path:

```
resolvectl query example.com
getent hosts example.com
```

If both work, you have the useful version of this setup: a real local DNS server, without throwing away `systemd-resolved`'s integration with the rest of the system.

Small config. Much less swearing next time.

## References

- `systemd-resolved.service(8)`: [https://man7.org/linux/man-pages/man8/systemd-resolved.service.8.html](https://man7.org/linux/man-pages/man8/systemd-resolved.service.8.html?ref=frank.be)
- `resolved.conf(5)`: [https://man7.org/linux/man-pages/man5/resolved.conf.5.html](https://man7.org/linux/man-pages/man5/resolved.conf.5.html?ref=frank.be)
- systemd-resolved and VPN DNS routing: [https://systemd.io/RESOLVED-VPNS/](https://systemd.io/RESOLVED-VPNS/?ref=frank.be)