Networking & Firewall
Static Network Configuration
The server uses a static IP address with systemd-networkd:
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; }];
};
};| Parameter | Value |
|---|---|
| IP address | 192.168.1.20/24 |
| Gateway | 192.168.1.1 |
| DNS | 192.168.1.1 (router, which forwards to AdGuard) |
| Interface | enp1s0 |
| Wake-on-LAN | Enabled |
| IPv6 | Disabled |
systemd-networkd
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:
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
networking.firewall = {
enable = true;
allowPing = false;
allowedUDPPorts = [ 53 ];
allowedTCPPorts = [ 53 80 443 ];
};| Port | Protocol | Service |
|---|---|---|
| 53 | TCP + UDP | DNS (AdGuard Home) |
| 80 | TCP | HTTP (Traefik, redirects to HTTPS) |
| 443 | TCP | HTTPS (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:
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.