Nixos-container networking NAT icmp_seq=1 Destination Host Unreachable

Hey hey,

I’m redoing my nixos-container to work with mullvad vpn (via services.mullvad), coming from openvpn (protonvpn). I’m running into some trouble getting privateNetwork to work with the container, and it seems like i’m not the only one: (1) (2).

The wiki leads me to believe that my configuration should be enough:

Outside container:

networking = {
    nat = {
      enable = true;
      internalInterfaces = [ "ve-+" ];   # packets coming from ve-* interfaces
      externalInterface = "wg0-mullvad"; # will be translated to VPN thru NAT
    };
    # don't remeber why i had this set, but makes no difference
    # networkmanager.unmanaged = [ "interface-name:ve-*" ];
  };
  <snip>

Inside container:

 containers.pirateship = {
    autoStart = true;
    privateNetwork = true; # needed for vpn
    hostAddress = "192.168.100.10";  # address of the container in pantheon
    localAddress = "192.168.100.11"; # address of pantheon in the container
    <snip>

However,
[root@pirateship:~]# ping 1.1.1.1

PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data.
From 192.168.100.11 icmp_seq=1 Destination Host Unreachable

disabling privateNetwork makes everything connect, and this worked with my earlier openvpn setup.

I don’t know enough about networking to be able to diagnose what’s going wrong, and there seems to be nothing relevant in any logs AFAICT. Anyone know what’s going wrong, or some way to debug this? Any suggestions are welcome :slight_smile:

1 Like

You can verify where packets are stuck with tcpdump host 1.1.1.1 and icmp, you can execute this command on every step of your machine (e.g. in the container, and outside of the container, while executing pings in the background). Maybe your Rounting is incorrect. You can verify you rounting by executing ip r get 1.1.1.1 inside the container and see via which interface it is trying to escape your container. Maybe your NAT or Forwarding Rules are broken, by executing the tcpdump commands on the host machine and verifing packages are traveling correctly.

First of all make sure your Firewall rules are correct, since it could be blocked by the firewall. I recommend the pwru utility, that takes tcpdump like instructions.

1 Like

For networking.networkmanager.unmanaged = [ "interface-name:ve-*" ];, it came from nixos manual in the nixos container section.

Not expert here, I also configured nixos-container with wiki’s first block config like yours, but it only let me access services from the host to the containers, but not the other way around.
I tried suggestion from here, but still

I can successfully let container to ping 1.1.1.1 with the bridge setup, also from the nixos wiki, you may try it.
Also I have to explicitly set container localaddress like this in the Additional context to make it work, some thing like this.

networking.interfaces.eth0.ipv4.addresses = [
  {
    address = "192.168.1.11";
    prefixLength = 24;
  }
];
1 Like

Hey, thanks for your response!

running
$ sudo tcpdump host 1.1.1.1 and icmp

tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on enp5s0, link-type EN10MB (Ethernet), snapshot length 262144 bytes

hangs in both the host and container…

jack@pantheon $ ip r get 1.1.1.1

1.1.1.1 dev wg0-mullvad table 1836018789 src 10.150.32.207 uid 1000 
    cache

The following does look weird to me, it looks like 1.1.1.1 gets routed through 192.168.100.10, which is containers.pirateship.hostAddress. I believe that this is the address of the container in the host? Do i need to swap localAddress and hostAddress?

[root@pirateship:~]# ip r get 1.1.1.1

1.1.1.1 via 192.168.100.10 dev eth0 src 192.168.100.11 uid 0 
    cache

should 192.168.100.10 be 192.168.100.11 instead?
[root@pirateship:~]# ping 192.168.100.11

PING 192.168.100.11 (192.168.100.11) 56(84) bytes of data.
64 bytes from 192.168.100.11: icmp_seq=1 ttl=64 time=0.045 ms

this address is reachable.

pwru fails in the container as well
[root@pirateship:~]# pwru

2026/05/11 10:51:16 WARN Failed to retrieve available ftrace functions (is /sys/kernel/tracing or /sys/kernel/debug/tracing mounted?) error="failed to open: open /sys/kernel/debug/tracing/available_filter_functions: no such file or directory"
2026/05/11 10:51:16 ERROR Failed to run pwru error="Cannot find a matching kernel function"

but disabling the firewall in the container makes no difference, so i’m going to assume that’s not the issue here.

following the bridge setup from the wiki also yields no success.: (full config)

  networking = {
    nat = {
      enable = true;
      internalInterfaces = [ "ve-+" ];   # packets coming from ve-* interfaces
      externalInterface = "wg0-mullvad"; # will be translated to VPN thru NAT
    };
    bridges.br0.interfaces = ["eth0"];
    # Get bridge-ip with DHCP
    useDHCP = false;
    interfaces."br0".useDHCP = true;

    # Set bridge-ip static
    interfaces."br0".ipv4.addresses = [{
      address = "192.168.100.3";
      prefixLength = 24;
    }];
    defaultGateway = "192.168.100.1";
    nameservers = [ "192.168.100.1" ];
  };
  ...
  containers.pirateship = {
    autoStart = true;
    privateNetwork = true; # needed for vpn
    # hostAddress = "192.168.100.10";  # address of the container in pantheon
    hostBridge = "br0";
    localAddress = "192.168.100.5/24"; # address of pantheon in the container
    ...
systemd[1]: Starting Container 'pirateship'...
systemd-nspawn[310040]: ░ Spawning container pirateship on /var/lib/nixos-containers/pirateship.
systemd-nspawn[310040]: Failed to add interface vb-pirateship to bridge br0: No such device
systemd-nspawn[310051]: Parent died too early
systemd[1]: container@pirateship.service: Main process exited, code=exited, status=1/FAILURE
systemd[1]: container@pirateship.service: Failed with result 'exit-code'.
systemd[1]: Failed to start Container 'pirateship'.

It probably hangs because it is trying to resolve your ip addresses via DNS, try sudo tcpdump -ni any host 1.1.1.1 and icmpin the container and on the host. Ping to 192.168.100.11 is your lokalhost and should definitely work.

edit: does not hang:

[root@pirateship:~]# tcpdump -ni any host 1.1.1.1 and icmp

tcpdump: WARNING: any: That device doesn't support promiscuous mode
(Promiscuous mode not supported on the "any" device)
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
11:51:49.053231 eth0  Out IP 0.0.0.0 > 1.1.1.1: ICMP echo request, id 37374, seq 1, length 64
11:51:50.098558 eth0  Out IP 0.0.0.0 > 1.1.1.1: ICMP echo request, id 37374, seq 2, length 64
11:51:51.123519 eth0  Out IP 0.0.0.0 > 1.1.1.1: ICMP echo request, id 37374, seq 3, length 64

What about your host machine? You can see that Packets are exiting your system but not returning?

jack@pantheon $ sudo tcpdump -ni any host 1.1.1.1 and icmp

tcpdump: WARNING: any: That device doesn't support promiscuous mode
(Promiscuous mode not supported on the "any" device)
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
14:45:50.734090 wg0-mullvad Out IP 10.132.24.166 > 1.1.1.1: ICMP echo request, id 44815, seq 1, length 64
14:45:50.739612 wg0-mullvad In  IP 1.1.1.1 > 10.132.24.166: ICMP echo reply, id 44815, seq 1, length 64
14:45:51.735515 wg0-mullvad Out IP 10.132.24.166 > 1.1.1.1: ICMP echo request, id 44815, seq 2, length 64
14:45:51.741540 wg0-mullvad In  IP 1.1.1.1 > 10.132.24.166: ICMP echo reply, id 44815, seq 2, length 64
14:45:52.736924 wg0-mullvad Out IP 10.132.24.166 > 1.1.1.1: ICMP echo request, id 44815, seq 3, length 64
14:45:52.742840 wg0-mullvad In  IP 1.1.1.1 > 10.132.24.166: ICMP echo reply, id 44815, seq 3, length 64

those go in and out…

but there is nothing when you ping in your container, but execute tcpdump on your host? I would have exepected something like br0 In IP 192.168.100.10 > 1.1.1.1 on your hhost

Ah like that, sorry. It was unclear to me where i should run tcpdump. The configuration with bridge br0 fails to launch the container. I’ve reverted back to the original config.

jack@pantheon $ sudo tcpdump -ni any host 1.1.1.1 and icmp

tcpdump: WARNING: any: That device doesn't support promiscuous mode
(Promiscuous mode not supported on the "any" device)
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
15:21:56.563492 ve-pirateship In  IP 0.0.0.0 > 1.1.1.1: ICMP echo request, id 41487, seq 1, length 64
15:21:57.586820 ve-pirateship In  IP 0.0.0.0 > 1.1.1.1: ICMP echo request, id 41487, seq 2, length 64
15:21:58.610556 ve-pirateship In  IP 0.0.0.0 > 1.1.1.1: ICMP echo request, id 41487, seq 3, length 64

There is definitively something broken with your container config, as the ip is 0.0.0.0 and not like expected 192.168.100.11. Could you do ip a in your container and also on your host machine?

host:

jack@pantheon $ ip a                                                                                                                                        ✔  10021  16:26:52 
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host noprefixroute 
       valid_lft forever preferred_lft forever
2: ip6tnl0@NONE: <NOARP> mtu 1452 qdisc noop state DOWN group default qlen 1000
    link/tunnel6 :: brd :: permaddr 12b7:8291:64e9::
3: enp5s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether d8:43:ae:65:ff:ee brd ff:ff:ff:ff:ff:ff
    altname enxd843ae65ffee
    inet 192.168.2.234/24 brd 192.168.2.255 scope global dynamic noprefixroute enp5s0
       valid_lft 42272sec preferred_lft 42272sec
    inet6 fe80::1549:bc73:7c42:a3df/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever
4: wg0-mullvad: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1380 qdisc noqueue state UNKNOWN group default qlen 1000
    link/none 
    inet 10.159.31.210/32 brd 10.159.31.210 scope global wg0-mullvad
       valid_lft forever preferred_lft forever
    inet6 fc00:bbbb:bbbb:bb01:d:0:1f:1fd2/128 scope global 
       valid_lft forever preferred_lft forever

container:

[root@pirateship:~]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host noprefixroute 
       valid_lft forever preferred_lft forever
2: ip6tnl0@NONE: <NOARP> mtu 1452 qdisc noop state DOWN group default qlen 1000
    link/tunnel6 :: brd :: permaddr 228a:7858:2ab3::
3: eth0@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether da:5e:4a:cb:7f:f7 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::d85e:4aff:fecb:7ff7/64 scope link proto kernel_ll 
       valid_lft forever preferred_lft forever

the ip6tnl0 interfaces are new in both machines, could be the result of a flake update?

that’s probably one problem, but the eth0 interface is missing a ipv4 address. I expected something like inet 192.168.100.11/24 to see in your eth0 interface.

oh damnit, i commented the localAddress. Give me a second to rebuild

[root@pirateship:~]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host noprefixroute 
       valid_lft forever preferred_lft forever
2: ip6tnl0@NONE: <NOARP> mtu 1452 qdisc noop state DOWN group default qlen 1000
    link/tunnel6 :: brd :: permaddr 4a34:6ce8:bdff::
3: eth0@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether da:5e:4a:cb:7f:f7 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.100.11/32 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::d85e:4aff:fecb:7ff7/64 scope link proto kernel_ll 
       valid_lft forever preferred_lft forever

this is the correct one, which does have an ipv4 address attached to the interface

and how does tcpdump and ping behave now?

tcpdump doesn’t pick up anything on the host while pinging in the container now. :frowning:

it should at least be pick up inside the container.

Both inside and outside of the container nothing