Configuring DHCP Relay Agent on Linux: Forwarding IP Assignment Requests Across Multiple Subnets and VLANs

Network tutorial - IT technology blog
Network tutorial - IT technology blog

The Problem with Multi-VLAN Networks

I’ve run into this situation more than once: setting up an environment with multiple VLANs — VLAN 10 for servers, VLAN 20 for workstations, VLAN 30 for IoT devices. The DHCP server runs centrally on VLAN 10. Everything works fine until a machine on VLAN 20 boots up… and just sits there waiting for an IP that never comes.

Restarting the network interface, restarting the DHCP server, checking cables — nothing worked. Then it clicked: DHCP relies on broadcast, and broadcasts don’t cross routers or layer-3 switches. VLAN 20 and VLAN 10 are completely separate broadcast domains.

Why DHCP Can’t Cross Subnet Boundaries on Its Own

The DHCP Discovery packet a client sends is a broadcast to 255.255.255.255. When the router receives it, it simply drops it — routers don’t forward broadcasts by default.

Specifically, when a client on 192.168.20.0/24 sends a DHCP Discover:

Source IP:   0.0.0.0
Destination: 255.255.255.255
Protocol:    UDP 68 (client) → UDP 67 (server)

This packet only exists within the broadcast domain of VLAN 20. The router won’t forward it to VLAN 10 where the DHCP server is running. The client waits about 60 seconds with no response, then self-assigns a 169.254.x.x (APIPA) address — and the network is effectively useless.

Three Approaches — and Which One You Should Actually Use

Option 1: Run a Separate DHCP Server per Subnet

The simplest approach on the surface: one DHCP server per subnet. But in practice you’ll immediately run into:

  • Decentralized management with no clear view of who has which IP
  • Configuration changes require updates in multiple places
  • Debugging issues becomes twice as hard

Option 2: Proxy ARP or Unnumbered Interface

Configure the router to simulate ARP on behalf of the DHCP server. It works in theory, but ARP cache timeouts falling out of sync with DHCP lease renewals will produce random, nearly impossible-to-reproduce errors.

Option 3: DHCP Relay Agent — The RFC-Standard Solution

RFC 3046 was created exactly for this problem. A DHCP Relay Agent sits at each subnet, receives the client’s broadcast, and converts it to a unicast packet sent directly to the centralized DHCP server. The server responds, and the relay forwards it back to the client.

The flow looks like this:

Client (VLAN 20) --[broadcast]--> Relay Agent (192.168.20.1)
Relay Agent ------[unicast]-----> DHCP Server (192.168.10.10)
DHCP Server ------[unicast]-----> Relay Agent
Relay Agent ------[unicast]-----> Client

Installing and Configuring DHCP Relay Agent on Linux

Step 1: Install isc-dhcp-relay

# Ubuntu/Debian
sudo apt update && sudo apt install isc-dhcp-relay -y

# CentOS/RHEL/Rocky Linux
sudo dnf install dhcp-relay -y

On Debian/Ubuntu, the installer will prompt for the DHCP server address and interfaces to relay. I usually skip that step and configure the file manually to be safe.

Step 2: Map Out Your Network Topology

Before typing any commands, you need to know your IP ranges clearly. I often use toolcraft.app/en/tools/developer/ip-subnet-calculator — enter a CIDR and it instantly shows the network range, broadcast address, and available host count. Calculating /26 or /27 by hand isn’t hard, but when you’re juggling 5–6 subnets at once, mistakes are easy to make.

Example topology used in this guide:

VLAN 10 (Server):      192.168.10.0/24  — interface eth0.10  (IP: 192.168.10.1)
VLAN 20 (Workstation): 192.168.20.0/24  — interface eth0.20  (IP: 192.168.20.1)
VLAN 30 (IoT):         192.168.30.0/24  — interface eth0.30  (IP: 192.168.30.1)
DHCP Server:           192.168.10.10
Linux Router/Relay:    has IPs on all 3 VLANs

Step 3: Configure isc-dhcp-relay

sudo nano /etc/default/isc-dhcp-relay
# DHCP server address (multiple servers separated by spaces)
SERVERS="192.168.10.10"

# Interfaces the relay listens on (all relevant VLANs)
INTERFACES="eth0.10 eth0.20 eth0.30"

# -a: append agent information option (circuit-id)
OPTIONS="-a"

The -a option matters more than I initially thought — it appends information about which interface received the request to the packet. The DHCP server uses this to distinguish which subnet the client is coming from and assign the correct IP range.

Step 4: Declare Subnets on the DHCP Server

On the DHCP server side (also running isc-dhcp-server), you must declare a pool for each subnet:

sudo nano /etc/dhcp/dhcpd.conf
authoritative;
default-lease-time 86400;
max-lease-time 172800;

# VLAN 10 — server network, no pool needed if using static IPs
subnet 192.168.10.0 netmask 255.255.255.0 {
    option routers 192.168.10.1;
    option domain-name-servers 8.8.8.8, 1.1.1.1;
}

# VLAN 20 — workstations
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;
    option domain-name-servers 8.8.8.8, 1.1.1.1;
    option broadcast-address 192.168.20.255;
    default-lease-time 43200;
}

# VLAN 30 — IoT devices (shorter lease time)
subnet 192.168.30.0 netmask 255.255.255.0 {
    range 192.168.30.50 192.168.30.150;
    option routers 192.168.30.1;
    option domain-name-servers 8.8.8.8;
    default-lease-time 3600;
    max-lease-time 7200;
}

Important tip: The DHCP server must have a subnet declaration for every network — including the one the server itself is on (VLAN 10). Without it, dhcpd will throw an error and refuse to start.

Step 5: Enable the Services

# On the relay agent machine (Linux router)
sudo systemctl enable --now isc-dhcp-relay
sudo systemctl status isc-dhcp-relay

# On the DHCP server
sudo systemctl enable --now isc-dhcp-server
sudo systemctl status isc-dhcp-server

Debugging When Clients Still Can’t Get an IP

The first time I set this up, I spent nearly two hours debugging. Here are the most common issues you’ll encounter:

Check Whether the Relay Agent Is Receiving Requests

# View logs in real-time
sudo journalctl -u isc-dhcp-relay -f

# Capture packets directly on the interface
sudo tcpdump -i eth0.20 port 67 or port 68 -n -v

Check Whether the DHCP Server Sees the Forwarded Requests

sudo journalctl -u isc-dhcp-server -f

# Check issued leases
cat /var/lib/dhcp/dhcpd.leases

Common Errors

“No subnet declaration for eth0.20” — Missing subnet declaration in dhcpd.conf. Add the corresponding subnet block and you’re done.

Relay is running but not forwarding — The firewall on the DHCP server may be blocking it. Check:

# UFW
sudo ufw allow 67/udp

# iptables
sudo iptables -A INPUT -p udp --dport 67 -j ACCEPT

Client gets an IP but from the wrong subnet — The -a option is missing from the relay config. Without it, the server can’t tell which VLAN the request came from and hands out the first available IP it finds. Add -a to OPTIONS and restart the relay.

When to Use Relay on Linux vs. on a Switch

If your infrastructure includes a layer-3 switch (Cisco, Juniper, MikroTik RouterOS), configure ip helper-address directly on the switch’s VLAN interface — it’s cleaner and has fewer failure points.

# Cisco IOS example
interface Vlan20
 ip address 192.168.20.1 255.255.255.0
 ip helper-address 192.168.10.10

But in a homelab, on a VPS acting as a router, or in a pure Linux infrastructure, isc-dhcp-relay is stable and production-ready for medium-scale environments. I’ve been running it for a small office network with around 60 devices across 4 VLANs, and it hasn’t had any issues in months.

Share: