You’ve read about DNS-level ad blocking, why NixOS, and the technical details behind finite. Now let’s get it running.

This guide assumes you have basic command-line familiarity. If you can SSH into a server and edit text files, you’re good.

What You’ll Need

Hardware:

  • Raspberry Pi 3B+, 4, or 5 (tested on 3B+)
  • SD card, 8GB minimum (16GB+ recommended)
  • 5V 3A power supply
  • Ethernet cable

On your computer:

  • Nix package manager (or NixOS)
  • SD card reader

Don’t have Nix installed? See the official installation guide or finite’s install-nix.md.

Step 1: Clone the Repository

git clone https://github.com/wh1le/finite
cd finite

Step 2: Configure Your Settings

All customization happens in one file: settings.nix. Open it:

vim settings.nix  # or your editor of choice

Here’s what to change:

Required Changes

{
  # Your username (will be created on the Pi)
  USERNAME = "yourname";
 
  # Change this immediately after first login
  USER_PASSWORD = "changeme";
 
  # Your SSH public key for passwordless login
  SSH_PUBLIC_KEY = "ssh-ed25519 AAAA... you@machine";
 
  # Your network settings
  ROUTER_IP = "192.168.1.1";      # Your router's IP
  STATIC_IP = "192.168.1.253";    # IP to assign to the Pi
}

Finding your router IP: Usually 192.168.1.1 or 192.168.0.1. Check your computer’s network settings - the “gateway” or “router” address.

Choosing a static IP: Pick something in your network range that your router won’t assign via DHCP. Often routers use .1 through .100 for DHCP, so .200 or higher is safe.

SSH public key: If you don’t have one, generate it:

ssh-keygen -t ed25519 -C "you@machine"
cat ~/.ssh/id_ed25519.pub  # copy this to settings.nix

Optional Changes

{
  # SSH port (default 22 is fine, custom port adds obscurity)
  SSH_PORT = 22;
 
  # Your timezone
  TIMEZONE = "America/New_York";
 
  # Which subnets can query your DNS
  UNBOUND_SUBNETS = [
    "127.0.0.1/32 allow"
    "192.168.1.0/24 allow"  # adjust to your network
  ];
 
  # Upstream DNS provider (Mullvad by default)
  PRIVATE_DNS_SERVERS = [
    "185.213.155.123@853#de-fra-dns-001.mullvad.net"
  ];
}

Step 3: Build the SD Card Image

make build_image

Or if you prefer the full command:

nix build .#nixosConfigurations.finite.config.system.build.sdImage

This takes a while - 5-15 minutes depending on your machine. Nix is downloading and building everything needed for a complete NixOS system.

When done, you’ll have an image at ./result/sd-image/nixos-sd-image-*-aarch64-linux.img.

Step 4: Flash the SD Card

Find your SD card’s device name:

# Linux
lsblk -f
 
# macOS
diskutil list

Look for your SD card (usually /dev/sdX on Linux, /dev/diskN on macOS). Double-check this - flashing to the wrong device will destroy data.

Flash the image:

# Linux
sudo dd if=./result/sd-image/*.img of=/dev/sdX bs=4M status=progress conv=fsync
sync
 
# macOS
diskutil unmountDisk /dev/diskN
sudo dd if=./result/sd-image/*.img of=/dev/rdiskN bs=4m status=progress

Warning: Replace /dev/sdX or /dev/diskN with your actual SD card device. Getting this wrong can wipe your hard drive.

Step 5: Boot and Connect

  1. Insert the SD card into your Pi
  2. Connect ethernet
  3. Connect power

Wait about 60-90 seconds for first boot. The Pi will get its static IP from the configuration.

SSH in:

ssh -p 22 yourname@192.168.1.253
# Use the port and IP from your settings.nix

If you set up your SSH key correctly, you’re in without a password.

Step 6: Post-Boot Setup

Change the default password

passwd

Even though SSH uses keys, change the password anyway. It’s used for sudo.

Set Pi-hole web UI password

sudo podman exec -it pi-hole pihole setpassword YOUR_NEW_PASSWORD

Enter a strong password. You’ll use this to access the Pi-hole dashboard.

Update Pi-hole database

sudo podman exec pi-hole pihole -g

Verify Pi-hole is running

sudo podman ps

You should see a pi-hole container running.

Check Pi-hole dashboard

Open a browser and go to:

http://192.168.1.253/admin

(Use your Pi’s static IP)

Log in with the password you just set. You should see the Pi-hole dashboard with some blocked queries already.

Step 7: Configure Your Router

The final step: tell your router to use the Pi for DNS.

Option A: Router DNS Settings

Log into your router’s admin panel (usually http://192.168.1.1). Find DNS settings - often under “LAN” or “DHCP”.

Set:

  • Primary DNS: Your Pi’s static IP (e.g., 192.168.1.253)
  • Secondary DNS: Leave blank (or set to the same IP)

Save and reboot the router.

Option B: Stubborn ISP Router

Some ISP-provided routers don’t let you change DNS. Options:

  1. Use your own router. Plug it into the ISP router, use yours for DHCP/DNS.

  2. Pi-hole DHCP. Disable DHCP on your router, enable it in Pi-hole. The Pi will assign IPs and DNS to all devices.

  3. Per-device configuration. Set DNS manually on each device you want protected. Annoying, but works.

Step 8: Verify Everything Works

From any device on your network:

nslookup github.com 192.168.1.253

Should return an IP address. If it does, the Pi is answering DNS queries.

Check Pi-hole’s query log - you should see your lookup appear.

Try visiting a site known for ads (any news site works). Ads should be blocked or missing.

Troubleshooting

Can’t SSH into the Pi

  • Check ethernet cable is connected
  • Verify green light on Pi-hole is blinking.
  • Verify you’re using the right IP (the static IP from settings.nix)
  • Make sure your SSH key matches what’s in settings.nix
  • Try connecting a monitor to see boot messages

Pi-hole dashboard shows zero queries

  • Router might not be using the Pi for DNS yet
  • Try nslookup directly against the Pi’s IP to test
  • Check if the post-init script ran: ls /var/lib/pi-hole-postinit.stamp

Ads still showing

  • Clear browser cache
  • Some devices cache DNS aggressively - try rebooting them
  • Some ads use first-party domains that aren’t blocked by default
  • Check Pi-hole’s query log to see if requests are being blocked

Unbound not starting

  • Check system time: date. If it’s wrong, TLS will fail.
  • Look at logs: journalctl -u unbound
  • The most common issue is clock skew on first boot

Updating

To update finite after pulling new changes:

# On your main machine
cd finite
git pull
nixos-rebuild switch --flake path:.#finite --target-host \
  pi-hole@raspbery_pi_api --sudo --ask-sudo-password

This builds locally and pushes the result to the Pi - much faster than building on the Pi itself. Don’t forget to backup settings.nix before pulling new changes.

Keeping Pi-hole Updated

Pi-hole runs in a container. To update it, you’d update the image digest in finite’s config and rebuild. The current version is pinned for reproducibility.

For blocklist updates (not Pi-hole itself):

sudo podman exec pi-hole pihole -g

What’s Next

You now have a local DNS server that:

  • Blocks ads and trackers network-wide
  • Encrypts upstream DNS queries
  • Logs nothing
  • Is fully reproducible from config files

If you want to understand the technical details:

The finite repo: github.com/wh1le/finite