← All articles
NETWORKING Using dnsmasq for Homelab DNS and DHCP 2026-02-14 · 6 min read · dns · dhcp · dnsmasq

Using dnsmasq for Homelab DNS and DHCP

Networking 2026-02-14 · 6 min read dns dhcp dnsmasq networking

Every homelab eventually hits the same networking annoyance: remembering IP addresses. Your Proxmox host is 192.168.1.50, Portainer is on .51:9000, the NAS is .52, Grafana is on .53:3000. You either memorize these, maintain a bookmarks folder, or set up proper DNS.

dnsmasq is a lightweight DNS forwarder and DHCP server that fits this need perfectly. It's a single binary, uses almost no resources (2-5 MB of RAM), and replaces the DNS and DHCP functions of your consumer router with something you actually control. Unlike a full BIND or PowerDNS installation, dnsmasq is designed to be simple — the configuration is a single file, and it handles the 90% case of homelab DNS without the complexity of a full nameserver.

dnsmasq DNS and DHCP server

Why Not Just Use Your Router's DNS?

Consumer routers handle DNS and DHCP, but they do it badly for homelab purposes:

dnsmasq gives you all of this in a 500 KB binary.

Installation

dnsmasq is available in every Linux distribution's package manager:

# Debian/Ubuntu
sudo apt install dnsmasq

# Fedora/RHEL
sudo dnf install dnsmasq

# Arch
sudo pacman -S dnsmasq

Before starting dnsmasq, disable any existing DNS stub resolvers that might conflict:

# On Ubuntu/Fedora with systemd-resolved
sudo systemctl disable --now systemd-resolved

# Remove the symlinked resolv.conf
sudo rm /etc/resolv.conf

# Create a static resolv.conf pointing to localhost
echo "nameserver 127.0.0.1" | sudo tee /etc/resolv.conf

If you don't want to disable systemd-resolved entirely, you can configure dnsmasq to listen on a different port or interface. But for a dedicated DNS/DHCP server, disabling resolved is cleaner.

Basic DNS Configuration

The dnsmasq configuration lives in /etc/dnsmasq.conf. Here's a practical homelab configuration:

# /etc/dnsmasq.conf

# Listen on the LAN interface only (not localhost for external queries)
interface=eth0
bind-interfaces

# Don't read /etc/resolv.conf — we set upstream servers explicitly
no-resolv

# Upstream DNS servers (forwarding)
server=1.1.1.1
server=1.0.0.1
server=8.8.8.8

# Local domain
local=/lab/
domain=lab

# Never forward plain names (without a dot) upstream
domain-needed

# Never forward reverse lookups for private ranges upstream
bogus-priv

# Cache size (default is 150, bump it up)
cache-size=1000

# Log queries (useful for debugging, disable in production)
# log-queries
# log-facility=/var/log/dnsmasq.log

Adding Local DNS Records

This is the killer feature for homelabs. Define hostname-to-IP mappings that all devices on your network can resolve:

# /etc/dnsmasq.conf (continued)

# Host records — these resolve from any device using this DNS server
address=/proxmox.lab/192.168.1.50
address=/nas.lab/192.168.1.52
address=/grafana.lab/192.168.1.53
address=/portainer.lab/192.168.1.51
address=/pihole.lab/192.168.1.60
address=/jellyfin.lab/192.168.1.55

# Wildcard — route all *.apps.lab to the reverse proxy
address=/.apps.lab/192.168.1.51

The wildcard entry is particularly useful if you run a reverse proxy (Traefik, Caddy, Nginx Proxy Manager). Point *.apps.lab at your proxy server, and then configure the proxy to route based on hostname:

nextcloud.apps.lab → proxy → nextcloud container
gitea.apps.lab     → proxy → gitea container
immich.apps.lab    → proxy → immich container

Alternatively, use a hosts-format file for cleaner management:

# /etc/dnsmasq.conf
addn-hosts=/etc/dnsmasq-hosts
# /etc/dnsmasq-hosts
192.168.1.50  proxmox.lab proxmox
192.168.1.51  portainer.lab portainer
192.168.1.52  nas.lab nas
192.168.1.53  grafana.lab grafana
192.168.1.55  jellyfin.lab jellyfin
192.168.1.60  pihole.lab pihole
# Reload after changes (no restart needed)
sudo killall -HUP dnsmasq
# or
sudo systemctl reload dnsmasq

DHCP Configuration

dnsmasq can replace your router's DHCP server. This gives you control over IP assignments, lease times, and DNS settings pushed to clients.

Important: Disable DHCP on your router before enabling it in dnsmasq. Two DHCP servers on the same network causes unpredictable behavior.

# /etc/dnsmasq.conf (continued)

# DHCP range — assign IPs from .100 to .200, 24-hour leases
dhcp-range=192.168.1.100,192.168.1.200,24h

# Default gateway (your router)
dhcp-option=option:router,192.168.1.1

# DNS server (this dnsmasq instance)
dhcp-option=option:dns-server,192.168.1.60

# NTP server (optional)
dhcp-option=option:ntp-server,192.168.1.1

# Domain
dhcp-option=option:domain-name,lab

# Static leases (MAC address → IP)
dhcp-host=aa:bb:cc:dd:ee:01,192.168.1.50,proxmox
dhcp-host=aa:bb:cc:dd:ee:02,192.168.1.51,portainer
dhcp-host=aa:bb:cc:dd:ee:03,192.168.1.52,nas
dhcp-host=aa:bb:cc:dd:ee:04,192.168.1.55,jellyfin

# Lease file location
dhcp-leasefile=/var/lib/dnsmasq/dnsmasq.leases

# Authoritative mode (speeds up DHCP, safe on your own network)
dhcp-authoritative

Static leases are critical for homelab servers. You want your Proxmox host, NAS, and services to always get the same IP. The MAC address ties the lease to a specific network interface.

# Find MAC addresses of your devices
ip link show  # On the device itself
# or from the DHCP leases after they connect
cat /var/lib/dnsmasq/dnsmasq.leases

Split-Horizon DNS

Split-horizon DNS resolves the same domain name to different IPs depending on where the query comes from. This is useful when you access services both internally and externally.

For example, nextcloud.example.com should resolve to 192.168.1.51 when you're on LAN but to your public IP when you're away:

# /etc/dnsmasq.conf

# Override public DNS for your domain when on LAN
address=/nextcloud.example.com/192.168.1.51
address=/jellyfin.example.com/192.168.1.55
address=/gitea.example.com/192.168.1.51

# Everything else for example.com goes to the normal public DNS
# (no special configuration needed — dnsmasq forwards to upstream)

This avoids the "hairpin NAT" problem where internal requests to your public IP go out to your router and back in. With split-horizon DNS, internal traffic stays internal.

Integration with Pi-hole

If you run Pi-hole for ad-blocking, you have two options:

Option 1: Pi-hole as Frontend, dnsmasq as Backend

Pi-hole handles ad-blocking and forwards non-blocked queries to dnsmasq for local resolution:

Clients → Pi-hole (ad blocking) → dnsmasq (local DNS) → Upstream (1.1.1.1)

On Pi-hole, set the upstream DNS server to your dnsmasq instance's IP. On dnsmasq, set upstream to your preferred public DNS (1.1.1.1, 8.8.8.8).

Option 2: Use Pi-hole's Built-in dnsmasq

Pi-hole actually uses dnsmasq internally. You can add local DNS records directly to Pi-hole:

# Pi-hole custom DNS records
# /etc/pihole/custom.list
192.168.1.50 proxmox.lab
192.168.1.51 portainer.lab
192.168.1.52 nas.lab

Or through the Pi-hole web interface under Local DNS > DNS Records.

This is simpler (one service instead of two) but gives you less control over DHCP and advanced dnsmasq features.

Conditional Forwarding

Send DNS queries for specific domains to specific servers. Useful when you have multiple DNS zones or VPN-connected networks:

# /etc/dnsmasq.conf

# Forward .lab queries to ourselves (handled locally)
local=/lab/

# Forward .corp queries to the corporate DNS (over VPN)
server=/corp.example.com/10.10.0.1

# Forward reverse lookups for 10.x.x.x to the VPN DNS
server=/10.in-addr.arpa/10.10.0.1

# Forward .consul queries to the Consul DNS interface
server=/consul/127.0.0.1#8600

Monitoring and Debugging

When DNS isn't working, you need visibility. dnsmasq provides several debugging tools:

# Enable query logging
echo "log-queries" >> /etc/dnsmasq.conf
sudo systemctl restart dnsmasq

# Watch queries in real time
sudo journalctl -u dnsmasq -f

# Example output:
# dnsmasq[1234]: query[A] proxmox.lab from 192.168.1.100
# dnsmasq[1234]: config proxmox.lab is 192.168.1.50
# dnsmasq[1234]: query[A] reddit.com from 192.168.1.100
# dnsmasq[1234]: forwarded reddit.com to 1.1.1.1
# Test DNS resolution
dig proxmox.lab @192.168.1.60
dig google.com @192.168.1.60

# Check DHCP leases
cat /var/lib/dnsmasq/dnsmasq.leases

# Send a SIGHUP to reload config without restart
sudo killall -HUP dnsmasq

# Check dnsmasq cache statistics (send SIGUSR1)
sudo killall -USR1 dnsmasq
sudo journalctl -u dnsmasq | tail -5
# Shows: cache size, insertions, evictions, hits, misses

A Complete Configuration

Here's a production-ready dnsmasq config for a typical homelab:

# /etc/dnsmasq.conf — Homelab DNS + DHCP

# === Network ===
interface=eth0
bind-interfaces

# === Upstream DNS ===
no-resolv
server=1.1.1.1
server=1.0.0.1

# === Local Domain ===
local=/lab/
domain=lab
expand-hosts
domain-needed
bogus-priv

# === Performance ===
cache-size=1000
neg-ttl=300

# === DHCP ===
dhcp-range=192.168.1.100,192.168.1.200,24h
dhcp-option=option:router,192.168.1.1
dhcp-option=option:dns-server,192.168.1.60
dhcp-option=option:domain-name,lab
dhcp-authoritative
dhcp-leasefile=/var/lib/dnsmasq/dnsmasq.leases

# === Static Leases ===
dhcp-host=aa:bb:cc:dd:ee:01,192.168.1.50,proxmox
dhcp-host=aa:bb:cc:dd:ee:02,192.168.1.51,docker-host
dhcp-host=aa:bb:cc:dd:ee:03,192.168.1.52,nas
dhcp-host=aa:bb:cc:dd:ee:04,192.168.1.55,media

# === Local DNS Records ===
addn-hosts=/etc/dnsmasq-hosts

# === Wildcard for Reverse Proxy ===
address=/.apps.lab/192.168.1.51

# === Split-Horizon (external domains resolved internally) ===
address=/nextcloud.example.com/192.168.1.51
address=/jellyfin.example.com/192.168.1.55
# Enable and start
sudo systemctl enable --now dnsmasq

# Verify
dig proxmox.lab @127.0.0.1
dig google.com @127.0.0.1

Point your router's DHCP to push this server as the DNS server for all clients, or manually configure your devices to use it. Either way, you'll never have to type an IP address into a browser again. That alone makes dnsmasq worth the 15 minutes it takes to set up.