Clients on the tunnel can reach the gateway, and the gateway can reach the internal resolver, so DNS ought to work. It does not, because those are two different permissions and a working DNS service is a third thing. The listener has to exist on an address the clients can reach, the firewall has to allow queries to that address, and the client has to be told to use it. Opening the tunnel in both directions solves the first two by accident and leaves the third undone.

The listener is a local service with two directions

Inbound, a client sends a query to the gateway's tunnel address on port 53. Outbound, the daemon forwards that query to its configured upstream. Both halves have to work, and they fail in different ways.

A timeout reported by the client and a timeout seen on the gateway are not the same fault.

Bind the tunnel address, not every address

A dedicated dnsmasq instance for this job needs an explicit listen address, a bind mode that tolerates the tunnel appearing after start, and one clear upstream. This is the smallest version I use:

listen-address=127.0.0.1,10.90.0.1
bind-dynamic
no-resolv
no-hosts
server=192.168.50.53

bind-dynamic binds the individual addresses and copes with tun0 appearing later, which matters on a host where the VPN service restarts. Listing loopback keeps local tools working without adding a public listener. no-resolv stops the daemon inheriting upstreams from the host's resolver configuration, and no-hosts keeps this instance from publishing the gateway's hosts file. None of those options grants anyone permission to query the service.

Check for an existing port-53 listener before you start a second daemon, and use the distribution's configuration owner rather than a stray file in the wrong directory.

Allow DNS input, not tunnel transit

The firewall rules that make this work are local-input rules scoped to the tunnel interface, the client pool, the gateway address and port 53. Two rules, one per protocol, are enough:

sudo ufw allow in on tun0 proto udp from 10.90.0.0/24 to 10.90.0.1 port 53
sudo ufw allow in on tun0 proto tcp from 10.90.0.0/24 to 10.90.0.1 port 53

DNS runs over TCP as well as UDP, so a UDP-only rule leaves large answers failing in a way that looks intermittent. These are not ufw route permissions, and they must not become a broad allow on tun0. Order matters as much as content: an earlier rule that drops all input from tun0 still wins, and appending an allowance below a terminal drop creates nothing.

If gateway DNS is now part of the design, revise that earlier policy on purpose and record why.

The upstream half is a separate path

Queries leave the gateway from its own address toward the upstream resolver, so the gateway's route and output policy toward that resolver have to allow the exchange. That is a different rule from the one that lets clients reach the listener, and it fails with a different symptom.

Do not add unrestricted tunnel output to make replies work; tracked replies do not need it.

Push the option, then check the handoff

The server can advertise the resolver to clients with a pushed option. That is an option, not an instruction the operating system must obey:

push "dhcp-option DNS 10.90.0.1"

A Linux command-line client may need its connection manager's resolver integration, and a managed device may need its management profile updated. Confirm the active resolver on the client after connecting instead of assuming the push was applied. When two mechanisms both try to set DNS, you get a race that presents as flapping resolution.

One owner for the client's resolver configuration is the goal, and the push is usually that owner only when the client platform agrees.

Test what you actually built

Query one private fully qualified name and one permitted external name directly against the tunnel address, over both UDP and TCP. Then repeat through the application's ordinary lookup path.

If the direct queries work and the application still fails, the fault is in the handoff rather than on the server:

  • The client never applied the pushed option.
  • Another resolver was set later and won the race.
  • The application uses its own resolver library or a cached answer.

Finish by confirming that a source outside the approved VPN path cannot use the listener at all. Successful resolution does not authorise the address it returned, so keep those two questions separate.

ተዛማጅ ጽሑፎች