[GH-ISSUE #3148] [5.0.1.5][5.0.3.0] broken AmneziaDNS #1459

Open
opened 2026-10-13 20:42:53 +00:00 by bsakaguchi · 3 comments
Owner

Originally created by @shellgame323 on GitHub (Sep 8, 2026).
Original GitHub issue: https://github.com/amnezia-vpn/amnezia-client/issues/3148

Describe the bug
AmneziaDNS doesn't work when set up from the 5.0.1.5 client. Installing AmneziaDNS from the older 5.0.0.5 client works normally.

To Reproduce
Steps to reproduce the behavior:

  1. Open the 5.0.1.5 client
  2. Click 'add self-hosted vpn'
  3. Add your server. Choose Manual XRay installation.
  4. Add AmneziaDNS service
  5. Enable 'use AmneziaDNS' in connection settings
  6. Click 'connect'. It will connect normally.
  7. Try to visit any website while connected. It doesn't load.
  8. Turn off 'use AmneziaDNS' in connection settings.
  9. Visit any website. It works now.

Expected behavior
AmneziaDNS was expected to work normally, like in installations made via the 5.0.0.5 client.

Desktop:

  • OS: Linux Fedora
  • Version: 44

Server:

  • OS: Ubuntu 24.04

Additional context
I tried this on multiple different servers. AmneziaDNS installation via the 5.0.1.5 client doesn't work. Installation via the older 5.0.0.5 client does work. I was able to use AmneziaDNS for some time after installing via 5.0.0.5 then switching to 5.0.1.5
Apps that bypass vpn traffic are still unable to load websites, which further proves that it is an issue with the DNS.

Originally created by @shellgame323 on GitHub (Sep 8, 2026). Original GitHub issue: https://github.com/amnezia-vpn/amnezia-client/issues/3148 **Describe the bug** AmneziaDNS doesn't work when set up from the 5.0.1.5 client. Installing AmneziaDNS from the older 5.0.0.5 client works normally. **To Reproduce** Steps to reproduce the behavior: 1. Open the 5.0.1.5 client 2. Click 'add self-hosted vpn' 3. Add your server. Choose Manual XRay installation. 4. Add AmneziaDNS service 5. Enable 'use AmneziaDNS' in connection settings 5. Click 'connect'. It will connect normally. 6. Try to visit any website while connected. It doesn't load. 7. Turn off 'use AmneziaDNS' in connection settings. 8. Visit any website. It works now. **Expected behavior** AmneziaDNS was expected to work normally, like in installations made via the 5.0.0.5 client. **Desktop:** - OS: Linux Fedora - Version: 44 **Server:** - OS: Ubuntu 24.04 **Additional context** I tried this on multiple different servers. AmneziaDNS installation via the 5.0.1.5 client doesn't work. Installation via the older 5.0.0.5 client does work. I was able to use AmneziaDNS for some time after installing via 5.0.0.5 then switching to 5.0.1.5 Apps that bypass vpn traffic are still unable to load websites, which further proves that it is an issue with the DNS.
Author
Owner

@HeisLuka commented on GitHub (Sep 9, 2026):

I compared the server deployment paths in 5.0.0.5, 5.0.1.5, and current dev.

There is a useful narrowing here, although I don't think the source alone proves the exact regression trigger yet.

The failure is strongly localized to the server deployment

The most useful control in this report is:

DNS deployed by 5.0.0.5
+
client later upgraded to 5.0.1.5
=
AmneziaDNS still works

while:

fresh XRay + DNS deployment by 5.0.1.5
=
VPN connects
AmneziaDNS selected
DNS traffic fails

That makes a purely client-side DNS-selection regression unlikely.

Something in the server generation matters.

The AmneziaDNS scripts themselves did not change

I compared these files between tags 5.0.0.5 and 5.0.1.5.

client/server_scripts/dns/Dockerfile has the same SHA in both releases:

861fa45c59a20bbe97ad11808778700ec6d5a25d

client/server_scripts/build_container.sh is also identical:

b996237fcccd766e3953d5b6194424eed8fa2315

and still does:

docker build --no-cache --pull ...

The DNS container run script is also unchanged and places the resolver at:

172.29.172.254

on:

amnezia-dns-net

The host network preparation is identical too:

bridge: amn0
subnet: 172.29.172.0/24
network: amnezia-dns-net

and the shared host firewall script also has the same content between these releases.

So I would not currently classify this as a source-level regression in the AmneziaDNS shell scripts.

latest is still a reproducibility issue, but does not prove this regression

The DNS Dockerfile currently begins with:

FROM mvance/unbound:latest

combined with:

docker build --no-cache --pull

This means a DNS deployment is not reproducible from the Amnezia client version alone.

The exact upstream image digest used during installation is not recorded in the Amnezia server configuration.

That should probably be fixed independently by pinning an immutable version/digest and recording the deployed digest.

However, without the image IDs/digests from a working old deployment and a failing new one, I don't think it is safe to claim that an upstream Unbound image change caused this particular regression.

The important server-side version boundary is XRay

The XRay Dockerfile changed substantially between these releases.

5.0.0.5 deploys:

Xray-core v25.8.3

while 5.0.1.5 deploys:

Xray-core v26.7.28

The XRay setup architecture changed at the same boundary.

In 5.0.0.5, xray/configure_container.sh generates the initial server.json.

In 5.0.1.5, that script only generates the key material and the client writes the server configuration through:

XrayConfigurator::writeServerConfigForSetup()

I compared the ordinary RAW/TCP + REALITY + Vision path.

The obvious configuration shape is still substantially equivalent:

VLESS
network = tcp
security = reality
flow = xtls-rprx-vision
outbound = freedom

Also, Xray-core's Vision UDP path is not simply removed in 26.7.28: both the older and newer cores contain the XUDP/Mux path used to transport UDP with Vision.

So I would keep:

Xray-core 25.8.3 -> 26.7.28

and:

old shell-generated server.json
-> new C++-generated server.json

as the remaining high-value regression boundary, but not call either one the root cause without an A/B test.

There is a separate source-confirmed bug: DNS installation has no data-plane success condition

Current InstallController::setupContainer() performs essentially:

prepare host
build image
run container
configure container
configure firewall
startup
-> success

For DockerContainer::Dns there is no DNS-specific installer/validator.

The DNS container's configure_container.sh is empty, and run_container.sh only starts the container in detached mode:

docker run -d \
    --restart always \
    --network amnezia-dns-net \
    --ip=172.29.172.254 \
    ...

I don't see any installation postcondition such as:

container is actually running
UDP 53 is listening
TCP 53 is listening
DNS request to 172.29.172.254 succeeds
DNS request succeeds from the VPN container namespace

So currently:

docker deployment command succeeded

can be promoted to:

AmneziaDNS is installed and usable

without proving the second statement.

That failed deployment is then made authoritative

For a self-hosted admin profile, current getDnsPair() checks:

const bool dnsOnServer =
    containers.contains(DockerContainer::Dns);

and when AmneziaDNS is enabled it selects:

172.29.172.254

as the primary resolver.

So the complete failure mechanism can be:

DNS container deployment appears successful
        ↓
Dns container is recorded as installed
        ↓
no DNS data-plane check is performed
        ↓
user enables AmneziaDNS
        ↓
effective DNS becomes 172.29.172.254
        ↓
resolver/path is actually dead
        ↓
VPN transport still works
but all normal hostname resolution fails

That part is actionable independently of what caused the 5.0.1.5 regression.

Fastest way to isolate the actual regression

On one failing 5.0.1.5 deployment I would test four layers separately.

A. Test the DNS container itself

Verify:

container running
UDP 53 listening
TCP 53 listening

and perform a direct query to:

172.29.172.254

from amnezia-dns-net.

If this fails, the problem is inside the DNS image/container/configuration.

B. Test from the XRay container

Run the same DNS query from the XRay container/network namespace.

Expected:

XRay container
-> amnezia-dns-net
-> 172.29.172.254:53
-> valid DNS response

If A succeeds and B fails, the problem is Docker networking/container attachment rather than Unbound itself.

C. Test through VLESS directly

While connected, query:

@172.29.172.254

explicitly through the VPN.

If A and B succeed but this fails, the problem is narrowed to:

client
-> VLESS/XUDP
-> server XRay
-> 172.29.172.254

This is where the 25.8.3 -> 26.7.28 A/B becomes particularly valuable.

D. Test ordinary OS resolution

If explicit DNS through the tunnel succeeds but normal applications still fail, only then continue into client DNS application/routing.

So the discriminator is:

A fails
    -> DNS container/runtime

A passes, B fails
    -> Docker network attachment/path

A+B pass, C fails
    -> VLESS/XRay UDP data plane

A+B+C pass, normal DNS fails
    -> client DNS configuration/routing

A very useful A/B test

Because the report already demonstrates:

new client + old server deployment = working

I would keep the client constant and vary only server XRay:

same host
same AmneziaDNS image digest
same DNS container
same VLESS/REALITY parameters

Xray-core 25.8.3
vs
Xray-core 26.7.28

Then perform only:

DNS query to 172.29.172.254 through VLESS

If 25.8.3 works and 26.7.28 fails with everything else identical, the regression is localized to XRay-core.

If both work, compare the normalized effective server.json produced by the two Amnezia generations next.

That would avoid conflating:

client version
DNS image
Docker networking
XRay version
generated XRay config

in one test.

Suggested product invariant

Installing AmneziaDNS should be transactional:

build/run
-> verify container running
-> verify resolver listening
-> query resolver through intended server network path
-> only then mark DNS generation usable

And enabling AmneziaDNS should not make 172.29.172.254 authoritative merely because a Dns container entry exists.

The stronger invariant is:

useAmneziaDns
AND
Dns container installed
AND
resolver health verified

Regression tests

  1. Fresh DNS install:

    • real query through amnezia-dns-net must succeed before install returns success.
  2. DNS container starts but UDP/TCP 53 is unavailable:

    • installation must fail or remain unhealthy.
  3. DNS answers locally but XRay container cannot reach it:

    • installation/activation must report the server-network failure.
  4. DNS becomes unhealthy:

    • do not silently keep advertising 172.29.172.254 as a known-good resolver.
  5. Reproduce the same DNS image/config with XRay 25.8.3 and 26.7.28.

  6. Compare normalized generated server.json between 5.0.0.5 and 5.0.1.5 with secrets removed.

  7. Pin the AmneziaDNS base image to an immutable version/digest and expose the deployed digest in diagnostics.

So my current classification would be:

the regression is confirmed and strongly localized to the fresh server deployment, but the exact 5.0.1.5 trigger still needs one runtime discriminator.

Separately, there is a source-confirmed lifecycle bug: AmneziaDNS can be promoted as installed and selected as the authoritative resolver without any DNS data-plane health check.

<!-- gh-comment-id:5608564636 --> @HeisLuka commented on GitHub (Sep 9, 2026): I compared the server deployment paths in `5.0.0.5`, `5.0.1.5`, and current `dev`. There is a useful narrowing here, although I don't think the source alone proves the exact regression trigger yet. ### The failure is strongly localized to the server deployment The most useful control in this report is: ```text DNS deployed by 5.0.0.5 + client later upgraded to 5.0.1.5 = AmneziaDNS still works ``` while: ```text fresh XRay + DNS deployment by 5.0.1.5 = VPN connects AmneziaDNS selected DNS traffic fails ``` That makes a purely client-side DNS-selection regression unlikely. Something in the server generation matters. ### The AmneziaDNS scripts themselves did not change I compared these files between tags `5.0.0.5` and `5.0.1.5`. `client/server_scripts/dns/Dockerfile` has the same SHA in both releases: ```text 861fa45c59a20bbe97ad11808778700ec6d5a25d ``` `client/server_scripts/build_container.sh` is also identical: ```text b996237fcccd766e3953d5b6194424eed8fa2315 ``` and still does: ```sh docker build --no-cache --pull ... ``` The DNS container run script is also unchanged and places the resolver at: ```text 172.29.172.254 ``` on: ```text amnezia-dns-net ``` The host network preparation is identical too: ```text bridge: amn0 subnet: 172.29.172.0/24 network: amnezia-dns-net ``` and the shared host firewall script also has the same content between these releases. So I would not currently classify this as a source-level regression in the AmneziaDNS shell scripts. ### `latest` is still a reproducibility issue, but does not prove this regression The DNS Dockerfile currently begins with: ```dockerfile FROM mvance/unbound:latest ``` combined with: ```sh docker build --no-cache --pull ``` This means a DNS deployment is not reproducible from the Amnezia client version alone. The exact upstream image digest used during installation is not recorded in the Amnezia server configuration. That should probably be fixed independently by pinning an immutable version/digest and recording the deployed digest. However, without the image IDs/digests from a working old deployment and a failing new one, I don't think it is safe to claim that an upstream Unbound image change caused this particular regression. ### The important server-side version boundary is XRay The XRay Dockerfile changed substantially between these releases. `5.0.0.5` deploys: ```text Xray-core v25.8.3 ``` while `5.0.1.5` deploys: ```text Xray-core v26.7.28 ``` The XRay setup architecture changed at the same boundary. In `5.0.0.5`, `xray/configure_container.sh` generates the initial `server.json`. In `5.0.1.5`, that script only generates the key material and the client writes the server configuration through: ```text XrayConfigurator::writeServerConfigForSetup() ``` I compared the ordinary RAW/TCP + REALITY + Vision path. The obvious configuration shape is still substantially equivalent: ```text VLESS network = tcp security = reality flow = xtls-rprx-vision outbound = freedom ``` Also, Xray-core's Vision UDP path is not simply removed in 26.7.28: both the older and newer cores contain the XUDP/Mux path used to transport UDP with Vision. So I would keep: ```text Xray-core 25.8.3 -> 26.7.28 ``` and: ```text old shell-generated server.json -> new C++-generated server.json ``` as the remaining high-value regression boundary, but not call either one the root cause without an A/B test. ### There is a separate source-confirmed bug: DNS installation has no data-plane success condition Current `InstallController::setupContainer()` performs essentially: ```text prepare host build image run container configure container configure firewall startup -> success ``` For `DockerContainer::Dns` there is no DNS-specific installer/validator. The DNS container's `configure_container.sh` is empty, and `run_container.sh` only starts the container in detached mode: ```sh docker run -d \ --restart always \ --network amnezia-dns-net \ --ip=172.29.172.254 \ ... ``` I don't see any installation postcondition such as: ```text container is actually running UDP 53 is listening TCP 53 is listening DNS request to 172.29.172.254 succeeds DNS request succeeds from the VPN container namespace ``` So currently: ```text docker deployment command succeeded ``` can be promoted to: ```text AmneziaDNS is installed and usable ``` without proving the second statement. ### That failed deployment is then made authoritative For a self-hosted admin profile, current `getDnsPair()` checks: ```cpp const bool dnsOnServer = containers.contains(DockerContainer::Dns); ``` and when AmneziaDNS is enabled it selects: ```text 172.29.172.254 ``` as the primary resolver. So the complete failure mechanism can be: ```text DNS container deployment appears successful ↓ Dns container is recorded as installed ↓ no DNS data-plane check is performed ↓ user enables AmneziaDNS ↓ effective DNS becomes 172.29.172.254 ↓ resolver/path is actually dead ↓ VPN transport still works but all normal hostname resolution fails ``` That part is actionable independently of what caused the 5.0.1.5 regression. ### Fastest way to isolate the actual regression On one failing `5.0.1.5` deployment I would test four layers separately. #### A. Test the DNS container itself Verify: ```text container running UDP 53 listening TCP 53 listening ``` and perform a direct query to: ```text 172.29.172.254 ``` from `amnezia-dns-net`. If this fails, the problem is inside the DNS image/container/configuration. #### B. Test from the XRay container Run the same DNS query from the XRay container/network namespace. Expected: ```text XRay container -> amnezia-dns-net -> 172.29.172.254:53 -> valid DNS response ``` If A succeeds and B fails, the problem is Docker networking/container attachment rather than Unbound itself. #### C. Test through VLESS directly While connected, query: ```text @172.29.172.254 ``` explicitly through the VPN. If A and B succeed but this fails, the problem is narrowed to: ```text client -> VLESS/XUDP -> server XRay -> 172.29.172.254 ``` This is where the `25.8.3 -> 26.7.28` A/B becomes particularly valuable. #### D. Test ordinary OS resolution If explicit DNS through the tunnel succeeds but normal applications still fail, only then continue into client DNS application/routing. So the discriminator is: ```text A fails -> DNS container/runtime A passes, B fails -> Docker network attachment/path A+B pass, C fails -> VLESS/XRay UDP data plane A+B+C pass, normal DNS fails -> client DNS configuration/routing ``` ### A very useful A/B test Because the report already demonstrates: ```text new client + old server deployment = working ``` I would keep the client constant and vary only server XRay: ```text same host same AmneziaDNS image digest same DNS container same VLESS/REALITY parameters Xray-core 25.8.3 vs Xray-core 26.7.28 ``` Then perform only: ```text DNS query to 172.29.172.254 through VLESS ``` If 25.8.3 works and 26.7.28 fails with everything else identical, the regression is localized to XRay-core. If both work, compare the normalized effective `server.json` produced by the two Amnezia generations next. That would avoid conflating: ```text client version DNS image Docker networking XRay version generated XRay config ``` in one test. ### Suggested product invariant Installing AmneziaDNS should be transactional: ```text build/run -> verify container running -> verify resolver listening -> query resolver through intended server network path -> only then mark DNS generation usable ``` And enabling AmneziaDNS should not make `172.29.172.254` authoritative merely because a `Dns` container entry exists. The stronger invariant is: ```text useAmneziaDns AND Dns container installed AND resolver health verified ``` ### Regression tests 1. Fresh DNS install: * real query through `amnezia-dns-net` must succeed before install returns success. 2. DNS container starts but UDP/TCP 53 is unavailable: * installation must fail or remain unhealthy. 3. DNS answers locally but XRay container cannot reach it: * installation/activation must report the server-network failure. 4. DNS becomes unhealthy: * do not silently keep advertising `172.29.172.254` as a known-good resolver. 5. Reproduce the same DNS image/config with XRay 25.8.3 and 26.7.28. 6. Compare normalized generated `server.json` between 5.0.0.5 and 5.0.1.5 with secrets removed. 7. Pin the AmneziaDNS base image to an immutable version/digest and expose the deployed digest in diagnostics. So my current classification would be: **the regression is confirmed and strongly localized to the fresh server deployment, but the exact 5.0.1.5 trigger still needs one runtime discriminator.** Separately, there is a source-confirmed lifecycle bug: **AmneziaDNS can be promoted as installed and selected as the authoritative resolver without any DNS data-plane health check.**
Author
Owner

@shellgame323 commented on GitHub (Sep 21, 2026):

I tested with 5.0.3.0, it's still broken.

Also I have more details on the issue: it doesn't matter which version of the client AmneziaDNS was installed from. The only thing that matters is what version the XRay was installed from. When XRay is installed from 5.0.1.5 or 5.0.3.0, AmneziaDNS is broken. When XRay is installed from 5.0.0.5, everything works.

As @HeisLuka pointed out, the version of XRay changed between releases significantly, so I suppose that's where the problem is localized.

<!-- gh-comment-id:5767123026 --> @shellgame323 commented on GitHub (Sep 21, 2026): I tested with 5.0.3.0, it's still broken. Also I have more details on the issue: it doesn't matter which version of the client AmneziaDNS was installed from. The only thing that matters is what version the XRay was installed from. When XRay is installed from 5.0.1.5 or 5.0.3.0, AmneziaDNS is broken. When XRay is installed from 5.0.0.5, everything works. As @HeisLuka pointed out, the version of XRay changed between releases significantly, so I suppose that's where the problem is localized.
Author
Owner

@ArconWell commented on GitHub (Sep 22, 2026):

Data point from a Windows 11 + iOS setup (server XRay+DNS deployed by client 5.0.2.1, i.e. the new generation): I ran the layered A/B/C test proposed above. A and B pass. C also passes — but only from the iOS client: server-side tcpdump shows its queries reaching 172.29.172.254 through the tunnel and being answered instantly. From the Windows client, identical queries never arrive at the server at all (canary domain absent from the capture), while UDP to public DNS through the same tunnel works. So at least in my deployment the new-generation server path is healthy, and the failure is inside the Windows client chain. Full diagnosis with captures: #3198

<!-- gh-comment-id:5780095434 --> @ArconWell commented on GitHub (Sep 22, 2026): Data point from a Windows 11 + iOS setup (server XRay+DNS deployed by client 5.0.2.1, i.e. the new generation): I ran the layered A/B/C test proposed above. A and B pass. C also passes — but only from the iOS client: server-side tcpdump shows its queries reaching 172.29.172.254 through the tunnel and being answered instantly. From the Windows client, identical queries never arrive at the server at all (canary domain absent from the capture), while UDP to public DNS through the same tunnel works. So at least in my deployment the new-generation server path is healthy, and the failure is inside the Windows client chain. Full diagnosis with captures: #3198
Sign in to join this conversation.
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
amnezia-vpn/amnezia-client#1459
No description provided.