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 finiteStep 2: Configure Your Settings
All customization happens in one file: settings.nix. Open it:
vim settings.nix # or your editor of choiceHere’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.nixOptional 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_imageOr if you prefer the full command:
nix build .#nixosConfigurations.finite.config.system.build.sdImageThis 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 listLook 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=progressWarning: Replace
/dev/sdXor/dev/diskNwith your actual SD card device. Getting this wrong can wipe your hard drive.
Step 5: Boot and Connect
- Insert the SD card into your Pi
- Connect ethernet
- 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.nixIf you set up your SSH key correctly, you’re in without a password.
Step 6: Post-Boot Setup
Change the default password
passwdEven 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_PASSWORDEnter a strong password. You’ll use this to access the Pi-hole dashboard.
Update Pi-hole database
sudo podman exec pi-hole pihole -gVerify Pi-hole is running
sudo podman psYou 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:
-
Use your own router. Plug it into the ISP router, use yours for DHCP/DNS.
-
Pi-hole DHCP. Disable DHCP on your router, enable it in Pi-hole. The Pi will assign IPs and DNS to all devices.
-
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.253Should 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
nslookupdirectly 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-passwordThis 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 -gWhat’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 DNS Bootstrap Problem - how first-boot works
The finite repo: github.com/wh1le/finite
