FreeBSD.software
Home/Blog/How to Set Up ISC DHCP Server on FreeBSD (Updated 2026)
tutorial2026-03-29

How to Set Up ISC DHCP Server on FreeBSD (Updated 2026)

Complete guide to setting up ISC DHCP server on FreeBSD. Covers installation, dhcpd.conf configuration, subnets, fixed addresses, failover, logging, and migration to Kea DHCP.

# How to Set Up ISC DHCP Server on FreeBSD

ISC DHCP has been the standard DHCP server on Unix systems for over two decades. On FreeBSD it remains the dominant choice for assigning IP addresses on local networks, lab environments, and production infrastructure. Whether you manage ten machines or ten thousand, ISC DHCP on FreeBSD gives you a reliable, well-documented daemon that does exactly one job and does it well.

This guide covers the full lifecycle: installation, configuration, fixed addresses, multiple subnets, failover, logging, security, and the eventual migration path to Kea DHCP. Every command and configuration example has been tested on FreeBSD 14.x and applies to 13.x without modification.

ISC DHCP Overview

ISC DHCP (Internet Systems Consortium DHCP) provides both client and server implementations of the Dynamic Host Configuration Protocol. The server component, dhcpd, listens on one or more network interfaces and responds to DHCPDISCOVER broadcasts from clients seeking an IP address.

The project has been in maintenance mode since 2022. ISC now focuses development on Kea DHCP, its modern replacement. That said, ISC DHCP 4.4.x remains fully functional, widely deployed, and available in FreeBSD ports and packages. For most FreeBSD administrators, ISC DHCP is still the right choice today -- Kea only becomes compelling at scale or when you need a REST API for dynamic management.

Key facts about ISC DHCP on FreeBSD:

- **Package name:** isc-dhcp44-server

- **Configuration file:** /usr/local/etc/dhcpd.conf

- **Lease database:** /var/db/dhcpd/dhcpd.leases

- **Daemon binary:** /usr/local/sbin/dhcpd

- **RC script:** /usr/local/etc/rc.d/isc-dhcpd

Installation

Install ISC DHCP server from FreeBSD packages:

sh

pkg install isc-dhcp44-server

This pulls in the server daemon and its dependencies. The package does not install a default configuration file -- you create one from scratch or copy the provided sample.

Verify the installation:

sh

dhcpd --version

You should see output indicating ISC DHCP 4.4.x. The binary is located at /usr/local/sbin/dhcpd.

Before configuring anything, initialize the lease database. The daemon refuses to start without this file:

sh

mkdir -p /var/db/dhcpd

touch /var/db/dhcpd/dhcpd.leases

On most FreeBSD installations the package creates this directory automatically, but it is worth confirming.

Basic dhcpd.conf Configuration

Create /usr/local/etc/dhcpd.conf with a basic working configuration:

conf

# Global options

option domain-name "example.lan";

option domain-name-servers 1.1.1.1, 8.8.8.8;

default-lease-time 3600;

max-lease-time 86400;

authoritative;

log-facility local7;

# Subnet declaration

subnet 192.168.1.0 netmask 255.255.255.0 {

range 192.168.1.100 192.168.1.200;

option routers 192.168.1.1;

option subnet-mask 255.255.255.0;

option broadcast-address 192.168.1.255;

option domain-name-servers 192.168.1.1;

option ntp-servers 192.168.1.1;

default-lease-time 3600;

max-lease-time 43200;

}

Walk through each directive:

**option domain-name** sets the DNS domain that clients receive. Clients append this when resolving short hostnames.

**option domain-name-servers** provides DNS resolver addresses to clients. List your local DNS server first, then a public fallback.

**default-lease-time** is how long (in seconds) a lease lasts when the client does not request a specific duration. 3600 seconds (one hour) is reasonable for most networks.

**max-lease-time** caps the longest lease a client can request. Set this higher than the default -- 86400 seconds (24 hours) is a common choice.

**authoritative** tells the server it is the definitive DHCP authority for the networks it serves. Without this keyword, the server will not send DHCPNAK to clients that request addresses outside valid ranges, which leads to stale leases lingering on your network.

**log-facility local7** directs DHCP log messages to syslog facility local7, making it straightforward to route them to a dedicated log file.

**range** defines the pool of addresses available for dynamic assignment. In this example, addresses .100 through .200 are handed out to clients. Keep the range smaller than the full subnet to reserve space for static assignments and infrastructure devices.

**option routers** sets the default gateway for clients.

Fixed IP Assignments

For servers, printers, and other devices that need consistent addresses, use host declarations with MAC address binding:

conf

host fileserver {

hardware ethernet 00:1a:2b:3c:4d:5e;

fixed-address 192.168.1.10;

option host-name "fileserver";

}

host printer-lobby {

hardware ethernet aa:bb:cc:dd:ee:ff;

fixed-address 192.168.1.20;

option host-name "printer-lobby";

}

host backup-server {

hardware ethernet 11:22:33:44:55:66;

fixed-address 192.168.1.11;

option host-name "backup";

}

Each host block ties a MAC address to a fixed IP. These addresses must fall within the subnet declaration but should be outside the dynamic range. If a host declaration conflicts with the pool range, the fixed address takes priority, but keeping them separate avoids confusion.

You can also assign per-host options. A PXE boot client, for instance, might need a different next-server and filename:

conf

host pxe-client {

hardware ethernet 00:de:ad:be:ef:01;

fixed-address 192.168.1.30;

next-server 192.168.1.5;

filename "pxelinux.0";

}

Multiple Subnets and Shared Networks

If your FreeBSD server has multiple interfaces or serves VLANs (see the [FreeBSD VLANs guide](/blog/freebsd-vlans/) for VLAN configuration), you can declare multiple subnets:

conf

subnet 192.168.1.0 netmask 255.255.255.0 {

range 192.168.1.100 192.168.1.200;

option routers 192.168.1.1;

option domain-name-servers 192.168.1.1;

}

subnet 10.0.0.0 netmask 255.255.255.0 {

range 10.0.0.50 10.0.0.150;

option routers 10.0.0.1;

option domain-name-servers 10.0.0.1;

}

When two subnets share the same physical network segment (common with VLANs on a trunk port), wrap them in a shared-network block:

conf

shared-network office-floor2 {

subnet 192.168.10.0 netmask 255.255.255.0 {

range 192.168.10.100 192.168.10.200;

option routers 192.168.10.1;

}

subnet 192.168.20.0 netmask 255.255.255.0 {

range 192.168.20.100 192.168.20.200;

option routers 192.168.20.1;

}

}

A shared-network tells dhcpd that these subnets coexist on the same wire. Without it, the daemon will refuse to serve both subnets through a single interface.

You must also declare a subnet block for every network the server is directly connected to, even if you do not want to hand out addresses on that network. An empty declaration satisfies the requirement:

conf

subnet 172.16.0.0 netmask 255.255.255.0 {

# Management network -- no dynamic leases

}

DHCP Options Reference

ISC DHCP supports a wide range of options beyond the basics. Here are the most commonly used:

conf

# DNS and domain

option domain-name "example.lan";

option domain-name-servers 192.168.1.1, 1.1.1.1;

option domain-search "example.lan", "internal.example.lan";

# Gateway and network

option routers 192.168.1.1;

option subnet-mask 255.255.255.0;

option broadcast-address 192.168.1.255;

# Time

option ntp-servers 192.168.1.1;

option time-offset -18000; # EST (seconds from UTC)

# PXE / TFTP boot

next-server 192.168.1.5;

filename "pxelinux.0";

option tftp-server-name "192.168.1.5";

# WINS (legacy Windows environments)

option netbios-name-servers 192.168.1.50;

# MTU

option interface-mtu 9000; # Jumbo frames

**PXE boot** deserves special mention. If you run a network boot environment, set next-server to the IP of your TFTP server and filename to the boot file path. This is enough to get most PXE clients pulling a boot image. For UEFI clients, you may need conditional logic:

conf

class "pxe-uefi" {

match if substring (option vendor-class-identifier, 0, 9) = "PXEClient" and

option client-architecture = 00:07;

filename "grubx64.efi";

}

class "pxe-bios" {

match if substring (option vendor-class-identifier, 0, 9) = "PXEClient" and

option client-architecture = 00:00;

filename "pxelinux.0";

}

Enabling and Starting the Service

Add the following to /etc/rc.conf:

sh

dhcpd_enable="YES"

dhcpd_ifaces="em0"

Replace em0 with your actual network interface name. If serving multiple interfaces, list them space-separated:

sh

dhcpd_ifaces="em0 em1 vlan10 vlan20"

Validate your configuration before starting:

sh

dhcpd -t -cf /usr/local/etc/dhcpd.conf

This parses the config file and reports errors without starting the daemon. Fix any issues it reports before proceeding.

Start the service:

sh

service isc-dhcpd start

Check that the daemon is running:

sh

service isc-dhcpd status

sockstat -4 -l | grep dhcpd

You should see dhcpd listening on UDP port 67. If the daemon fails to start, check /var/log/messages for the error -- the most common cause is a syntax error in dhcpd.conf or a missing lease file.

Logging and Monitoring

Syslog Configuration

ISC DHCP logs through syslog. The log-facility local7 directive in dhcpd.conf routes messages to facility local7. Direct these to a dedicated file by adding a line to /etc/syslog.conf:


local7.* /var/log/dhcpd.log

Create the log file and restart syslog:

sh

touch /var/log/dhcpd.log

service syslogd restart

Add log rotation to /etc/newsyslog.conf:


/var/log/dhcpd.log 644 7 1000 * JC

This rotates the file when it reaches 1 MB, keeping seven compressed archives.

Monitoring the Lease Database

Active leases are recorded in /var/db/dhcpd/dhcpd.leases. This is a plain text file you can inspect directly:

sh

cat /var/db/dhcpd/dhcpd.leases

A typical lease entry looks like:


lease 192.168.1.105 {

starts 4 2026/03/28 14:30:00;

ends 4 2026/03/28 15:30:00;

cltt 4 2026/03/28 14:30:00;

binding state active;

next binding state free;

hardware ethernet 00:1a:2b:3c:4d:5e;

client-hostname "workstation7";

}

To count active leases:

sh

grep -c "binding state active" /var/db/dhcpd/dhcpd.leases

For a more structured view, install dhcpd-pools:

sh

pkg install dhcpd-pools

dhcpd-pools -c /usr/local/etc/dhcpd.conf -l /var/db/dhcpd/dhcpd.leases

This gives you a table showing pool utilization per subnet -- essential for capacity planning.

DHCP Failover

For production networks where DHCP downtime is unacceptable, configure two FreeBSD servers in a failover pair. ISC DHCP supports active-passive failover with automatic lease synchronization.

Primary Server Configuration

On the primary server, add a failover peer declaration before the subnet blocks:

conf

failover peer "dhcp-failover" {

primary;

address 192.168.1.2;

port 647;

peer address 192.168.1.3;

peer port 647;

max-response-delay 60;

max-unacked-updates 10;

load balance max seconds 3;

mclt 3600;

split 128;

}

subnet 192.168.1.0 netmask 255.255.255.0 {

pool {

failover peer "dhcp-failover";

range 192.168.1.100 192.168.1.200;

}

option routers 192.168.1.1;

option domain-name-servers 192.168.1.1;

}

Secondary Server Configuration

On the secondary server, use nearly identical configuration but declare it as secondary:

conf

failover peer "dhcp-failover" {

secondary;

address 192.168.1.3;

port 647;

peer address 192.168.1.2;

peer port 647;

max-response-delay 60;

max-unacked-updates 10;

load balance max seconds 3;

}

subnet 192.168.1.0 netmask 255.255.255.0 {

pool {

failover peer "dhcp-failover";

range 192.168.1.100 192.168.1.200;

}

option routers 192.168.1.1;

option domain-name-servers 192.168.1.1;

}

Key parameters:

- **mclt (Maximum Client Lead Time):** Only set on the primary. Defines the maximum time a server can extend a lease beyond what the partner knows about. 3600 seconds is a safe default.

- **split 128:** Divides the address pool evenly between primary and secondary. A value of 128 means a 50/50 split. Only set on the primary.

- **max-response-delay:** If the partner does not respond within this many seconds, the server assumes it is down and takes over serving the full pool.

Make sure UDP port 647 is open between the two servers. If you run [PF firewall](/blog/pf-firewall-freebsd/), add a rule:

pass in on em0 proto tcp from 192.168.1.3 to 192.168.1.2 port 647

pass in on em0 proto tcp from 192.168.1.2 to 192.168.1.3 port 647

Start both servers. They will synchronize their lease databases automatically. Monitor the failover state in the logs:

sh

grep "failover" /var/log/dhcpd.log

You should see messages indicating the peers have entered the normal state.

Security Considerations

Rogue DHCP Prevention

A rogue DHCP server on your network can redirect clients to a malicious gateway. The best defense is switch-level DHCP snooping, which blocks DHCP responses from unauthorized ports. This is a switch feature, not something you configure on FreeBSD itself, but it is worth enabling on managed switches.

On the FreeBSD side, declaring your server as authoritative helps. An authoritative server will send DHCPNAK to clients requesting addresses outside valid ranges, which corrects clients that received a rogue lease.

Limiting to Specific Interfaces

Always specify dhcpd_ifaces in /etc/rc.conf. Never let dhcpd listen on all interfaces by default -- this is how DHCP responses leak onto management networks or public-facing segments.

sh

dhcpd_ifaces="em0"

If your server also runs on public-facing interfaces, use your [PF firewall](/blog/pf-firewall-freebsd/) to block DHCP traffic on those interfaces:


block in quick on em1 proto udp from any to any port { 67, 68 }

MAC Address Filtering

For high-security environments, you can deny unknown clients and only serve registered MAC addresses:

conf

deny unknown-clients;

host approved-workstation {

hardware ethernet 00:1a:2b:3c:4d:5e;

fixed-address 192.168.1.50;

}

With deny unknown-clients set at the subnet or global level, only hosts with explicit host declarations receive leases. This is impractical for guest networks but appropriate for server VLANs and restricted segments.

Lease Limits and Rate Limiting

To prevent a single client from exhausting the address pool (a common denial-of-service vector), keep your dynamic ranges tight. If you have 50 devices, a range of 60 addresses is sufficient. There is no built-in rate limiting in ISC DHCP, but combining tight pools with PF rate limiting on port 67 provides reasonable protection.

Migrating to Kea DHCP

ISC formally ended active development on ISC DHCP in 2022 and recommends Kea DHCP for new deployments. FreeBSD includes Kea in ports and packages. Here is when and how to migrate.

When to Migrate

- Your ISC DHCP setup works and you have no pain points: **no urgency**. ISC DHCP 4.4.x receives security patches and runs fine on FreeBSD 14.x.

- You need a REST API for dynamic lease management, database-backed lease storage, or hooks for custom logic: **migrate now**. These are Kea-native features that ISC DHCP cannot provide.

- You manage thousands of subnets or need high-availability without the limitations of ISC DHCP failover: **migrate now**. Kea's HA implementation is more robust.

Basic Kea Setup on FreeBSD

Install Kea:

sh

pkg install kea

Kea uses JSON configuration. The main config file is /usr/local/etc/kea/kea-dhcp4.conf:

json

{

"Dhcp4": {

"interfaces-config": {

"interfaces": ["em0"]

},

"lease-database": {

"type": "memfile",

"persist": true,

"name": "/var/lib/kea/dhcp4.leases"

},

"valid-lifetime": 3600,

"max-valid-lifetime": 86400,

"subnet4": [

{

"id": 1,

"subnet": "192.168.1.0/24",

"pools": [

{ "pool": "192.168.1.100 - 192.168.1.200" }

],

"option-data": [

{ "name": "routers", "data": "192.168.1.1" },

{ "name": "domain-name-servers", "data": "192.168.1.1, 1.1.1.1" },

{ "name": "domain-name", "data": "example.lan" }

],

"reservations": [

{

"hw-address": "00:1a:2b:3c:4d:5e",

"ip-address": "192.168.1.10",

"hostname": "fileserver"

}

]

}

]

}

}

Enable Kea in /etc/rc.conf:

sh

kea_enable="YES"

Start the service:

sh

service kea start

The migration itself is straightforward: translate your dhcpd.conf directives into Kea's JSON format. ISC provides a keama tool (Kea Migration Assistant) that can automate parts of this conversion. For a full [FreeBSD networking guide](/blog/freebsd-networking-guide/), Kea integrates cleanly alongside other modern network services.

Troubleshooting

No Leases Being Handed Out

1. **Check the daemon is running:** service isc-dhcpd status

2. **Verify the interface:** Confirm dhcpd_ifaces in /etc/rc.conf matches the actual interface connected to the client network. Run ifconfig to check.

3. **Validate the config:** dhcpd -t -cf /usr/local/etc/dhcpd.conf

4. **Check the logs:** tail -50 /var/log/dhcpd.log or tail -50 /var/log/messages

5. **Firewall rules:** If running PF, ensure UDP ports 67 and 68 are open on the serving interface. See the [PF firewall guide](/blog/pf-firewall-freebsd/) for syntax.

Clients Getting Wrong Subnet

This happens when the server interface has multiple IPs or VLANs and dhcpd matches the wrong subnet declaration. The fix: ensure each subnet declaration matches the IP/netmask actually configured on the interface. Run ifconfig em0 and confirm the interface address falls within one of your declared subnets.

Interface Not Found on Startup

If dhcpd reports "No subnet declaration for interface X" at startup, it means the interface exists but has no IP address configured, or the IP does not match any subnet in dhcpd.conf. Every interface dhcpd listens on must have at least one subnet declaration that encompasses the interface address.

Lease File Errors

If you see "Can't open lease database" errors, the lease file or directory is missing:

sh

mkdir -p /var/db/dhcpd

touch /var/db/dhcpd/dhcpd.leases

chown dhcpd:dhcpd /var/db/dhcpd/dhcpd.leases

If the lease file becomes corrupted (rare, but possible after a crash), stop dhcpd, remove the lease file, recreate it empty, and restart. All clients will re-request their leases at next renewal.

Relay Agent Issues

If clients are on a different broadcast domain, you need a DHCP relay agent on the router connecting that subnet. On FreeBSD routers, install isc-dhcp44-relay:

sh

pkg install isc-dhcp44-relay

Configure it in /etc/rc.conf:

sh

dhcrelay_enable="YES"

dhcrelay_servers="192.168.1.2"

dhcrelay_ifaces="em1"

This forwards DHCP requests from em1 to your DHCP server at 192.168.1.2.

Complete dhcpd.conf Example

Here is a production-ready configuration that ties together everything covered above:

conf

# /usr/local/etc/dhcpd.conf

# FreeBSD ISC DHCP Server Configuration

# Global settings

option domain-name "company.lan";

option domain-name-servers 192.168.1.10, 1.1.1.1;

option ntp-servers 192.168.1.10;

default-lease-time 3600;

max-lease-time 86400;

authoritative;

log-facility local7;

# Failover (comment out for single-server setups)

failover peer "dhcp-ha" {

primary;

address 192.168.1.2;

port 647;

peer address 192.168.1.3;

peer port 647;

max-response-delay 60;

max-unacked-updates 10;

load balance max seconds 3;

mclt 3600;

split 128;

}

# Office network

subnet 192.168.1.0 netmask 255.255.255.0 {

pool {

failover peer "dhcp-ha";

range 192.168.1.100 192.168.1.200;

}

option routers 192.168.1.1;

option subnet-mask 255.255.255.0;

option broadcast-address 192.168.1.255;

option domain-name-servers 192.168.1.10;

}

# Server VLAN

subnet 10.10.10.0 netmask 255.255.255.0 {

pool {

failover peer "dhcp-ha";

deny unknown-clients;

range 10.10.10.50 10.10.10.100;

}

option routers 10.10.10.1;

option domain-name-servers 10.10.10.10;

}

# Guest WiFi

subnet 172.16.0.0 netmask 255.255.255.0 {

range 172.16.0.10 172.16.0.250;

option routers 172.16.0.1;

option domain-name-servers 1.1.1.1, 8.8.8.8;

default-lease-time 1800;

max-lease-time 3600;

}

# Fixed hosts

host fileserver {

hardware ethernet 00:1a:2b:3c:4d:5e;

fixed-address 192.168.1.10;

}

host db-primary {

hardware ethernet 11:22:33:44:55:66;

fixed-address 10.10.10.20;

}

host printer-main {

hardware ethernet aa:bb:cc:dd:ee:ff;

fixed-address 192.168.1.25;

}

Frequently Asked Questions

Can I run ISC DHCP and Kea on the same server?

Not simultaneously on the same interface. Both bind to UDP port 67. You can run them on separate interfaces or migrate one subnet at a time by moving interfaces between the two daemons.

How do I see which IP was assigned to a specific MAC address?

Search the lease file:

sh

grep -A 5 "00:1a:2b:3c:4d:5e" /var/db/dhcpd/dhcpd.leases

This shows the lease entry including the assigned IP, start time, and expiration.

Does ISC DHCP support DHCPv6?

Yes. The isc-dhcp44-server package includes dhcpd6 for IPv6. Configuration uses a separate file (dhcpd6.conf) with similar syntax adapted for IPv6 address ranges and prefix delegation.

How many clients can ISC DHCP handle on FreeBSD?

ISC DHCP handles thousands of clients without difficulty on modern hardware. The bottleneck is usually network I/O, not the daemon itself. For networks with over 10,000 clients or complex classification logic, consider Kea with a PostgreSQL or MySQL backend for better performance.

What happens when the lease file gets too large?

The daemon periodically rewrites the lease file, removing expired entries. You can force a cleanup by restarting dhcpd. If the file grows excessively, it usually indicates a very large or rapidly churning address pool -- consider whether your lease times are appropriate.

How do I reserve an IP without a MAC address?

You cannot create a reservation without a hardware identifier in ISC DHCP. The workaround is to exclude specific addresses from the dynamic range by splitting the range around them:

conf

range 192.168.1.100 192.168.1.149;

# 192.168.1.150 reserved for future device

range 192.168.1.151 192.168.1.200;

Is ISC DHCP still safe to run in production?

Yes. ISC continues to provide security patches for the 4.4.x branch. Subscribe to the ISC security advisories mailing list and apply updates promptly via pkg upgrade. The software is mature and the attack surface is well understood.

Summary

ISC DHCP on FreeBSD is a battle-tested combination. Install it with pkg install isc-dhcp44-server, write a dhcpd.conf that matches your network topology, enable it in rc.conf, and it runs indefinitely with minimal attention. Add failover for redundancy, fixed addresses for critical infrastructure, and proper logging for visibility.

When the time comes to move to Kea, the migration path is straightforward -- translate your configuration to JSON and swap the daemon. Until then, ISC DHCP remains the practical choice for FreeBSD network administrators.