Set Up Personal DNS Server

Set Up Personal DNS Server

When I first realized my ISP's DNS resolver was logging every domain I visited, I decided to take control. Running your own DNS server transforms how your homelab works: you get privacy, faster lookups for internal services, and the ability to block ads and malware at the network level. This isn't just for paranoia—it's fundamental infrastructure for any serious self-hosted setup.

Why Run Your Own DNS?

DNS is the phonebook of the internet. Every time you visit a website, your device asks a resolver "What's the IP address for compacthost.com?" By default, that question goes to your ISP or a public resolver like Cloudflare's 1.1.1.1. They see everything.

When I set up my personal DNS server, the privacy difference was immediate. My router now holds all those queries. I can also point internal services (like `jellyfin.local` or `nextcloud.home`) to my homelab's private IPs without touching the public DNS system. I can block entire categories of domains—gambling sites, tracking pixels, malware—before they even load.

The third benefit: performance. DNS queries on your local network are instant. No waiting for external lookups.

Two Approaches: Simple vs. Full-Featured

I prefer dnsmasq for most homelabs. It's lightweight, runs on anything from a Raspberry Pi to a Docker container, and handles DHCP alongside DNS. If you need complex zone management or plan to expose DNS externally, BIND9 is the proper choice, but dnsmasq suits 95% of home setups.

For this guide, I'll show you the dnsmasq route first, then a Docker Compose alternative that's even easier to maintain.

Installation: dnsmasq on Ubuntu/Debian

I run dnsmasq on a dedicated VM with 512MB RAM and a static IP. It's stable enough to forget about for months.

sudo apt update
sudo apt install -y dnsmasq

# Stop it for now—we need to configure first
sudo systemctl stop dnsmasq
sudo systemctl disable dnsmasq

The config file lives at `/etc/dnsmasq.conf`. It's huge and commented. I create a minimal config by starting fresh:

sudo cp /etc/dnsmasq.conf /etc/dnsmasq.conf.backup
sudo tee /etc/dnsmasq.conf > /dev/null << 'EOF'
# Listen on the local network interface (not just 127.0.0.1)
listen-address=192.168.1.10

# Forward queries to Cloudflare or Quad9 by default
server=1.1.1.1
server=9.9.9.9

# Local domain mappings
local=/home/
address=/router.home/192.168.1.1
address=/jellyfin.home/192.168.1.50
address=/nextcloud.home/192.168.1.51
address=/homeassistant.home/192.168.1.52

# Block known malware/ad domains (optional)
addn-hosts=/etc/dnsmasq.d/blocklist.txt

# DHCP (optional—only if you want dnsmasq to assign IPs)
dhcp-range=192.168.1.100,192.168.1.200,24h
dhcp-option=option:router,192.168.1.1

# Cache settings
cache-size=1024
neg-cache-time=3600

# Log everything (comment out if too verbose)
log-queries
log-facility=/var/log/dnsmasq.log

# Performance
threads=4
EOF
Tip: Replace 192.168.1.10 with your actual DNS server IP, and adjust the internal addresses to match your network. Use ip addr to confirm your server's IP before proceeding.

Now enable and start the service:

sudo systemctl enable dnsmasq
sudo systemctl start dnsmasq

# Verify it's listening
sudo ss -tlnp | grep dnsmasq

You should see port 53 open on your specified IP. Test it locally:

nslookup google.com 192.168.1.10
nslookup jellyfin.home 192.168.1.10

Both queries should return results. If not, check logs with:

sudo tail -f /var/log/dnsmasq.log

Docker Compose: The Cleaner Way

I prefer Docker for DNS because it's isolated and easy to backup or migrate. Here's a production-ready compose file:

version: '3.8'

services:
  dnsmasq:
    image: jpillora/dnsmasq:latest
    container_name: dnsmasq
    restart: always
    network_mode: host
    volumes:
      - ./dnsmasq.conf:/etc/dnsmasq.conf:ro
      - ./blocklist.txt:/etc/dnsmasq.d/blocklist.txt:ro
    environment:
      TZ: UTC
    cap_add:
      - NET_ADMIN
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

Save that as `docker-compose.yml` and create the config file alongside it:

mkdir dns-server && cd dns-server
# Paste the compose above into docker-compose.yml

cat > dnsmasq.conf << 'EOF'
listen-address=127.0.0.1,192.168.1.10
server=1.1.1.1
server=9.9.9.9
local=/home/
address=/router.home/192.168.1.1
address=/jellyfin.home/192.168.1.50
address=/nextcloud.home/192.168.1.51
cache-size=1024
threads=4
log-queries
EOF

# Create empty blocklist (or populate it with malware domains)
touch blocklist.txt

docker-compose up -d

The Docker approach avoids package conflicts and makes DNS portable—I can spin it up on any machine with Docker.

Configure Your Clients

On each device or router, point DNS to your server's IP. For your router (usually 192.168.1.1), find the DNS settings and enter `192.168.1.10`. DHCP-connected clients will automatically use it.

On a Linux machine, edit `/etc/resolv.conf`:

nameserver 192.168.1.10
nameserver 1.1.1.1

On macOS, go to System Preferences → Network → DNS Servers and add your server's IP.

Watch out: If your DNS server goes down, all network resolution fails. Always configure a fallback resolver (like 1.1.1.1) as a secondary. Test your setup on one client first before pushing it to your entire network.

Adding Ad-Blocking Lists

Create a blocklist file with domains you want to block. I maintain a combined list from reputable sources:

curl -s https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts | grep "^0.0.0.0" | cut -d' ' -f2 > /etc/dnsmasq.d/blocklist.txt

Add that to a cron job for weekly updates:

0 3 * * 0 curl -s https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts | grep "^0.0.0.0" | cut -d' ' -f2 > /etc/dnsmasq.d/blocklist.txt && systemctl restart dnsmasq

Now blocked domains will resolve to `0.0.0.0`, which effectively kills the connection.

Monitoring and Maintenance

I check DNS logs periodically to catch suspicious patterns:

# Top domains queried in the last hour
tail -n 10000 /var/log/dnsmasq.log | grep "query\[A\]" | cut -d' ' -f6 | sort | uniq -c | sort -rn | head -20

If a client is querying unusual domains, that's your signal to investigate. I also monitor my DNS server's memory and CPU—a healthy dnsmasq uses less than 50MB RAM.

Next Steps: DNSSEC and External Access

For a production setup, enable DNSSEC validation in dnsmasq to verify DNS responses aren't spoofed. If you want to access internal services outside your home network, combine this DNS server with Tailscale or Cloudflare Tunnel—don't expose port 53 to the public internet.

Your personal DNS server is now the foundation of a private, controlled network. Every query stays in your hands, and internal services resolve instantly. That's the homelab advantage.

```