| Tested on | Rocky Linux 10.2 (Red Quartz) |
|---|---|
| Package | kea 3.0.3-1.el10_2 |
| Applies to | RHEL, Rocky Linux, AlmaLinux, Oracle Linux, CentOS Stream, Fedora |
| Privilege | sudo or root |
| Scope | Configure Kea DHCPv4 on one subnet: dynamic pool, gateway and DNS options, MAC reservation, and lease verification. Does not cover DHCPv6, relays, HA, or ISC-to-Kea migration. |
| Related guides | Set a static IP on Rocky Linux Configure BIND DNS server on RHEL and Rocky Linux nmcli command examples for CentOS and RHEL firewalld cheat sheet tcpdump command |
If you search for dhcp-server on Rocky Linux 10, RHEL 10, AlmaLinux 10, or Oracle Linux 10, dnf often reports no matching package. On those EL 10 systems, Kea replaces the legacy ISC DHCP server: install kea, configure /etc/kea/kea-dhcp4.conf, and manage kea-dhcp4.service. ISC dhcpd.conf syntax does not drop into Kea's JSON configuration.
Understand the Kea DHCPv4 setup
On RHEL 10 and compatible EL 10 distributions, Kea is the DHCP server package that replaces ISC dhcp-server. The kea-dhcp4 daemon hands out IPv4 addresses. You define subnets, pools, options, and reservations in JSON instead of dhcpd.conf.
A new client on the wire follows the usual DHCP exchange:
DHCP client
↓ DHCPDISCOVER
Kea DHCPv4 server
↓ DHCPOFFER
Client requests address
↓ DHCPREQUEST / DHCPACK
Kea records the leaseThis article covers one server on one broadcast domain: the server's interface sits in 192.168.56.0/24, clients obtain addresses from a pool or a reservation, and Kea stores leases in the default CSV memfile backend. Remote client networks require a separate DHCP relay agent that forwards requests to the Kea server. Configuring the relay and defining relayed subnets in Kea are outside this directly connected lab.
| Platform | DHCP server |
|---|---|
| EL 8 and EL 9 | ISC DHCP may be available as dhcp-server |
| EL 10 | Kea is provided through the kea package |
| Topic | Legacy ISC DHCP | Kea on EL 10 |
|---|---|---|
| Package | dhcp-server on EL 8/9 |
kea |
| IPv4 config | /etc/dhcp/dhcpd.conf |
/etc/kea/kea-dhcp4.conf |
| Syntax | subnet / pool stanzas |
JSON subnet4 arrays |
| Service | dhcpd |
kea-dhcp4 |
Lab layout
The commands below assume two Rocky Linux 10.2 VMs on an isolated VirtualBox or libvirt network. Adjust interface names and addresses for your environment.
| System | Address | Role |
|---|---|---|
dhcp-server |
192.168.56.10/24 |
Kea DHCPv4 server |
dhcp-client |
DHCP | Requests a lease |
| Dynamic pool | 192.168.56.100–192.168.56.150 |
Automatic assignments |
| Reserved address | 192.168.56.50 |
MAC 52:54:00:12:34:56 |
| Gateway option | 192.168.56.1 |
Must be an existing router; omit when the isolated segment has none |
| DNS option | 192.168.56.10 |
Use only when a resolver is running there — see Configure BIND DNS server |
In the lab, Kea listens on enp0s8. Replace that name with the interface connected to your DHCP clients.
domain-name-servers to 192.168.56.10 so you can see the option in client output. Do not use that address unless a DNS service is actually listening there. When a client applies the lease, it updates /etc/resolv.conf. A resolver that does not answer breaks ping github.com, git push, and SSH hostnames on that machine. For a lab that only hands out IP addresses, leave domain-name-servers out of Kea entirely (see Minimal lab without gateway or DNS options). To run BIND on the same subnet first, follow Configure BIND DNS server on RHEL and Rocky Linux.
Prepare the DHCP server network
Install Kea only after the server has a stable address on the subnet it will serve. You need the correct interface, a fixed IPv4 address, and confirmation that nothing else is already bound to UDP port 67.
List interfaces and addresses:
ip -br addressSample output:
lo UNKNOWN 127.0.0.1/8 ::1/128
enp0s3 UP 10.0.2.15/24
enp0s8 UP 192.168.56.10/24The enp0s8 line shows the lab server on 192.168.56.10/24, which matches the Kea subnet below.
Confirm the connected route for that segment:
ip routeSample output:
default via 10.0.2.2 dev enp0s3 proto dhcp src 10.0.2.15 metric 101
10.0.2.0/24 dev enp0s3 proto kernel scope link src 10.0.2.15 metric 101
192.168.56.0/24 dev enp0s8 proto kernel scope link src 192.168.56.10 metric 102The 192.168.56.0/24 route on enp0s8 tells you the kernel considers that interface local to the DHCP subnet.
Check NetworkManager device state:
nmcli device statusSample output:
DEVICE TYPE STATE CONNECTION
enp0s3 ethernet connected enp0s3
enp0s8 ethernet connected Wired connection 1
lo loopback connected (externally) loBoth NICs are managed and connected. For detailed static-IP steps with nmcli, see set a static IP on Rocky Linux.
Verify that UDP port 67 is not already in use before you start Kea. ss command lists UDP listeners when you filter for the DHCP server port:
sudo ss -lunp | grep ':67 'When nothing is listening yet, the command prints no lines and exits quietly.
Attach the client VM to the same Layer 2 network as enp0s8. If the client lands on a NAT or corporate VLAN by mistake, it will never see your server's offers.
Install Kea DHCP server
On Rocky Linux 10, RHEL 10, AlmaLinux 10, or Oracle Linux 10, install the Kea package. Package installs and queries on Enterprise Linux use dnf command. Optional example files ship in the separate kea-doc package.
sudo dnf install keaIf you still expect the old package name, dnf explains the mismatch:
sudo dnf install dhcp-serverSample output:
No match for argument: dhcp-server
Error: Unable to find a match: dhcp-serverThe supported DHCP server package on the tested EL 10 platform is kea, not dhcp-server. Install only kea for the service; add kea-doc when you want packaged examples under /usr/share/doc/kea/examples/.
Confirm the installed build with rpm command:
rpm -q keaSample output:
kea-3.0.3-1.el10_2.x86_64Read the DHCPv4 server version:
kea-dhcp4 -VSample output:
3.0.3 (3.0.3 (tarball))
premium: no
linked with:
- log4cplus 2.1.1
- OpenSSL 3.5.5 27 Jan 2026
lease backends:
- Memfile backend 3.0Kea 3.0.x is what Rocky Linux 10.2 ships in BaseOS at the time of testing.
Inspect the systemd unit. systemctl command shows the ExecStart= configuration path and the privileges the daemon uses:
systemctl cat kea-dhcp4Sample output:
# /usr/lib/systemd/system/kea-dhcp4.service
[Unit]
Description=Kea DHCPv4 Server
Documentation=man:kea-dhcp4(8)
Wants=network-online.target
After=network-online.target
After=time-sync.target
[Service]
Type=notify
User=kea
AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_NET_RAW
ConfigurationDirectory=kea
ConfigurationDirectoryMode=0750
ExecStart=/usr/sbin/kea-dhcp4 -c /etc/kea/kea-dhcp4.confThe ExecStart= line points at /etc/kea/kea-dhcp4.conf. The unit runs as user kea with CAP_NET_BIND_SERVICE and CAP_NET_RAW for DHCP sockets.
| Item | Location |
|---|---|
| DHCPv4 configuration | /etc/kea/kea-dhcp4.conf |
| Service | kea-dhcp4.service |
| Default lease database | /var/lib/kea/kea-leases4.csv |
| Packaged examples | /usr/share/doc/kea/examples/ after installing kea-doc |
Run dnf info kea on your system if you need the repository summary or available version before change windows.
Configure Kea DHCPv4
Back up the shipped file before you replace it:
sudo cp -a /etc/kea/kea-dhcp4.conf /etc/kea/kea-dhcp4.conf.backupReplace the router and DNS addresses with services that actually exist on your network. On a completely isolated lab without a router or DNS server, remove the routers and domain-name-servers entries. Advertising placeholder addresses proves option delivery but does not provide working Internet access or name resolution. When you need a working resolver on 192.168.56.10, set up BIND on the same lab network before you point clients at it through Kea.
The lab uses one complete JSON document with interface selection, lease timers, a single subnet4, a dynamic pool, default gateway and DNS options, a memfile lease database, one host reservation, and basic logging:
{
"Dhcp4": {
"interfaces-config": {
"interfaces": [ "enp0s8" ]
},
"lease-database": {
"type": "memfile",
"persist": true,
"name": "/var/lib/kea/kea-leases4.csv"
},
"valid-lifetime": 3600,
"renew-timer": 900,
"rebind-timer": 1800,
"loggers": [
{
"name": "kea-dhcp4",
"output_options": [
{
"output": "stdout"
}
],
"severity": "INFO"
}
],
"subnet4": [
{
"id": 1,
"subnet": "192.168.56.0/24",
"pools": [
{
"pool": "192.168.56.100 - 192.168.56.150"
}
],
"option-data": [
{
"name": "routers",
"data": "192.168.56.1"
},
{
"name": "domain-name-servers",
"data": "192.168.56.10"
},
{
"name": "domain-name",
"data": "example.test"
}
],
"reservations": [
{
"hw-address": "52:54:00:12:34:56",
"ip-address": "192.168.56.50",
"hostname": "reserved-client"
}
]
}
]
}
}Save that content to /etc/kea/kea-dhcp4.conf on the server. Change enp0s8 to your DHCP-facing interface and align addresses with your lab table.
| Setting | Purpose |
|---|---|
interfaces |
NIC that receives client broadcasts |
subnet |
IPv4 network Kea serves |
id |
Unique numeric ID for the subnet |
pool |
Addresses available for dynamic clients |
routers |
Default gateway option (option 3) |
domain-name-servers |
DNS resolvers (option 6) |
valid-lifetime |
Lease lifetime in seconds |
reservations |
Fixed mapping for a known MAC |
lease-database |
Persistent memfile CSV backend |
Keep the reserved address outside the dynamic pool when you can. Here 192.168.56.50 is reserved while the pool starts at 192.168.56.100, which makes allocation behavior easier to read in logs and in the lease file.
Minimal lab without gateway or DNS options
When you only need address assignment and reservations, drop routers, domain-name-servers, and domain-name from option-data. Clients still get leases, and you avoid pointing them at a DNS address that does not exist yet.
"option-data": []Or remove the option-data array entirely when you have no options to send:
"pools": [
{
"pool": "192.168.56.100 - 192.168.56.150"
}
],
"reservations": [
{
"hw-address": "52:54:00:12:34:56",
"ip-address": "192.168.56.50",
"hostname": "reserved-client"
}
]Add gateway and DNS options back only when those services exist and you have verified reachability from a client. For DNS, install and test BIND first, then set domain-name-servers to that server's address.
Validate and start Kea
Test JSON syntax before you restart the service:
sudo kea-dhcp4 -t /etc/kea/kea-dhcp4.confA valid file ends with INFO lines about the added subnet and listening interface. The command does not print Syntax check failed.
Kea listens on UDP 67 on the DHCP-facing interface. If the server has more than one network interface, enp0s8 may sit in a different firewalld zone than the default, so open the dhcp service on the zone bound to that NIC.
Find which zone owns the lab interface:
sudo firewall-cmd --get-zone-of-interface=enp0s8Sample output:
publicAdd the DHCP service to that zone permanently. Replace public if your interface belongs to another zone such as internal:
sudo firewall-cmd --permanent --zone=public --add-service=dhcpSample output:
successReload firewalld so the permanent rule is active:
sudo firewall-cmd --reloadSample output:
successConfirm the rule is present in both runtime and permanent configuration:
sudo firewall-cmd --zone=public --query-service=dhcpSample output:
yessudo firewall-cmd --permanent --zone=public --query-service=dhcpSample output:
yesBoth commands should return yes before you start Kea.
Enable and start Kea:
sudo systemctl enable --now kea-dhcp4Check service health:
systemctl status kea-dhcp4 --no-pagerSample output:
● kea-dhcp4.service - Kea DHCPv4 Server
Loaded: loaded (/usr/lib/systemd/system/kea-dhcp4.service; enabled; preset: disabled)
Active: active (running) since Wed 2026-07-22 09:55:08 IST
Main PID: 61344 (kea-dhcp4)
Status: "Dispatching packets..."active (running) and Dispatching packets... mean the daemon passed its configuration check and entered the main loop.
Confirm the server listens on UDP port 67:
sudo ss -lunp | grep ':67 'Sample output:
UNCONN 0 0 192.168.56.10:67 0.0.0.0:* users:(("kea-dhcp4",pid=61344,fd=12))Kea is bound to the server address on the lab subnet. If this line is missing, recheck interfaces-config and systemctl status kea-dhcp4.
Confirm the DHCP service is allowed in the lab interface zone:
sudo firewall-cmd --zone=public --list-servicesSample output:
cockpit dhcp dhcpv6-client http sshReplace public with the zone from --get-zone-of-interface=enp0s8 when your assignment differs.
Request and verify a client lease
On the second VM, set the client NIC to automatic IPv4 configuration with NetworkManager. nmcli command can switch a profile to DHCP—replace Wired connection 1 and enp0s8 with the names from nmcli connection show and nmcli device status on the client.
sudo nmcli connection modify "Wired connection 1" ipv4.method autoApply the profile:
sudo nmcli connection up "Wired connection 1"NetworkManager sends DHCPDISCOVER on that interface. For a clean test, disconnect and bring the profile up again after you start Kea.
Check the leased address:
ip -4 address show enp0s8Sample output:
2: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500
inet 192.168.56.100/24 brd 192.168.56.255 scope global dynamic enp0s8
valid_lft 3599sec preferred_lft 3599secThe address falls inside the configured pool. The displayed valid_lft should begin near the configured valid-lifetime of 3600 seconds and count down. ip address does not show the DHCP T1 and T2 renewal timers configured through renew-timer and rebind-timer.
Inspect the default route:
ip routeSample output:
default via 192.168.56.1 dev enp0s8
192.168.56.0/24 dev enp0s8 proto kernel scope link src 192.168.56.100The client installed the routers option as default gateway 192.168.56.1.
Read DNS and domain options NetworkManager received:
nmcli device show enp0s8 | grep -E 'IP4.DNS|IP4.DOMAIN'Sample output:
IP4.DNS[1]: 192.168.56.10
IP4.DOMAIN[1]: example.testThose values come from the domain-name-servers and domain-name options in Kea. Receiving them does not prove the resolver answers queries; run dig against the server or follow the verification steps in Configure BIND DNS server if you installed BIND on that host.
Confirm the client can reach the DHCP server on the local segment with ping command:
ping -c 2 192.168.56.10Sample output:
PING 192.168.56.10 (192.168.56.10) 56(84) bytes of data.
64 bytes from 192.168.56.10: icmp_seq=1 ttl=64 time=0.36 ms
64 bytes from 192.168.56.10: icmp_seq=2 ttl=64 time=0.08 ms
--- 192.168.56.10 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002msLayer 3 connectivity to the server confirms the lease is usable on the local segment. It does not validate the configured gateway at 192.168.56.1 unless that router exists and accepts traffic.
Configure and test a MAC reservation
Host reservations map a client MAC address to a fixed IPv4 address. Find the client MAC on the test system:
ip link show enp0s8Sample output:
2: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500
link/ether 52:54:00:12:34:56 brd ff:ff:ff:ff:ff:ffAdd or update the reservations block under the same subnet4 entry (already present in the example JSON above). After you change the file, validate syntax:
sudo kea-dhcp4 -t /etc/kea/kea-dhcp4.confRestart Kea to load the reservation:
sudo systemctl restart kea-dhcp4On the client, release the old lease and request a new one. With NetworkManager, bring the connection down and up:
sudo nmcli connection down "Wired connection 1"sudo nmcli connection up "Wired connection 1"Confirm the reserved address:
ip -4 address show enp0s8Sample output:
2: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500
inet 192.168.56.50/24 brd 192.168.56.255 scope global dynamic enp0s8
valid_lft 3598sec preferred_lft 3598secThe client with MAC 52:54:00:12:34:56 receives 192.168.56.50 instead of a pool address. The lease timer should again start near 3600 seconds. If the old address persists, clear the lease state on the client and verify you edited the correct MAC and subnet section.
Inspect the Kea lease database and logs
Kea writes active IPv4 leases to the memfile CSV. View them in columns:
sudo column -s, -t /var/lib/kea/kea-leases4.csvSample output:
address hwaddr client_id valid_lifetime expire subnet_id fqdn_fwd fqdn_rev hostname state user_context pool_id
192.168.56.100 52:54:00:ab:cd:ef 3600 1784697912 1 0 0 0 0
192.168.56.50 52:54:00:12:34:56 3600 1784698520 1 0 0 reserved-client 0 0Useful fields include the assigned address, hardware address, lease lifetime, expiration timestamp, subnet ID, hostname, and pool_id when Kea assigns from a dynamic pool.
Do not edit /var/lib/kea/kea-leases4.csv while kea-dhcp4 is running. Kea keeps authoritative state in memory and can overwrite or corrupt manual changes.
Read recent service logs:
sudo journalctl -u kea-dhcp4 --no-pager -n 10Sample output:
Jul 22 09:55:31 kea-dhcp4[61344]: DHCP4_PACKET_RECEIVED ... DHCPDISCOVER ... on interface enp0s8
Jul 22 09:55:31 kea-dhcp4[61344]: DHCP4_LEASE_OFFER ... lease 192.168.56.50 will be offered
Jul 22 09:55:31 kea-dhcp4[61344]: DHCP4_PACKET_SEND ... DHCPOFFER ... to 192.168.56.50Follow logs live while you renew a client lease:
sudo journalctl -u kea-dhcp4 -fStop the follow with Ctrl+C when you are done.
To watch packets on the wire, install tcpdump if it is not already present, then capture DHCP traffic with tcpdump command on the server interface during a fresh client acquisition:
sudo dnf install tcpdumpsudo tcpdump -ni enp0s8 'port 67 or port 68' -c 4Sample output:
09:55:20.112401 IP 0.0.0.0.bootpc > 255.255.255.255.bootps: BOOTP/DHCP, Request from 52:54:00:ab:cd:ef, length 300, xid 0x8a1c2f10, Flags [none]
DHCP-Message Discover
09:55:20.113522 IP 192.168.56.10.bootps > 192.168.56.100.bootpc: BOOTP/DHCP, Reply, length 338, xid 0x8a1c2f10, Flags [none]
Your-IP 192.168.56.100
DHCP-Message Offer
09:55:20.115801 IP 0.0.0.0.bootpc > 255.255.255.255.bootps: BOOTP/DHCP, Request from 52:54:00:ab:cd:ef, length 300, xid 0x8a1c2f10, Flags [none]
DHCP-Message Request
09:55:20.116204 IP 192.168.56.10.bootps > 192.168.56.100.bootpc: BOOTP/DHCP, Reply, length 338, xid 0x8a1c2f10, Flags [none]
Your-IP 192.168.56.100
DHCP-Message ACKA full DORA exchange shows DISCOVER, OFFER, REQUEST, and ACK between the client and 192.168.56.10. If you see replies from a different server ID, another DHCP service is still active on the segment.
After a server reboot, confirm kea-dhcp4 is enabled and clients can renew:
sudo systemctl is-enabled kea-dhcp4Sample output:
enabledExisting leases reload from the CSV when the service starts with persist: true.
Kea management API note
Kea supports runtime management commands through control sockets and an HTTP or HTTPS management API. In Kea 3.0.3, the older kea-ctrl-agent component is deprecated because Kea daemons can expose HTTP or HTTPS control endpoints directly. API configuration, authentication, TLS, lease commands, and remote reconfiguration belong in a separate management article. Do not deploy the deprecated Control Agent on new installations.
Troubleshoot Kea DHCP failures
| Symptom | Likely cause | Fix |
|---|---|---|
dnf install dhcp-server fails on EL 10 |
Kea replaced ISC DHCP on EL 10 | Install kea; on EL 8/9 check whether dhcp-server is still available |
kea-dhcp4 fails to start |
JSON syntax or subnet mismatch | Run sudo kea-dhcp4 -t /etc/kea/kea-dhcp4.conf and read the error line |
| Client receives no offer | Wrong interface, firewall zone, VLAN, or competing DHCP | Check interfaces-config, firewall-cmd --get-zone-of-interface, and disable other DHCP servers |
| Kea does not listen on UDP 67 | Service down or interface not listed | Check systemctl status kea-dhcp4, then confirm UDP 67 with ss -lunp |
subnet4 not selected |
Server NIC not in the configured subnet | Align ip -br address with the subnet value |
| Client gets address from wrong range | Another DHCP server answered first | Isolate the lab network; capture DHCP with tcpdump on the server NIC |
| Client gets address but no gateway | Missing routers option |
Add routers under option-data for the subnet |
| Client gets address but DNS fails | Wrong resolver or unreachable DNS host | Fix domain-name-servers; install BIND with Configure BIND DNS server and test with dig |
git push or SSH fails after DHCP testing |
Client applied a bad DNS address to /etc/resolv.conf |
Remove domain-name-servers from Kea when no resolver runs; fix or restore /etc/resolv.conf on the affected host |
| Reservation ignored | Wrong MAC, stale lease, or reservation in wrong subnet | Verify ip link, release client lease, restart Kea |
| Reserved address conflicts | Address already leased or statically configured | Remove conflicting lease or choose another reserved IP |
| JSON parser error | Missing comma, quote, or brace | Re-run kea-dhcp4 -t and fix the reported position |
| Lease CSV appears empty | Service not running or wrong database path | Check lease-database name and service status |
| Firewall rule present but client still fails | Client and server not on same broadcast domain | Verify Layer 2 attachment; relays need a separate guide |
| Works locally but not across routers | Remote subnet without relay | Configure a DHCP relay toward this server |
When a syntax test fails, Kea prints a precise location:
sudo kea-dhcp4 -t /tmp/kea-bad.jsonSample output:
Syntax check failed with: /tmp/kea-bad.json:1.24: syntax error, unexpected }, expecting ]Fix the JSON at the reported line and column, then test the production file again before you restart the service.
References
- Red Hat Enterprise Linux 10 — Providing DHCP services
- Red Hat Enterprise Linux 10 — Infrastructure services: Kea replaces ISC DHCP
- ISC Kea 3.0.3 Administrator Reference Manual
- ISC Kea 3.0.3 DHCPv4 server
- ISC Kea 3.0.3 host reservations
- ISC Kea 3.0.3 lease storage
Summary
On RHEL 10 and compatible Rocky Linux 10, AlmaLinux 10, and Oracle Linux 10 systems, Kea replaces the legacy dhcp-server package. Install kea, define one subnet4 block in /etc/kea/kea-dhcp4.conf, validate with kea-dhcp4 -t, allow the dhcp service in the firewalld zone assigned to your DHCP-facing NIC, and enable kea-dhcp4.service. Clients on the same broadcast domain receive pool addresses or MAC reservations, while leases persist in /var/lib/kea/kea-leases4.csv. EL 8 and EL 9 may still use ISC dhcp-server instead. Use journalctl and tcpdump when you need to separate configuration mistakes from network or duplicate-server problems.

