Skip to content

Networking & Firewall

Static Network Configuration

The server uses a static IP address with systemd-networkd:

nix
networking = {
  useDHCP = false;
  enableIPv6 = false;
  dhcpcd.enable = false;
  modemmanager.enable = false;

  hostName = "homelab";
  nameservers = [ "192.168.1.1" ];

  defaultGateway = {
    interface = "enp1s0";
    address = "192.168.1.1";
  };

  interfaces.enp1s0 = {
    wakeOnLan.enable = true;
    ipv4.addresses = [{ address = "192.168.1.20"; prefixLength = 24; }];
  };
};
ParameterValue
IP address192.168.1.20/24
Gateway192.168.1.1
DNS192.168.1.1 (router, which forwards to AdGuard)
Interfaceenp1s0
Wake-on-LANEnabled
IPv6Disabled

systemd-networkd

nix
systemd.network = {
  enable = true;
  wait-online.enable = false;  # Don't block boot waiting for network
  networks."10-ignore-containers" = {
    matchConfig.Name = "ve-*";
    linkConfig.Unmanaged = "yes";
  };
};

Container virtual ethernet interfaces (ve-*) are marked as unmanaged by systemd-networkd to prevent interference with NixOS container networking.

WARNING

networking.useNetworkd is intentionally not set to true. A comment in the source notes it "breaks container network connectivity for no reason." The explicit systemd.network.enable = true achieves the desired result without the side effects.

NAT for Containers

Containers on private networks need NAT to reach the internet:

nix
networking.nat = {
  enable = true;
  enableIPv6 = false;
  externalInterface = "enp1s0";
  internalInterfaces = [ "ve-+" ];  # All container veth interfaces
};

The ve-+ wildcard matches all container virtual ethernet interfaces (ve-immich, ve-authelia, etc.).

Firewall

nix
networking.firewall = {
  enable = true;
  allowPing = false;
  allowedUDPPorts = [ 53 ];
  allowedTCPPorts = [ 53 80 443 ];
};
PortProtocolService
53TCP + UDPDNS (AdGuard Home)
80TCPHTTP (Traefik, redirects to HTTPS)
443TCPHTTPS (Traefik)

All other inbound traffic is dropped. ICMP echo (ping) is explicitly disabled at both the firewall and kernel level.

DNS Resolution

systemd-resolved is force-disabled:

nix
services.resolved.enable = lib.mkForce false;

The server uses the router (192.168.1.1) as its upstream DNS. AdGuard Home handles DNS for the rest of the network on port 53.

Container Network Topology

Internet


enp1s0 (192.168.1.20)

  ├── Traefik container (no private network, binds to host)

  ├── NAT ──┬── ve-authelia   (10.10.10.8/10.10.10.9)
  │         ├── ve-immich     (10.10.10.4/10.10.10.5)
  │         ├── ve-vaultwarden (10.10.10.2/10.10.10.3)
  │         └── ve-opencloud  (10.10.10.100/10.10.10.101)

  └── AdGuard container (no private network, binds to 127.0.0.1)

Each container with privateNetwork = true gets a point-to-point veth pair:

  • Host address: the even IP (e.g. 10.10.10.4)
  • Container address: the odd IP (e.g. 10.10.10.5)

Traefik and AdGuard run without private networking -- they bind directly to the host's network namespace.