Skip to content

Firewall Address Lists

Address lists are named groups of IP addresses and subnets that can be referenced as match criteria in firewall filter, NAT, and mangle rules. They separate the “what IPs” concern from the “what action” concern, making rulesets easier to read and maintain. Entries can be added statically at configuration time or populated dynamically by firewall rules at runtime.

Block a set of IPs and allow a trusted management range using address lists:

# Define the lists
/ip firewall address-list
add list=blocked address=203.0.113.10 comment="known bad actor"
add list=blocked address=198.51.100.0/24 comment="abusive subnet"
add list=mgmt_hosts address=192.168.100.10 comment="admin workstation"
add list=mgmt_hosts address=192.168.100.11 comment="admin workstation 2"
# Use the lists in filter rules
/ip firewall filter
add chain=input src-address-list=blocked action=drop comment="drop blocked IPs"
add chain=input src-address-list=mgmt_hosts action=accept comment="allow mgmt access"

Static entries are added manually and persist until explicitly removed. They survive reboots and are stored in the router configuration.

/ip firewall address-list
# Single host
add list=trusted_servers address=10.0.0.5 comment="app server"
# Subnet
add list=trusted_servers address=10.0.1.0/24 comment="office LAN"
# IPv6 (use /ipv6 firewall address-list)
/ipv6 firewall address-list
add list=trusted_v6 address=2001:db8::/32 comment="trusted IPv6 range"
# List all entries
/ip firewall address-list print
# List entries in a specific list
/ip firewall address-list print where list=blocked
# Remove a specific entry
/ip firewall address-list remove [find list=blocked address=203.0.113.10]
# Flush all entries from a list
/ip firewall address-list remove [find list=blocked]

Firewall rules can add IPs to address lists at runtime using the add-src-to-address-list and add-dst-to-address-list actions. Dynamic entries are created when packets match the rule and are automatically removed when their timeout expires.

Adds the packet’s source IP to a named list:

/ip firewall filter
add chain=input protocol=tcp dst-port=22 \
action=add-src-to-address-list \
address-list=ssh_scanners \
address-list-timeout=1h \
comment="flag SSH connection attempts"

Adds the packet’s destination IP to a named list — useful for tracking which internal hosts are being targeted:

/ip firewall mangle
add chain=prerouting src-address-list=suspect_clients \
action=add-dst-to-address-list \
address-list=targeted_hosts \
address-list-timeout=30m \
comment="track destinations reached by suspect clients"

The address-list-timeout parameter accepts durations in NdNhNmNs format, or 0 for no expiry (permanent entry):

ValueMeaning
0Entry is permanent (survives reboot)
30s30 seconds
5m5 minutes
1h1 hour
1d1 day
2d3h30m2 days, 3 hours, 30 minutes

Combining dynamic population with timeouts implements automatic expiry — addresses are blocked for a set period and then reinstated without manual cleanup.

/ip firewall address-list
# Manually add a host that should be blocked for 24 hours
add list=temp_blocked address=203.0.113.55 timeout=1d comment="manual 24h block"
/ip firewall filter
# Stage 1: detect port scan attempt (NEW connection to a port that should be closed)
add chain=input protocol=tcp dst-port=23,3389,4899 connection-state=new \
action=add-src-to-address-list \
address-list=port_scanners \
address-list-timeout=10m \
comment="tag port scanner — expires after 10 min"
# Stage 2: drop tagged scanners
add chain=input src-address-list=port_scanners action=drop \
comment="drop port scanners"

A classic “tarpit” pattern: an IP must trigger multiple rules before it is blacklisted. Legitimate users who mistype a password once are not blocked; persistent attackers are.

/ip firewall filter
# Stage 1: first attempt — tag for 1 minute
add chain=input protocol=tcp dst-port=22 connection-state=new \
src-address-list=!ssh_stage2 \
action=add-src-to-address-list \
address-list=ssh_stage1 \
address-list-timeout=1m \
comment="SSH stage1 — first attempt"
# Stage 2: second attempt within stage1 window — escalate for 1 hour
add chain=input protocol=tcp dst-port=22 connection-state=new \
src-address-list=ssh_stage1 \
action=add-src-to-address-list \
address-list=ssh_stage2 \
address-list-timeout=1h \
comment="SSH stage2 — repeated attempt"
# Drop anyone in stage2 (active brute-force)
add chain=input src-address-list=ssh_stage2 action=drop \
comment="drop SSH brute-force attackers"

Using Lists Across Filter, NAT, and Mangle

Section titled “Using Lists Across Filter, NAT, and Mangle”

Address lists are referenced identically across all firewall tables using src-address-list= and dst-address-list= match parameters.

/ip firewall filter
# Drop outbound traffic to known C2 servers
add chain=forward dst-address-list=known_c2 action=drop \
comment="block traffic to known C2 IPs"
# Accept management access from trusted hosts only
add chain=input src-address-list=mgmt_hosts protocol=tcp \
dst-port=22,8291 action=accept \
comment="allow management from trusted hosts"
# Drop all other management attempts
add chain=input protocol=tcp dst-port=22,8291 action=drop \
comment="drop all other management attempts"
/ip firewall nat
# Masquerade only clients in the allowed_internet list
add chain=srcnat src-address-list=allowed_internet \
out-interface=ether1-wan action=masquerade \
comment="NAT approved clients"
# Port-forward to internal server only from partner IPs
add chain=dstnat src-address-list=partners \
protocol=tcp dst-port=443 \
action=dst-nat to-addresses=10.0.0.20 to-ports=443 \
comment="DNAT partner HTTPS to internal server"
/ip firewall mangle
# Mark packets from VoIP handsets for QoS treatment
add chain=prerouting src-address-list=voip_handsets \
action=mark-packet new-packet-mark=voip passthrough=yes \
comment="mark VoIP traffic for QoS"
# Mark connections from guest VLAN for bandwidth limiting
add chain=prerouting src-address-list=guest_clients \
action=mark-connection new-connection-mark=guest passthrough=yes \
comment="tag guest connections"

RouterOS address lists function like IP sets — a single named list can be maintained independently of the rules that reference it. This pattern scales well when the same group of IPs needs different treatment in multiple rule tables.

# Maintain one list of CDN egress addresses
/ip firewall address-list
add list=cdn_egress address=192.0.2.0/24
add list=cdn_egress address=198.51.100.128/25
# Reference from filter (accept bypass)
/ip firewall filter
add chain=forward src-address-list=cdn_egress action=accept \
comment="accept CDN egress traffic"
# Reference from mangle (different QoS marking)
/ip firewall mangle
add chain=prerouting src-address-list=cdn_egress \
action=mark-packet new-packet-mark=cdn passthrough=yes \
comment="mark CDN traffic for QoS"

Prefix a list name with ! to match packets whose address is not in the list:

/ip firewall filter
# Block SSH from anyone not in the mgmt_hosts list
add chain=input protocol=tcp dst-port=22 \
src-address-list=!mgmt_hosts action=drop \
comment="SSH only from mgmt hosts"

Match on both address list membership and connection state for more precise control:

/ip firewall filter
# Accept new connections only from known partners
add chain=input src-address-list=partners \
connection-state=new action=accept \
comment="allow new connections from partners"
# Reject new connections from everyone else
add chain=input connection-state=new action=reject \
reject-with=icmp-admin-prohibited \
comment="reject all other new inbound"

Scripts combined with the scheduler can pull external IP lists (threat feeds, blocklists) into an address list on a recurring schedule.

/system script
add name=update-ext-blacklist source={
:local url "https://example.org/blocklist.txt"
:local dstPath "ext-blacklist.txt"
:local listName "ext-blacklist"
# Download the latest list
/tool fetch url=$url dst-path=$dstPath
# Remove previously imported entries (identified by comment)
/ip firewall address-list remove [find list=$listName comment="auto-import"]
# Parse and import each line
:local contents [/file get $dstPath contents]
:foreach line in=[:toarray $contents] do={
# Skip blank lines and comments starting with #
:if ([:len $line] > 0 && [:find $line "#"] = (-1)) do={
/ip firewall address-list add list=$listName address=$line \
comment="auto-import"
}
}
}
/system scheduler
add name=blacklist-refresh interval=1h on-event=update-ext-blacklist \
comment="refresh external blocklist every hour"

Pre-seed the list with known-bad ranges, then let the scheduler top it up:

# Permanent static entries (no timeout = survive reboot)
/ip firewall address-list
add list=ext-blacklist address=192.0.2.0/24 comment="static seed — RFC5737"
add list=ext-blacklist address=198.51.100.0/24 comment="static seed — RFC5737"
# Drop everything in the list
/ip firewall filter
add chain=forward dst-address-list=ext-blacklist action=drop \
comment="block destinations in external blacklist"
add chain=forward src-address-list=ext-blacklist action=drop \
comment="block sources in external blacklist"

Note: /tool fetch requires a reachable DNS resolver and outbound HTTPS. Ensure the firewall does not block the router’s own outbound traffic before enabling scheduled fetches.

Collect known-bad source IPs in one list and drop them early in the input chain:

/ip firewall address-list
add list=ip-blacklist address=203.0.113.10 comment="abusive host"
add list=ip-blacklist address=198.51.100.0/24 comment="abusive subnet"
/ip firewall filter
# Place this rule near the top of the input chain
add chain=input src-address-list=ip-blacklist action=drop \
comment="drop blacklisted sources"

Outbound IP Blacklist (C2 / Malware Blocking)

Section titled “Outbound IP Blacklist (C2 / Malware Blocking)”
/ip firewall address-list
add list=c2-blacklist address=192.0.2.50 comment="known C2 server"
add list=c2-blacklist address=198.51.100.99 comment="known C2 server"
/ip firewall filter
add chain=forward dst-address-list=c2-blacklist action=drop \
comment="block outbound to known C2 IPs"

A whitelist accepts traffic from known-good sources and prevents them from being affected by later drop rules:

/ip firewall address-list
add list=ip-whitelist address=10.0.0.0/8 comment="internal ranges"
add list=ip-whitelist address=192.168.0.0/16 comment="RFC1918"
/ip firewall filter
# Accept before any blocking rules
add chain=input src-address-list=ip-whitelist action=accept \
comment="accept trusted whitelist — early return"
# Blacklist rules follow
add chain=input src-address-list=ip-blacklist action=drop \
comment="drop blacklisted sources"

Whitelisted IPs are never blacklisted; all other repeat offenders are blocked automatically:

/ip firewall filter
# 1. Skip whitelisted sources — they can never be blacklisted
add chain=input src-address-list=ip-whitelist action=accept \
comment="whitelist — bypass all checks"
# 2. Drop confirmed offenders
add chain=input src-address-list=ip-blacklist action=drop \
comment="drop dynamic blacklist"
# 3. Detect offenders (SSH brute force) and promote to blacklist
add chain=input protocol=tcp dst-port=22 connection-state=new \
src-address-list=!ip-whitelist \
action=add-src-to-address-list \
address-list=ssh-attempts \
address-list-timeout=2m \
comment="flag SSH attempt"
add chain=input protocol=tcp dst-port=22 connection-state=new \
src-address-list=ssh-attempts \
action=add-src-to-address-list \
address-list=ip-blacklist \
address-list-timeout=1d \
comment="promote repeat offender to blacklist for 24h"

Dynamic entries created by firewall rules expire automatically based on address-list-timeout. Entries added directly to /ip firewall address-list can also carry a timeout:

# Manual timed block — entry is removed automatically after the timeout
/ip firewall address-list
add list=ip-blacklist address=203.0.113.77 timeout=6h \
comment="manual block — expires in 6 hours"
# View current dynamic entries and their remaining TTL
/ip firewall address-list print where dynamic=yes
# Remove all expired-ish entries manually (already expired entries are auto-removed;
# this removes entries you added with a comment marker)
/ip firewall address-list remove [find list=ip-blacklist comment="auto-import"]
ParameterDescription
listName of the address list
addressIPv4 address, subnet (CIDR), or range (e.g. 10.0.0.1-10.0.0.10)
timeoutOptional expiry duration; 0 = permanent
commentFree-text annotation
disabledIf yes, entry is ignored by rules

Dynamic Action Parameters (in filter/mangle/nat rules)

Section titled “Dynamic Action Parameters (in filter/mangle/nat rules)”
ParameterDescription
actionadd-src-to-address-list or add-dst-to-address-list
address-listName of the list to add the IP to
address-list-timeoutHow long the entry persists; 0 = permanent

Match Parameters (in filter/mangle/nat rules)

Section titled “Match Parameters (in filter/mangle/nat rules)”
ParameterDescription
src-address-list=<name>Match packets whose source IP is in the list
dst-address-list=<name>Match packets whose destination IP is in the list
src-address-list=!<name>Match packets whose source IP is not in the list
dst-address-list=!<name>Match packets whose destination IP is not in the list