Skip to content

Troubleshooting Process for Docker Container DNS Hijacking by Tailscale

The 3x-ui proxy suddenly started returning EOF for all requests. The DNS inside the container was pointing to Tailscale’s 100.100.100.100, but MagicDNS was not enabled, resulting in all public domain names returning a SERVFAIL.

Reading Time
7 min read
Published on
2026.09.16

In short

By default, Docker containers inherit the host machine’s DNS settings. When Tailscale is running on the host machine, even if MagicDNS is disabled, the container’s DNS will be pointed to 100.100.100.100, causing public domain name resolution to fail. You can work around this by explicitly specifying the DNS in the Compose file; however, any container that relies on external domain name resolution will be affected.

🔍 Troubleshooting Docker DNS Failures

Tailscale Hijacked the Container DNS, While MagicDNS Wasn’t Actually Enabled

One morning, all proxy connections via 3x-ui were lost, and the client logs looked like this.

claude.ai:443 error: blog.cassianflorin.com:443 connect error: EOF

[TCP] dial PROXY (match Match/) 127.0.0.1:55911 --> chat.openai.com:443 error: blog.cassianflorin.com:443 connect error: EOF

[TCP] dial PROXY (match Match/) 127.0.0.1:56096 --> claude.ai:443
  error: blog.cassianflorin.com:443 connect error: EOF

[TCP] dial PROXY (match Match/) 127.0.0.1:55911 --> chat.openai.com:443
  error: blog.cassianflorin.com:443 connect error: EOF

No matter which target I tried to access, the error stopped at the same point—unable to connect to blog.cassianflorin.com:443 , returning EOF. The domain name example.com used in the commands below refers to this one.

The Service Itself Is Working Fine

First, check if the container is still running.

2053/tcp

docker ps -a | grep xui
# 输出:3xui_app   Up 7 hours   127.0.0.1:2053->2053/tcp

It’s been running for 7 hours, and port mapping is working normally. Next, check the logs.

docker logs --tail 20 3xui_app
# 输出显示 x-ui 3.4.2 和 Xray 26.6.27 正常启动

No anomalies there either. The 3x-ui side uses externalProxy to disguise traffic as HTTPS requests to a public website. I went through the configuration and found no issues, but connecting still results in EOF.

So let’s try accessing that domain directly on the host machine.

curl -I https://example.com
# 输出:HTTP/1.1 301 Moved Permanently

openssl s_client -connect example.com:443 -servername example.com
# 输出:Verification: OK

Access from the host machine works perfectly, and the certificate is valid. At this point, we’ve ruled out the service, configuration, and network layers—there’s only one remaining difference: all previous tests were performed on the host machine.

Switch to Testing Inside the Container

Resolve the same domain name inside the container.

docker exec 3xui_app nslookup example.com
Server:    127.0.0.11
Address:   127.0.0.11:53

** server can't find example.com: SERVFAIL

The container cannot resolve it. Since the host machine can connect but the container cannot, the problem is narrowed down to the container’s DNS.

docker exec 3xui_app cat /etc/resolv.conf
nameserver 127.0.0.11
search tailxxxxx.ts.net
options ndots:0

# Based on host file: '/etc/resolv.conf' (internal resolver)
# ExtServers: [host(100.100.100.100) host(fd7a:xxxx:xxxx::xx)]
# Overrides: []

Putting these three pieces of information together explains the issue. The container uses Docker’s built-in resolver 127.0.0.11 , which forwards traffic to 100.100.100.100 , and the search domain is .ts.net . All three point to Tailscale.

Tailscale Is Running, but MagicDNS Is Disabled

systemctl status tailscaled
# 输出:active (running)

tailscale status --json | grep -i MagicDNS
{
  "MagicDNSSuffix": "example.ts.net",
  "MagicDNSEnabled": false
}

Tailscale is indeed running, and the address 100.100.100.100 is indeed configured, but MagicDNSEnabled is false . The container is sending all DNS queries to a server that isn’t operational, so naturally, all public domain name queries result in SERVFAIL.

Why the Container Is Affected but the Host Machine Isn’t

Here’s how DNS works in a Docker container. Inside the container, only the built-in resolver 127.0.0.11 is recognized. When the Docker daemon starts, it reads the host machine’s /etc/resolv.conf and extracts nameserver from it to use as the forwarding target. The query path is: container → 127.0.0.11 → the host machine’s DNS → the public internet.

Once Tailscale is installed, it injects its own DNS address into /etc/resolv.conf . This happens regardless of whether MagicDNS is enabled or disabled; the address remains even when the feature is turned off. Docker reads this address and passes it directly to the container, so the entire chain’s exit point becomes that non-functional DNS server.

The host machine avoids this issue because its /etc/resolv.conf typically contains more than one nameserver .

nameserver 8.8.8.8
nameserver 114.114.114.114

The system tries them one by one; if the first one is unreachable, it falls back to a backup. Docker’s inheritance logic works differently—I haven’t delved into exactly which one it selects—but judging by the results, it doesn’t follow this pattern of retrying each entry in turn.

Three Solutions

Explicitly specify a DNS for the container by modifying docker-compose.yml —this was the method I ultimately used.

services:
  3xui:
    # ... 其他配置
    dns:
      - 8.8.8.8
      - 114.114.114.114
    # ... 其他配置

docker run .

docker run -d \
  --name 3xui_app \
  --dns 8.8.8.8 \
  --dns 114.114.114.114 \
  # ... 其他参数
  3x-ui-3xui:latest

Change the Docker daemon’s default DNS by editing /etc/docker/daemon.json .

{
  "dns": ["8.8.8.8", "114.114.114.114"]
}
systemctl restart docker

This will affect all containers on this machine that do not have an explicitly configured DNS, so think carefully before making this change.

Make Tailscale’s DNS work properly if you still need it to resolve internal domain names.

tailscale set --accept-dns=true

Then configure forwarding rules in the Tailscale management console to route public domain name queries to the public DNS.

Verification

Restart the container and first check if the configuration has been updated.

docker exec 3xui_app cat /etc/resolv.conf
nameserver 127.0.0.11
options ndots:0

# ExtServers: [8.8.8.8 114.114.114.114]
# Overrides: [nameservers]

ExtServers now points to the public DNS, and Overrides now includes nameservers , indicating that the explicit configuration has taken effect. Try resolving the domain again.

docker exec 3xui_app nslookup google.com
Server:    127.0.0.11
Address:   127.0.0.11:53

Non-authoritative answer:
Name:   google.com
Address: 142.250.xxx.xxx

is accessible, and the proxy has also been restored.

A Few Additional Notes

127.0.0.11 is the built-in DNS that Docker runs in every custom network. It handles container-to-container communication via container names and forwards external domain name queries, caching them in the process. Therefore, the nameserver seen inside a container is always this built-in DNS; the actual upstream server can be found in the ExtServers line.

This issue doesn’t just affect the proxy. Any container that needs to resolve external domain names will be affected—clients calling external APIs, email services sending messages, and monitoring systems pushing alerts to webhooks—all exhibit the same symptom: sudden connectivity loss while the services themselves appear to be functioning normally.

As for why the problem occurred on that particular day, I haven’t been able to pinpoint the cause. It’s possible that Tailscale was restarted, the configuration was re-inherited when the container was rebuilt, or the MagicDNS settings in the background were accidentally changed—but the logs aren’t detailed enough to determine the exact cause.

There’s no need to uninstall Tailscale itself. It works well for internal network connectivity; you just need to explicitly configure the container’s DNS settings.

Lessons Learned

Don’t rely on production containers to inherit the correct DNS settings. Hard-code the dns field—it’s a one-line fix.

To detect similar issues sooner, you can have the health check include a DNS resolution test.

services:
  app:
    healthcheck:
      test: ['CMD', 'nslookup', 'google.com']
      interval: 30s
      timeout: 10s
      retries: 3

The part that took the most time this time was that the entire hour or so was spent testing on the host machine. The service was running, the port was open, the certificate was valid, and the domain was accessible—everything was normal. The only thing no one thought to do was test it again from inside the container.


Keep reading