Skip to content
Networking

Auditing static DHCP leases in RouterOS: ten mismatches and four missing devices

By Victor Da Luz
MikroTik RouterOS DHCP networking Ansible homelab

I manage static DHCP leases in RouterOS through an Ansible playbook backed by a single state file - state/network.yaml - that records every device’s hostname, reserved IP, MAC address, and VLAN. The idea is simple: the state file is the source of truth, the playbook applies it to the router. In theory they stay in sync. In practice, they hadn’t been reconciled since I rebuilt the router.

The trigger was finishing up a batch of new downstream devices. Before I could treat the DHCP config as reliable, I needed to verify it actually matched what was on the network.

The problem with silent drift

Static DHCP leases fail quietly. If a device’s MAC doesn’t match its lease, RouterOS assigns it a dynamic address and the static lease sits at status=waiting. That’s the same status you’d see for a device that’s simply offline. Nothing alerts you. The device keeps working - just with a randomly assigned address instead of its reserved one.

This matters if you have firewall address lists tied to specific IPs, or if your monitoring looks for hosts by reserved address. A device drifting to a dynamic IP can silently fall out of firewall groups without anything visibly breaking.

Investigation

I started by diffing network.yaml against the Ansible playbook to find obvious gaps. A few devices were in the state file but had never been added to the playbook - easy misses from earlier sessions where I added hardware but didn’t finish the DHCP step.

The trickier findings came from comparing what the playbook thought it was configuring against what was actually active on the router. Running /ip dhcp-server lease print per VLAN showed the current active leases. For any suspicious entry, I cross-referenced the MAC shown as active against the physical device label or its settings menu.

That’s where the LG TVs became a puzzle. Five of them had their MAC entries rotated between each other - each TV’s DHCP entry contained the MAC of a different TV. They all had correct IPs, and they were all getting addresses (because RouterOS was matching the wrong TV’s MAC to the wrong static entry), which is exactly why this went unnoticed. Nothing obviously broke.

The Apple TVs had a related problem. A MAC address that belonged to one Apple TV appeared in the DHCP config for a different Apple TV, creating IP conflicts between entries.

What the audit found

Missing leases - in network.yaml but absent from the DHCP playbook:

  • MacBook Pro - was picking up a dynamic address
  • An iPhone - deliberately left dynamic; iOS MAC rotation makes static leases unreliable without configuring a fixed address in Wi-Fi settings
  • Two other devices that hadn’t made it from the state file into the playbook

MAC mismatches - wrong MAC assigned to an IP:

  • All five LG TVs - MACs cycled between entries across the IoT VLAN
  • Three Apple TVs - MAC confusion with resulting IP conflicts
  • Apple Watch - had been added with an old MAC address that didn’t match the device anymore

IP conflicts - two devices in network.yaml pointing to the same address:

  • Two Apple TVs sharing an IP
  • Apple Watch and a HomePod assigned to the same address

One entry got removed outright: an old Home Assistant Wi-Fi interface from when HA ran on a Raspberry Pi that’s no longer in the homelab. It had been sitting there pointing at a MAC that now belonged to a different device entirely.

Fixing it

The fix was methodical. For each mismatched device, I confirmed the actual MAC - either from the active lease table on the router or from the device’s settings - then updated network.yaml and re-ran the DHCP playbook. For IP conflicts, I renumbered the affected devices before running the playbook (RouterOS won’t create two static leases at the same IP, but it won’t loudly reject the attempt either).

After the reconciliation, every device with a static lease came up status=bound when it was online, or status=waiting for things that hadn’t recently connected. No unexpected dynamic leases.

Thinking about alerting

Once the leases were clean, I thought about catching future drift earlier. The RouterOS DHCP server has a lease-script property - you assign a script to a DHCP server and it runs on every lease assignment. The plan was a script that checks whether each new lease is dynamic and sends an alert through Apprise if it is:

:if ($leaseBound = "1") do={
    :local isDynamic [/ip dhcp-server lease get [find where active-address=$leaseActIP] dynamic]
    :if ($isDynamic) do={
        /tool fetch url="http://apprise.internal/notify/" \
            http-method=post \
            http-data=("body=Dynamic lease: " . $leaseActIP . " " . $leaseActMAC . " " . $leaseHostname)
    }
}

The problem is noise. The lease-script fires on every assignment, including renewals. HomePods and iOS devices renew frequently, so the alert fires constantly even for devices that are legitimately static. Getting the script to fire only on genuinely new dynamic leases requires filtering by lease age or tracking state externally - more complexity than the basic version. I wrote the script and tested it, but held off on running it long-term.

Lessons learned

  • Static DHCP leases fail silently. A mismatched MAC doesn’t break anything visible; it just makes your state file wrong. You won’t notice until you check.
  • MAC address rotation on Apple devices (iOS private MAC, Apple Watch, HomePods) makes some devices unreliable candidates for static leases. iOS lets you set “Use Fixed Address” per Wi-Fi network - worth doing for devices that need stable IPs.
  • RouterOS lease-script can work for alerting, but the signal-to-noise problem is real. Filtering to new leases only is harder than the basic implementation suggests.
  • A full reconciliation after any big network change takes a few hours and is worth it. The drift doesn’t break things; it just makes your documentation quietly wrong.

Related reading

Ready to Transform Your Career?

Let's work together to unlock your potential and achieve your professional goals.