Skip to content

DHCP Server Scripts, BOOTP Support, and Lease Logging

DHCP Server Scripts, BOOTP Support, and Lease Logging

Section titled “DHCP Server Scripts, BOOTP Support, and Lease Logging”

RouterOS DHCP server can run a script on every lease event, enabling automation such as dynamic queue creation, firewall address-list management, and external webhook notifications. This guide also covers BOOTP client support, lease event logging, and integrating DHCP lease data with network discovery.

For custom DHCP option definitions, option sets, vendor-class matchers, and PXE boot configuration, see Custom DHCP Options, PXE Boot, and Vendor Option Sets.


The lease-script property on a DHCP server names a RouterOS script (or inline code) that runs after each lease is assigned or de-assigned. RouterOS injects a set of read-only environment variables into the script scope that describe the event.

Attach a script to a DHCP server:

/ip dhcp-server set [find name=dhcp1] lease-script=my-lease-script

Or set inline code directly:

/ip dhcp-server set [find name=dhcp1] \
lease-script=":log info (\"bound=\".\$leaseBound.\" ip=\".\$leaseActIP)"
VariableTypeDescription
$leaseBound"1" / "0""1" when lease is being assigned; "0" on release or expiry
$leaseServerNamestringName of the DHCP server that fired the event
$leaseActIPstringIP address assigned to the client
$leaseActMACstringClient MAC address
$lease-hostnamestringClient-provided hostname (option 12), empty if not sent
$lease-optionsarrayArray of option codes received from the client
$lease-agent-circuit-idstringOption 82 circuit-id (set by relay agent, empty otherwise)
$lease-agent-remote-idstringOption 82 remote-id (set by relay agent, empty otherwise)

Variable names with hyphens: Variables like $lease-hostname require the $"lease-hostname" syntax in RouterOS scripting when referenced in expressions. Use $leaseActIP and $leaseActMAC directly (no quotes needed).

The $leaseBound variable is always "1" or "0" as a string, not an integer. Compare it with "1" or "0" accordingly.

The simplest lease script logs every bind and release to the system log:

# /system script
/system script add name=dhcp-log-events source={
:local ip $leaseActIP
:local mac $leaseActMAC
:local host $"lease-hostname"
:if ($leaseBound = "1") do={
:log info ("DHCP bound ip=" . $ip . " mac=" . $mac . " host=" . $host)
} else={
:log warning ("DHCP released ip=" . $ip . " mac=" . $mac . " host=" . $host)
}
}
/ip dhcp-server set [find name=dhcp1] lease-script=dhcp-log-events

Watch events in real time:

/log print follow where topics~"dhcp"

Create a simple queue when a client connects, and remove it on disconnect. This approach is suitable for per-host rate limiting when static lease rate-limit is not sufficient (for example, when limits vary based on external logic).

Note: For static per-host rate limiting, the built-in rate-limit field on a static DHCP lease is simpler and does not require a script.

/system script add name=dhcp-queue-manager source={
:local qname ("client-" . $leaseActMAC)
:if ($leaseBound = "1") do={
# Create queue on first bind; skip if already exists
:if ([:len [/queue simple find where name=$qname]] = 0) do={
/queue simple add \
name=$qname \
target=($leaseActIP . "/32") \
max-limit=10M/10M \
comment=$leaseActMAC
}
} else={
/queue simple remove [find where name=$qname]
}
}
/ip dhcp-server set [find name=dhcp1] lease-script=dhcp-queue-manager

Automatically populate a firewall address list with active DHCP clients:

/system script add name=dhcp-address-list source={
:if ($leaseBound = "1") do={
/ip firewall address-list add \
list=dhcp-active \
address=$leaseActIP \
comment=$leaseActMAC
} else={
/ip firewall address-list remove \
[find where list=dhcp-active and address=$leaseActIP]
}
}
/ip dhcp-server set [find name=dhcp1] lease-script=dhcp-address-list

Use the address list in firewall rules to, for example, allow internet access only to DHCP-bound clients:

/ip firewall filter add chain=forward action=accept \
src-address-list=dhcp-active comment="Allow DHCP clients"

Notify an external system via HTTP when a client binds. The fetch command runs asynchronously from the script context:

/system script add name=dhcp-webhook source={
:if ($leaseBound = "1") do={
/tool fetch \
url=("https://nms.example.net/dhcp?" \
. "ip=" . $leaseActIP \
. "&mac=" . $leaseActMAC \
. "&host=" . $"lease-hostname") \
keep-result=no
}
}
/ip dhcp-server set [find name=dhcp1] lease-script=dhcp-webhook

When DHCP relay inserts Option 82, the circuit-id and remote-id are available in scripts. This is useful in carrier or ISP environments to identify the subscriber’s physical port:

/system script add name=dhcp-option82-log source={
:local circuit $"lease-agent-circuit-id"
:local remote $"lease-agent-remote-id"
:if ($leaseBound = "1") do={
:log info ("DHCP bound ip=" . $leaseActIP \
. " circuit=" . $circuit \
. " remote=" . $remote)
}
}

If a lease script silently fails, add a :log warning at the top and monitor the system log. Script errors (syntax, missing variables) appear in the log under the script topic:

# Temporarily increase log verbosity for scripts
/system logging add topics=script action=memory
# Watch for script errors
/log print follow where topics~"script"
# Also watch DHCP topic
/log print follow where topics~"dhcp"

Test your script manually by creating a temporary scheduler trigger or running it via /system script run with variables set in the script itself for validation.


BOOTP (Bootstrap Protocol) is the predecessor to DHCP. Some legacy devices (embedded controllers, older network printers, some PXE clients) send BOOTP requests instead of DHCP DISCOVER messages. RouterOS handles both protocols on the same UDP port 67.

Two server properties control BOOTP behavior:

PropertyDefaultDescription
bootp-supportstaticWhich BOOTP clients receive responses
bootp-lease-timeforeverLease duration offered to BOOTP clients

bootp-support values:

ValueBehavior
noneIgnore all BOOTP requests
staticRespond only if a static lease matches the client MAC
dynamicRespond with both static and dynamic address assignments

bootp-lease-time values:

ValueBehavior
foreverLease never expires (BOOTP semantics)
lease-timeUse the server’s configured lease-time
<time>Fixed duration (e.g., 1d, 12h)

To reject all BOOTP clients and serve only DHCP:

/ip dhcp-server set [find name=dhcp1] bootp-support=none

Allow BOOTP only for pre-mapped devices. Create a static lease for each BOOTP device’s MAC address, then set bootp-support=static:

# Static lease for a BOOTP device
/ip dhcp-server lease add \
mac-address=00:11:22:33:44:55 \
address=192.168.88.50 \
comment="Legacy BOOTP printer"
# Restrict BOOTP to static mappings only
/ip dhcp-server set [find name=dhcp1] \
bootp-support=static \
bootp-lease-time=forever

With bootp-lease-time=forever, the device retains its address indefinitely without renewal. This matches traditional BOOTP behavior where there is no lease refresh mechanism.

Allow unrecognized BOOTP clients to receive an address from the pool:

/ip dhcp-server set [find name=dhcp1] \
bootp-support=dynamic \
bootp-lease-time=lease-time

Using bootp-lease-time=lease-time causes BOOTP clients to receive the same expiry time as DHCP clients, but since BOOTP has no renewal mechanism, the address will remain assigned as long as no other client claims it.


RouterOS DHCP server emits log messages under the dhcp topic for normal operation. Debug-level messages (option parsing errors, packet details) appear under dhcp,debug.

View active DHCP log messages:

/log print where topics~"dhcp"

Follow lease events in real time:

/log print follow where topics~"dhcp"

Create a logging action targeting your syslog server, then attach it to the dhcp topic:

/system logging action
add name=dhcp-syslog \
target=remote \
remote=192.0.2.10 \
remote-port=514 \
bsd-syslog=yes \
syslog-facility=daemon \
syslog-severity=info
/system logging
add topics=dhcp action=dhcp-syslog
add topics=dhcp,debug action=dhcp-syslog

For local retention and post-processing, log DHCP events to a rotating file:

/system logging action
add name=dhcp-file \
target=disk \
disk-file-name=dhcp-leases \
disk-lines-per-file=10000 \
disk-file-count=5
/system logging
add topics=dhcp action=dhcp-file

Files are written to the router’s flash storage and are accessible via /file print or FTP/SCP export.

The DHCP lease table shows current state but does not maintain history. For persistent, structured lease history, write events from the lease-script:

/system script add name=dhcp-history source={
:local ts [/system clock get time]
:local dt [/system clock get date]
:local event
:if ($leaseBound = "1") do={ :set event "BOUND" } else={ :set event "RELEASED" }
:local line ($dt . " " . $ts . " " . $event \
. " ip=" . $leaseActIP \
. " mac=" . $leaseActMAC \
. " host=" . $"lease-hostname" \
. " srv=" . $leaseServerName)
:log info $line
# Optionally append to a file (requires /file manipulation or export)
}

Tip: For high-volume environments (hundreds of leases/hour), prefer remote syslog with a dedicated log management system over file-based logging on the router.


By default, DHCP leases do not automatically create ARP entries. Enable add-arp on the DHCP server to create dynamic ARP entries for each active lease:

/ip dhcp-server set [find name=dhcp1] add-arp=yes

With add-arp=yes, each bound lease appears in /ip arp:

/ip arp print
# Example output:
# Flags: X - disabled, I - invalid, H - DHCP, D - dynamic
# # ADDRESS MAC-ADDRESS INTERFACE
# 0 DH 192.168.88.10 AA:BB:CC:DD:EE:FF bridge

The H flag identifies ARP entries created by DHCP. These entries allow the router to forward traffic to newly assigned clients immediately without waiting for ARP resolution.

For environments using a bridge interface, the bridge host table shows active MAC addresses and their source ports, which can be correlated with DHCP leases:

# Show bridge-learned MACs
/interface bridge host print
# Cross-reference with DHCP leases
/ip dhcp-server lease print

This correlation is useful for locating a client physically when you know its IP address from the DHCP lease.

DHCP Alert monitors a network interface for DHCP responses and can trigger an action when a DHCP server not on an approved list responds. This is a passive network scanning feature, not client discovery.

/ip dhcp-server alert
add interface=bridge \
alert-timeout=1m \
valid-server=192.168.88.1 \
on-alert=":log warning \"Rogue DHCP server detected!\""

Check alert state:

/ip dhcp-server alert print

The on-alert script runs when an unexpected DHCP server is detected. Use it to send a notification, block the offending MAC in the firewall, or page an administrator.

RouterOS /ip neighbor (MNDP/CDP/LLDP) is protocol-based and discovers only devices that actively send neighbor discovery frames — it does not populate from DHCP leases. To see all DHCP clients as neighbors, the clients themselves must run a supported discovery protocol.

For a unified view of all hosts on a segment, combine:

  1. /ip dhcp-server lease print — IP/MAC/hostname from DHCP
  2. /ip arp print — ARP table (enhanced by add-arp=yes)
  3. /interface bridge host print — L2 port visibility

Verify the script name is spelled correctly:

/ip dhcp-server print detail where name=dhcp1

Check that the named script exists:

/system script print where name=<script-name>

Run the script manually to check for errors:

/system script run <script-name>

Watch the script log topic for runtime errors:

/log print follow where topics~"script"

Check that bootp-support is not none:

/ip dhcp-server print detail where name=dhcp1

For bootp-support=static, verify a static lease exists for the client MAC:

/ip dhcp-server lease print where mac-address=<client-mac>

Capture traffic to confirm BOOTP requests are reaching the router:

/tool sniffer quick port=67,68 interface=<client-facing-interface>

Confirm add-arp=yes on the DHCP server:

/ip dhcp-server set [find name=dhcp1] add-arp=yes

Verify the client’s lease is in bound state:

/ip dhcp-server lease print where active-address=<ip>

FeatureLocation
Custom DHCP options and vendor-class matchersCustom DHCP Options, PXE Boot, and Vendor Option Sets
DHCP relay across subnetsDHCP Relay: Forwarding Requests Across Subnets and VLANs
Static simple queues/queue simple
Firewall address lists/ip firewall address-list
System logging/system logging