Setting Up ISC DHCP for Private IPs in a Proxmox Homelab
If you run a Proxmox VE homelab with multiple LXC containers and VMs, you quickly run into the problem of IP management. You could rely on your router’s DHCP, but that ties your infrastructure to whatever upstream device handles your network. In a lab environment, you want full control — static reservations for infrastructure containers, a dynamic pool for ephemeral test VMs, and no dependency on external services like a home router’s DHCP server.
This post walks through setting up ISC DHCP Server in a dedicated LXC container on Proxmox VE, using a representative homelab configuration.
Why a Dedicated DHCP Container?
There are a few approaches to DHCP in a Proxmox lab:
- Let your upstream router handle it — simple, but you lose control. You can’t define static reservations without logging into your home router, and any router reboot or replacement means reconfiguring everything.
- Run DHCP on the Proxmox host itself — works, but mixes host OS concerns with application services. You want your hypervisor to stay lean.
- Dedicated LXC container — clean separation, easy to back up, migrate, or rebuild. This is the way.
A DHCP container is lightweight (we’re talking ~3 MB RAM), has one job, and can be firewalled off from everything except the broadcast domain it serves.
Creating the Container
I create my containers using a consistent naming scheme. This container is called dhcp-srv with the static IP 10.99.99.2/24.
Prerequisites
- Proxmox VE host with a bridge (typically
vmbr0orvmbr1) - Debian LXC template downloaded (in my case, Debian 13 Trixie)
- SSH public key injected at container creation time (no password auth needed)
Container Creation via CLI
pct create 200 \
local:vztmpl/debian-13-standard_13.0-1_amd64.tar.zst \
--hostname dhcp-srv \
--net0 name=eth0,bridge=vmbr0,ip=dhcp \
--storage local-lvm \
--password <temporary-password> \
--unprivileged 1 \
--ssh-public-keys ~/.ssh/id_ed25519.pub
The --ssh-public-keys parameter is key — it injects your SSH key during template extraction so you can SSH in immediately after creation without password-based auth.
Important: Debian 13 (Trixie) templates don’t include sudo by default. Step into the container afterward and set it up:
pct enter 200
apt update && apt install -y sudo
echo '%sudo ALL=(ALL:ALL) ALL' >> /etc/sudoers
usermod -aG sudo tuxvador
exit
Setting a Static IP
Once the container boots, set a static IP by editing the network config. Inside the container:
cat > /etc/network/interfaces << 'EOF'
auto lo
iface lo inet loopback
auto eth0
iface eth0 inet static
address 10.99.99.2/24
gateway 10.99.99.1
EOF
systemctl restart networking
On the Proxmox host, 10.99.99.1 is the gateway configured on vmbr0 — the upstream router that connects the lab to the internet. The container’s own static IP is 10.99.99.2, which is also what we’ll reserve in the DHCP config itself (yes, DHCP servers need their own static address — that’s configured at the OS level, not via DHCP).
Installing ISC DHCP Server
ISC DHCP Server is the classic, battle-tested DHCP implementation for Linux. It’s available in Debian’s main repository:
apt update && apt install -y isc-dhcp-server
During installation you’ll be prompted to configure the listening interface. We’ll do that manually instead.
Configuring the Listening Interface
Edit /etc/default/isc-dhcp-server to tell the daemon which interface to listen on:
INTERFACESv4="eth0"
INTERFACESv6=""
This is critical — if you leave this blank or point it to the wrong interface, the server won’t respond to DISCOVER messages.
Writing the Configuration
This is the meat of it. The configuration lives at /etc/dhcp/dhcpd.conf. Here’s a representative file:
cat > /etc/dhcp/dhcpd.conf << 'EOF'
option domain-name "home.lab";
option domain-name-servers 8.8.8.8, 1.1.1.1;
default-lease-time 86400;
max-lease-time 86400;
ddns-update-style none;
authoritative;
subnet 10.99.99.0 netmask 255.255.255.0 {
range 10.99.99.80 10.99.99.200;
option routers 10.99.99.1;
option subnet-mask 255.255.255.0;
option broadcast-address 10.99.99.255;
}
host dhcp-srv { hardware ethernet 00:1a:2b:a3:ec:58; fixed-address 10.99.99.2; }
host mon-srv { hardware ethernet 00:1a:2b:35:9e:14; fixed-address 10.99.99.10; }
host web-proxy { hardware ethernet 00:1a:2b:e2:df:11; fixed-address 10.99.99.11; }
host media-srv { hardware ethernet 00:1a:2b:55:0c:64; fixed-address 10.99.99.13; }
host file-srv { hardware ethernet 00:1a:2b:94:00:9c; fixed-address 10.99.99.14; }
host dev-srv { hardware ethernet 00:1a:2b:41:af:0a; fixed-address 10.99.99.20; }
host vpn-srv { hardware ethernet 00:1a:2b:53:0c:f5; fixed-address 10.99.99.25; }
EOF
Breaking Down the Configuration
Global Options:
| Directive | Value | Purpose |
|---|---|---|
option domain-name | home.lab | Sets the DNS search domain for all clients |
option domain-name-servers | 8.8.8.8, 1.1.1.1 | DNS servers handed to clients |
default-lease-time | 86400 (24h) | How long a lease lasts by default |
max-lease-time | 86400 (24h) | Maximum lease duration a client can request |
ddns-update-style | none | Disables DDNS updates (not needed in a lab) |
authoritative | — | Declares this server as the authority for its subnet |
The authoritative directive is important. Without it, if a client moves from a different network and requests its old IP, ISC DHCP will silently refuse and the client will keep retrying its old (now invalid) address. authoritative tells clients “if I don’t know about that lease, forget it and take a new one.”
Subnet Declaration:
The subnet covers the lab’s private range — 10.99.99.0/24. The dynamic pool runs from .80 to .200. I intentionally start the pool at .80 to leave the lower range for static infrastructure containers and future use. The .1 address is the Proxmox host’s bridge gateway, which handles inter-container routing.
Static Reservations:
Each infrastructure container gets a fixed-address reservation tied to its MAC address. Proxmox assigns MACs in the 00:1a:2b:xx:xx:xx range for LXC containers by default. The mapping is straightforward:
| Container | MAC | IP | Role |
|---|---|---|---|
| dhcp-srv | 00:1a:2b:a3:ec:58 | .2 | DHCP server |
| mon-srv | 00:1a:2b:35:9e:14 | .10 | Security monitoring (monitoring stack) |
| web-proxy | 00:1a:2b:e2:df:11 | .11 | Reverse proxy / TLS |
| media-srv | 00:1a:2b:55:0c:64 | .13 | Media stack |
| file-srv | 00:1a:2b:94:00:9c | .14 | File server |
| dev-srv | 00:1a:2b:41:af:0a | .20 | Dev workspace |
| vpn-srv | 00:1a:2b:53:0c:f5 | .25 | VPN endpoint |
Note that I reserved the DHCP server’s own MAC-to-IP mapping even though it’s statically configured at the OS level. This is purely for consistency — the lease table won’t show an unknown entry for its own address, and if I ever switch the container to DHCP for some reason, the reservation will kick in automatically.
Starting and Testing the Server
Start the Service
systemctl restart isc-dhcp-server
systemctl status isc-dhcp-server
Check the logs to confirm it’s running:
journalctl -u isc-dhcp-server -n 20 --no-pager
Verifying Lease Assignment
When you create a new Proxmox container with ip=dhcp in its network config, you should see it appear in the leases file:
cat /var/lib/dhcp/dhcpd.leases
A healthy lease looks like:
lease 10.99.99.80 {
starts 5 2026/05/23 12:34:56;
ends 5 2026/05/23 12:34:56;
cltt 5 2026/05/23 12:34:56;
binding state active;
next binding state free;
rewind binding state free;
hardware ethernet 00:1a:2b:ab:cd:ef;
}
Testing from a Client Container
From any new container that received a DHCP lease, verify:
ip a show eth0
ip route show
cat /etc/resolv.conf
You should see the assigned IP, the gateway (10.99.99.1), and the DNS servers (8.8.8.8, 1.1.1.1).
Pitfalls I Ran Into
1. Silent Failures on Interface Mismatch
If INTERFACESv4 doesn’t match the actual network interface name, dhcpd starts without error but never sees any DISCOVER messages. Double-check ip a output — in LXC containers, the interface is almost always eth0.
2. Multiple DHCP Servers on the Same Broadcast Domain
If your upstream router also has DHCP enabled, you’ll see conflicting leases. Either disable DHCP on the router, or configure a DHCP relay. In my setup, I disabled DHCP on the ISP router and let the lab’s ISC DHCP server handle everything on the 10.99.99.0/24 subnet.
3. Lease Time for Ephemeral Containers
24 hours is fine for infrastructure containers, but if you frequently create and destroy test containers, consider reducing default-lease-time to something like 3600 (1 hour) to avoid lease exhaustion.
4. Container IDs vs IPs
Proxmox assigns container IDs (100, 101, 102…) but these have no relation to IP addresses. Keep a mapping in your documentation — it’s essential as the lab grows.
Resource Usage
ISC DHCP Server is incredibly lightweight. On a Debian 13 LXC:
- Memory: ~3 MB RSS (peaks at ~11 MB after a client storm)
- Disk: ~2.5 MB for the binary and config
- CPU: negligible — typically under 100ms of CPU time over days
There’s no excuse not to run a dedicated DHCP server in your lab.
Alternative: Kea DHCP
ISC DHCP Server is in maintenance mode upstream; the Internet Systems Consortium now recommends Kea as the replacement. Kea offers a modern REST API, database-backed lease storage, and better performance at scale. For a homelab with fewer than 50 clients, ISC DHCP is still perfectly adequate and simpler to configure. Kea will be covered in a future post.
Summary
Running a dedicated ISC DHCP Server in an LXC container gives you:
- Full control over IP allocations
- Static reservations for all infrastructure containers
- A dynamic pool for test/transient workloads
- Zero dependency on upstream router config
- A lightweight service that costs basically nothing in resources
The entire configuration is a single file (/etc/dhcp/dhcpd.conf) with fewer than 30 meaningful lines. If a container dies or needs to be rebuilt, the DHCP config is the first thing I restore — everything else gets its IP from the server automatically.
This is part of a series documenting a Proxmox VE homelab infrastructure.