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.
- 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 onemember addinstead of another set of rules. The Interface Lists documentation covers how membership,includeandexcludework. - Removing the port from the bridge turns
ether4into 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
ether4is 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. ether4is not in theLANlist. 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). Becauseether4isn’t inLAN, 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
DMZinterface list. accept DMZ → one hostplaced abovedrop 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.