RouterOS 7 DMZ: isolate one server, allow exactly one LAN host

Put a single server on its own RouterOS port and subnet, let it reach the internet and one NAS, block the rest of the LAN, and verify it, with the rules we ran.

By the ailog editors · Published Oct 1, 2026 · 8 min read · How we work
In short
  • Take the server’s router port out of the LAN bridge, give it its own subnet, and add it to a new interface list called DMZ. That takes four commands.
  • Two forward rules, in this order: accept DMZ → the one LAN host it needs (our NAS), then drop DMZ → the LAN. Internet access keeps working through the default configuration.
  • One srcnat masquerade rule makes the server’s traffic to the NAS appear to come from the router’s LAN address, because the NAS firewall only trusts the LAN subnet.
  • Change the server’s IP on a systemd timer with automatic rollback, then test from the server itself: internet OK, NAS OK, other LAN hosts blocked.

We had just brought up a backup server at home. It pulls a full copy of the NAS, and we plan to run a few small web services on it later. Once a box runs internet-facing services, it shouldn’t be able to reach the laptops, printers and appliances on the home LAN. It does need to read from the NAS. This post covers the RouterOS 7 configuration we applied on a MikroTik RB5009 to put that server in a one-port DMZ, explains why each rule is there, and shows how we checked it.

All addresses are examples. LAN = 10.0.0.0/24 (router 10.0.0.1, NAS 10.0.0.117), DMZ = 10.0.20.0/24 (router 10.0.20.1, server 10.0.20.20), and the server is cabled to ether4.

The design, and the one physical requirement

Direction Allowed? How
Server → internet yes defconf srcnat masquerade to WAN, no forward rule blocks it
Server → NAS yes explicit accept rule + masquerade
Server → any other LAN host no drop rule
Server → router services (SSH, WebFig, DNS) no defconf input chain drops what isn’t from LAN
LAN → server (admin SSH) yes nothing blocks LAN → DMZ in the forward chain

The one requirement is that the server must be plugged directly into a router port. If it goes through an unmanaged switch that also carries LAN devices, those devices share a layer-2 segment and can talk to the server without crossing the router’s firewall at all. With no VLAN setup, a dedicated physical port is the simplest boundary you can get. Before changing anything, we confirmed which port the server was on by looking up its MAC in the bridge host table (/interface bridge host print where mac-address=…). It was ether4. The NAS was on ether3.

Step 1: carve the port out of the bridge

/interface list add name=DMZ comment="isolated server"
/interface bridge port remove [find interface=ether4]
/interface list member add list=DMZ interface=ether4
/ip address add address=10.0.20.1/24 interface=ether4 comment=DMZ
  • The interface list gives the firewall one name to match on (in-interface-list=DMZ). If you later add a second isolated port, it becomes one member add instead of another set of rules. The Interface Lists documentation covers how membership, include and exclude work.
  • Removing the port from the bridge turns ether4 into a routed interface. From then on, any packet from the server to another subnet goes through the router’s forward chain, which is where the policy lives.
  • The new address on ether4 is the server’s gateway. We didn’t add a DHCP server on that port and gave the server a static address instead. With one host on the segment there’s nothing to hand out, and no DHCP lease to accidentally hand the server a LAN gateway.
  • ether4 is not in the LAN list. That matters for the input chain, as we’ll see below.

Step 2: two forward rules, in this order

/ip firewall filter add chain=forward action=accept in-interface-list=DMZ \
    dst-address=10.0.0.117 comment="DMZ->NAS allow"
/ip firewall filter add chain=forward action=drop in-interface-list=DMZ \
    dst-address=10.0.0.0/24 comment="DMZ->LAN block"

RouterOS evaluates a chain from top to bottom and stops at the first rule that matches, according to its Filter documentation. The accept rule for the NAS therefore has to come before the broader drop, or the drop wins. add without place-before appends to the end of the chain, so issuing them in this order is enough.

Appending after the default rules is safe here, but you should know what’s above them. The default forward chain on our router was:

accept   ipsec-policy=in,ipsec
accept   ipsec-policy=out,ipsec
fasttrack-connection   connection-state=established,related
accept   connection-state=established,related,untracked
drop     connection-state=invalid
drop     in-interface-list=WAN connection-nat-state=!dstnat

Only new connections reach our two rules, and that’s what we want. A server-initiated connection to the NAS is accepted once, and its return packets are then handled by the established/related accept near the top. A server-initiated connection to a laptop is new, matches the drop, and goes nowhere. A connection from the LAN to the server matches neither of our rules (its in-interface is the bridge, not DMZ), so admin SSH from the LAN keeps working. If you want to block that too, you need a separate rule.

Traffic from the server to the internet also matches neither rule, because its destination isn’t in the LAN range. It falls through to the end of the chain, and RouterOS accepts packets that match no rule. The default srcnat masquerade on out-interface-list=WAN handles the address translation.

On our router, the drop rule actually used a wider /16 destination than the LAN’s /24. During the router migration, the bridge still carried the factory 192.168.88.1/24 next to the real LAN address, and the wider prefix covered both. With a single LAN subnet, the /24 shown above does the job. Match whatever ranges your LAN really spans.

Step 3: the masquerade toward the NAS

/ip firewall nat add chain=srcnat action=masquerade \
    src-address=10.0.20.0/24 dst-address=10.0.0.117 comment="DMZ->NAS as LAN source"

Routing alone would deliver the server’s packets to the NAS, and the NAS would send replies back through its default gateway, the router. The masquerade is there because of the NAS’s own firewall, which only allows SSH and management from the LAN subnet. A connection from 10.0.20.20 would be refused there, even though the router allowed it.

Masquerade rewrites the source to the address of the interface the packet leaves through, according to MikroTik’s NAT documentation. For packets leaving toward the NAS on the bridge, that’s 10.0.0.1. The NAS sees a LAN source and accepts it, so we didn’t have to widen its rules for a host we’re deliberately distrusting.

The cost is visibility. In the NAS’s logs, every DMZ connection appears to come from the router, so you can’t tell the server apart from the router itself. If that matters to you, add the DMZ subnet to the NAS’s allow list instead and skip the NAT rule.

Why the server can’t reach the router either

Our test (below) showed SSH to the router blocked from the DMZ. Neither of our forward rules does that, because traffic addressed to the router goes through the input chain. Two existing settings cover it:

  • The default input chain ends with drop all not coming from LAN (in-interface-list=!LAN). Because ether4 isn’t in LAN, new connections from the server to any router service are dropped.
  • Our router setup had already limited SSH, WebFig and WinBox to LAN prefixes with /ip service set … address=.

A side effect of the first point: the server can’t use the router as its DNS resolver, because UDP/TCP 53 from the DMZ is dropped on input. So the server points at public resolvers (1.1.1.1 and 9.9.9.9). The default input chain does accept ICMP, so the server can still ping its gateway. Our rollback check in the next step depends on that.

Switching the server’s address without locking yourself out

The server was copying data from the NAS over SSH from its old LAN address, and our admin session used that address too. If we changed the IP over SSH and the new address didn’t work, the server would be stranded. So we scheduled both the change and an automatic rollback with systemd-run, which creates transient timer units:

# switch-dmz.sh (runs as root): back up, write static config, restart networking
cp -a /etc/network/interfaces /etc/network/interfaces.pre-dmz
cp -a /etc/resolv.conf /etc/resolv.conf.pre-dmz
cat > /etc/network/interfaces <<'X'
source /etc/network/interfaces.d/*
auto lo
iface lo inet loopback
auto eno3
iface eno3 inet static
    address 10.0.20.20/24
    gateway 10.0.20.1
X
printf "nameserver 1.1.1.1\nnameserver 9.9.9.9\n" > /etc/resolv.conf
ip addr flush dev eno3
systemctl restart networking
# revert-dmz.sh: if the new gateway doesn't answer, put the old config back
if ! ping -c3 -W2 10.0.20.1 >/dev/null 2>&1; then
  cp -a /etc/network/interfaces.pre-dmz /etc/network/interfaces
  cp -a /etc/resolv.conf.pre-dmz /etc/resolv.conf
  ip addr flush dev eno3; systemctl restart networking
  echo "REVERTED $(date)" >> /root/dmz-switch.log
else
  echo "OK $(date)" >> /root/dmz-switch.log
fi
sudo systemd-run --on-active=60  --unit=dmz-switch /root/switch-dmz.sh
sudo systemd-run --on-active=360 --unit=dmz-revert /root/revert-dmz.sh

That gave us 60 seconds to apply the router commands from steps 1–3, and the server changed address on its own afterwards. If anything had been wrong, the revert timer would have restored the old config five minutes after the switch. We stopped the copy job first. rsync resumes by skipping files that already exist, so the interruption cost minutes, not data.

Verifying from the right side

The router reported the new address and rules immediately. Then we waited for SSH to answer on 10.0.20.20 and ran every test from the server, because that’s the side the policy restricts:

ip -4 -br a show eno3            # expect 10.0.20.20/24
ip route | head -1               # expect default via 10.0.20.1
curl -s -m6 -o /dev/null -w "internet %{http_code}\n" https://deb.debian.org
ssh -o BatchMode=yes <nas-user>@10.0.0.117 "echo NAS_SSH_OK"
for h in 10.0.0.137 10.0.0.126 10.0.0.1; do
  timeout 3 bash -c "echo > /dev/tcp/$h/22" 2>/dev/null \
    && echo "$h:22 OPEN (problem)" || echo "$h:22 blocked"
done

Our results: the server had 10.0.20.20/24 with its default route via 10.0.20.1, the internet check returned 200, SSH to the NAS succeeded, and TCP/22 was blocked to two other LAN hosts and to the router. We then restarted the NAS copy from the new address, and it ran at about 110 MB/s, the same as before the move. The copy itself is described in migrating a Synology NAS to a Linux server with rsync.

Be clear about what that test covers: TCP/22 to three addresses. It shows the rules match, but it is not a full audit. The loop also prints “blocked” for a host that simply refuses the connection, so test hosts you know accept SSH from the LAN. For more confidence, scan the LAN range from the server (nmap -sn 10.0.0.0/24 should find only the NAS, plus the router itself, which answers ICMP on its input chain), and check the counters on the drop rule with /ip firewall filter print stats where comment~"DMZ".

Checklist

  • Server cabled directly to a router port, with no shared switch.
  • Port removed from the bridge, given its own subnet, and added to a DMZ interface list.
  • accept DMZ → one host placed above drop DMZ → LAN.
  • Masquerade toward that host only if its own firewall trusts only the LAN, and accept the log-attribution trade-off.
  • Server uses public DNS (or add an input rule for DNS from DMZ).
  • Address change scheduled with an automatic rollback.
  • Tests run from inside the DMZ: internet, the allowed host, several blocked hosts, the router.

The router side of this setup, including how management access was locked down, is in replacing an Omada ER7206 with a MikroTik RB5009.

Sources
  1. MikroTik RouterOS — Interface Lists
  2. MikroTik RouterOS — Filter
  3. MikroTik RouterOS — NAT
  4. MikroTik RouterOS — Building Advanced Firewall (default rule set)
  5. MikroTik RouterOS — Bridging and Switching
  6. systemd-run(1) — Debian trixie manpage

Related