On an Armbian machine used as a bypass router, both sing-box and WireGuard are running side by side. The router itself resolves internal domain names fine, yet downstream clients that point at it as their DNS server time out every time. The root cause turned out not to be the DNS matching rules, but rather that the traffic sing-box sends to its upstream DNS was choosing the wrong egress interface.

All addresses in this article are rewritten illustrative values, not the original network configuration; the topology relationships and protocol behavior of the private network are preserved.

Topology and Symptoms

RoleIllustrative AddressPurpose
Downstream client192.168.10.20DNS client
Bypass router LAN192.168.10.254 / eth0Address where sing-box DNS listens
Bypass router WireGuard10.77.0.11 / wg0Tunnel client
Internal DNS10.77.0.1:53DNS server on the WireGuard peer

The topology looks like this:

Client 192.168.10.20
  │ DNS query
  ▼
Bypass router 192.168.10.254:53 (sing-box DNS inbound)
  │ Expected: query 10.77.0.1:53 out through wg0
  ▼
WireGuard peer DNS 10.77.0.1:53

The internal test domain used is service.home.arpa (illustrative). The observed symptoms:

  • The router itself resolves that domain normally;
  • When a downstream client explicitly sets the router’s LAN IP as its DNS server, queries time out consistently;
  • sing-box is listening on UDP 53, and the corresponding inbound routing rules have been confirmed correct.

Step 1: Confirm the Inbound Request Arrives

Capture the downstream client’s DNS traffic on the bypass router:

sudo tcpdump -i eth0 -nn -K -l \
  'host 192.168.10.20 and udp port 53'

You will see requests carrying the same DNS Transaction ID retrying every few seconds, with no matching successful response. This tells you the client did hand the query to the router, but the processing chain never completed.

Check the listening port:

sudo ss -lnup | grep ':53 '

This confirms sing-box is listening on UDP 53 at the bypass router’s LAN IP.

Step 2: Observe the LAN and WireGuard Egresses Separately

Don’t start by dumping all DNS traffic. Open two terminals and watch each path separately:

# LAN: only this downstream client
sudo tcpdump -i eth0 -nn -K -l 'host 192.168.10.20 and udp port 53'
# WireGuard: only the target DNS server
sudo tcpdump -i wg0 -nn -K -l 'host 10.77.0.1 and udp port 53'

Then issue one explicit query from the downstream client:

nslookup -type=A service.home.arpa 192.168.10.254

What turned up in this case: the downstream request did arrive on the LAN, but sing-box’s new queries to the DNS upstream left from eth0 with the LAN address as the source, instead of leaving from wg0 with the WireGuard IP.

Key distinction: a DNS rule that “picks a particular upstream server” and the OS/application decision of “which interface to use to reach that server” are two separate things. Even when the rule matches correctly, the request can still time out if the actual egress path is wrong.

Step 3: Bind an Interface for the sing-box DNS Upstream

The upstream configuration at the time of the failure looked like this:

{
  "type": "udp",
  "tag": "internal-dns",
  "server": "10.77.0.1"
}

After the fix:

{
  "type": "udp",
  "tag": "internal-dns",
  "server": "10.77.0.1",
  "bind_interface": "wg0"
}

bind_interface applies to the network dial for that DNS server, making it use the specified interface. In an environment with TUN, policy routing, and WireGuard, this is more explicit than relying on the system’s automatic path selection.

Check the configuration and restart the service (adjust the path to your own configuration file):

sudo sing-box check -c /etc/sing-box/config.json
sudo systemctl restart sing-box

Then query again and capture on wg0:

sudo tcpdump -i wg0 -nn -K -l 'host 10.77.0.1 and udp port 53'

You should see the request on wg0 along with its matching response. In this case, adding bind_interface restored normal downstream DNS resolution.

Side Observation: NXDOMAIN After a Successful A Record

During the investigation, another class of symptom came up: an internal name resolves through a CNAME to a single-label hostname, the A query returns an address normally, but a subsequent AAAA query for the final hostname returns NXDOMAIN. This is a separate resolution anomaly that still needs its own investigation — it should not be mistaken for the cause of the downstream query timeouts.

If you see something similar, run these separately:

dig @<DNS server address> service.home.arpa A
dig @<DNS server address> service.home.arpa AAAA

And check whether the CNAME points at an unexpected single-label name, and whether the local resolver and the upstream DNS return consistent results.

Troubleshooting Takeaways

  1. Capture packets segment by segment along the chain: client → bypass router inbound → upstream egress → upstream response → client response.
  2. Verify the actual egress interface and source IP of the packets; the server field in the DNS configuration alone tells you nothing.
  3. When you receive the client’s request but see no matching response, check the upstream egress first; there is no need to immediately modify routing rules that have already been verified as correct.
  4. If tcpdump reports bad udp cksum, it may be the result of NIC checksum offload on locally sent packets; -K disables checksum verification during capture.
  5. home.arpa is a special-purpose domain for home networks; do not publish real public domain names, device names, internal addresses, and tunnel topologies as-is on a public blog.

Conclusion: the final failure point was that sing-box’s upstream UDP queries to the internal DNS were leaving through the LAN interface instead of going over WireGuard. Setting bind_interface: "wg0" on the corresponding DNS upstream server fixed it.