Skip to content

NixOS Pi-hole Unbound

I recently decommissioned the last Ubuntu server in my rack and replaced it with NixOS. My entire rack is Nix at this point 😅 I never saw that coming, even a year ago haha.

  • NixOS x86 router
  • NixOS x86 DNS server
  • NixOS x86 primary compute & storage

When I set out to get Pi-hole running on NixOS I figured cobbling together a working module in an afternoon was in the cards 😅 yup.. I was wrong and it's a little harder. So after blowing a Sunday afternoon on trying to flakeify a Pi-hole and Unbound setup I defaulted to Podman and Nix's virtualisation.oci-containers.containers which you can think of like a declarative compose. But the difference is your containers run as systemd services, and you still manage the podman containers with your normal tooling like podman exec -it <container> bash. You can debug your containers with systemctl and jourlctl commands, and I guess you can docker logs -f <container> too.

Containers

{
virtualisation = {
    containers.enable = true;
    podman.enable = true;

    oci-containers.containers = {
    "pihole" = {
        autoStart = true;
        image = "pihole/pihole:latest";
        environment = {
        TZ = "America/Edmonton";
        # Set password after
        # docker exec -ti <name_of_your_container> pihole -a -p
        # set a secure password here or it will be random
        # WEBPASSWORD= "set password"
        };
        ports = [
        "53:53/udp"
        "53:53/tcp"
        "80:80/tcp"
        ];
        volumes = [
        "/pihole/data:/etc/pihole"
        "/pihole/dnsmasq:/etc/dnsmasq.d"
        ];
    };
    "unbound" = {
        autoStart = true;
        image = "mvance/unbound:latest";
        ports = [
        "5053:53/tcp"
        "5053:53/udp"
        ];
        volumes = [
        "/pihole/unbound/data:/opt/unbound/etc/unbound/"
        "/pihole/unbound/rules/unbound.conf:/opt/unbound/etc/unbound/unbound.conf"
        "/pihole/unbound/rules/a-records.conf:/opt/unbound/etc/unbound/a-records.conf"
        "/pihole/unbound/rules/srv-records.conf:/opt/unbound/etc/unbound/srv-records.conf"
        "/pihole/unbound/rules/forward-records.conf:/opt/unbound/etc/unbound/forward-records.conf"
        "/pihole/unbound/rules/root.hints:/var/lib/unbound/root.hints"
        ];
    };
    };
};
}
server:
    # If no logfile is specified, syslog is used
    # logfile: "/var/log/unbound/unbound.log"
    verbosity: 0
    interface: 127.0.0.1
    port: 5053
    do-ip4: yes
    do-udp: yes
    do-tcp: yes
    do-ip6: no
    prefer-ip6: no
    #root-hints: "/var/lib/unbound/root.hints"
    harden-glue: yes
    harden-dnssec-stripped: yes
    use-caps-for-id: no
    edns-buffer-size: 1232
    prefetch: yes
    num-threads: 1
    # Ensure kernel buffer is large enough to not lose messages in traffic spikes
    so-rcvbuf: 1m
    # Ensure privacy of local IP ranges
    private-address: 192.168.0.0/16
    private-address: 169.254.0.0/16
    private-address: 172.16.0.0/12
    private-address: 10.0.0.0/8
    private-address: fd00::/8
    private-address: fe80::/10
    # tailnet reserved
    private-address: 100.0.0.0/8

Unbound

Remember to update this file every 6 months.

wget -O root.hints https://www.internic.net/domain/named.root

Tailscale

If you are running Tailscale on your Linux desktop your device will fall back to your tailnets MagicDNS server which has its own public resolver @ 100.100.100.100:53, which will bullocks your blocking endeavors. Android seems to respect your local networks DNS, but if you disconnect from WiFi ✨ TaDaaaa you are free to bypass blocking rules.

Is your DNS server a tailscale device already? If so, go to tailscale dashboard -> DNS -> Add global nameservers make sure its set to override. Put your tailscale IP address for your DNS server 🤯 works like a dream & you can leave MagicDNS on and still get all the tailscale DNS goodness 🥰.