Back to blog

MikroTik / RouterOS  ·  Tutorial  ·  May 2026

Port Forwarding
on MikroTik

DST-NAT from first principles — single ports, port ranges, hairpin NAT, dynamic IP with DDNS, and what to do when Starlink CGNAT makes forwarding impossible.

RouterOS NAT DST-NAT Firewall DDNS Starlink / CGNAT SMB / Hospitality MTCNA+

IP > Firewall - WinBoxFilter RulesNATMangleRawConnectionsAddRemoveEnableDisableReset CountersComment#ActionChainProtoDst.PortIn.IfTo AddressesComment0masqueradesrcnatether1Masquerade WAN1dst-natdstnattcp8080ether1192.168.10.50:80Forward 8080→webserver2dst-natdstnattcp3389ether1192.168.10.100:3389RDP forward3dst-natdstnattcp37777-37780ether1192.168.20.10Camera NVR ports4 items

Port forwarding on MikroTik is two rules working together: a dst-nat rule in the NAT chain that rewrites the destination, and an accept rule in the forward chain that lets the rewritten packet through. Most guides only show you the NAT rule and leave you wondering why nothing works. This tutorial explains both, then layers on the three scenarios you'll actually encounter in the field: a static WAN IP, a dynamic IP with DDNS, and a Starlink CGNAT connection where traditional forwarding is flat-out impossible.

By the end you'll have a clean, auditable rule set — not a pile of copy-pasted commands — and a clear mental model for diagnosing forwarding failures when they inevitably happen.

Prerequisites

01

How DST-NAT Works

Port forwarding is address rewriting — understand the two-rule model before touching the CLI.

When a packet arrives on your WAN interface destined for your public IP on port 8080, RouterOS processes it through the NAT chain first, then the forward chain. These are separate steps and both must pass for the packet to reach its destination.

The NAT rule (action=dst-nat) rewrites the packet's destination address and port — changing 203.0.113.1:8080 to 192.168.10.50:80, for example. The packet now looks like it was always headed for your internal server. But without a matching forward chain accept rule, the default drop policy kills it before it crosses the router. Both rules are required.

Internet 203.0.113.1 :8080 NAT Chain action=dst-nat rewrites dst IP+port → 192.168.10.50:80 STEP 1 Forward Chain action=accept allows rewritten pkt without this → DROP STEP 2 LAN Server 192.168.10.50 port 80 responds DROP — no fwd rule ✗ The Two-Rule Model DST-NAT rewrites the destination. The forward chain accept rule lets it through. Both are required.

A third piece matters in practice: masquerade / src-nat on the return path. When the server responds, it sends the reply back to RouterOS, which re-applies NAT in reverse to return the original source. This happens automatically as part of connection tracking — you don't configure it separately, but it's why connection-state=established,related accept rules in your forward chain are essential.

⚠ Gotcha — NAT Rule Alone Is Not Enough

The single most common mistake: adding the dst-nat rule, testing, and getting no response. The packet is being rewritten correctly — it's the forward chain dropping it. Always add both rules. If you're running the RouterOS default firewall script (generated on first boot), you need an explicit accept rule in the forward chain or your forwarded traffic will hit the default drop.

02

Basic DST-NAT — Static WAN IP

The cleanest case: you have a fixed public IP and want to expose one service. Get this right first — everything else builds on it.

Scenario: your WAN IP is 203.0.113.1 on interface ether1. You want external traffic on TCP port 8080 to reach an internal web server at 192.168.10.50 on port 80. Two rules, in order.

RouterOS — IP › Firewall › Filter Rules — forward chain (Winbox) WinBox — IP > Firewall > Filter Rules (forward chain) × IP > Firewall > Filter Rules + Add # CHAIN ACTION PROTO IN-IFACE DST-ADDR DST-PORT CONN-STATE COMMENT 0 forward fasttrack estab,related fasttrack 1 forward drop tcp ether1 192.168.10.50 80 new rate-limit new conns 2 forward log tcp ether1 192.168.10.50 80 new FWD-HIT log 3 forward accept tcp ether1 192.168.10.50 80 new Allow fwd→webserver 4 rules — order: rate-limit DROP before ACCEPT, LOG before ACCEPT Forward chain rules in order: fasttrack established → rate-limit drop → log → accept. The log and rate-limit rules must appear before the accept rule for the same port.

Rule 1 — DST-NAT (Rewrite)

RouterOS — DST-NAT rule
[admin@MikroTik] > /ip firewall nat add \
    chain=dstnat \
    protocol=tcp \
    dst-address=203.0.113.1 \
    dst-port=8080 \
    in-interface=ether1 \
    action=dst-nat \
    to-addresses=192.168.10.50 \
    to-ports=80 \
    comment="Forward 8080 → webserver:80"
# dst-address= is optional but good practice — locks the rule to your WAN IP
# specifically, not any IP on ether1.
# in-interface= further narrows it to inbound WAN traffic only.

Rule 2 — Forward Chain Accept

RouterOS — forward chain accept rule
[admin@MikroTik] > /ip firewall filter add \
    chain=forward \
    protocol=tcp \
    dst-address=192.168.10.50 \
    dst-port=80 \
    in-interface=ether1 \
    connection-state=new \
    action=accept \
    comment="Allow forwarded traffic to webserver:80"
# connection-state=new: only match new connections.
# Established/related replies are handled by a broader accept rule
# that should already exist in your default forward chain.
# Place this rule ABOVE any generic drop rules in the forward chain.

Verify

RouterOS — verify NAT and connections
[admin@MikroTik] > /ip firewall nat print
Flags: X - disabled, I - invalid, D - dynamic
 #    CHAIN    ACTION   PROTO  DST-ADDRESS      DST-PORT  TO-ADDRESSES     TO-PORTS
 0    dstnat   dst-nat  tcp    203.0.113.1      8080      192.168.10.50    80

[admin@MikroTik] > /ip firewall connection print where dst-address~"192.168.10.50"
# After a test connection from outside, this should show an active entry.
# No entry = packet never reached the NAT rule (check in-interface name).
# Entry present but no response = forward chain drop (check filter rules).
⚠ Gotcha — Rule Position in the Chain

RouterOS processes firewall rules top-to-bottom and stops at the first match. If you have a broad drop or reject rule before your new accept rule in the forward chain, your traffic will be dropped before it ever reaches the accept. Always use /ip firewall filter print to verify rule order. Use place-before= when adding rules to insert them at the correct position rather than appending to the end.

03

Port Ranges and Hairpin NAT

Forwarding a range of ports is one parameter change. Hairpin NAT is the fix for when your own LAN clients can't reach the server by its public IP.

Port Ranges

To forward a range of ports — say, a game server using UDP 27015–27020, or an IP camera system using TCP 37777–37780 — use the dst-port range syntax. RouterOS preserves the destination port number within the range unless you explicitly override it with to-ports.

RouterOS — port range DST-NAT
[admin@MikroTik] > /ip firewall nat add \
    chain=dstnat \
    protocol=tcp \
    dst-port=37777-37780 \
    in-interface=ether1 \
    action=dst-nat \
    to-addresses=192.168.20.10 \
    comment="Forward camera NVR ports 37777-37780"
# No to-ports= here — port is preserved within the range.
# Port 37777 on WAN → 37777 on 192.168.20.10, etc.
# For UDP camera streams, change protocol=udp.
⚠ Gotcha — Multiple Protocols Need Separate Rules

If a service uses both TCP and UDP on the same port (e.g. some VoIP and game servers), you need two separate DST-NAT rules — one for protocol=tcp and one for protocol=udp. RouterOS does not support protocol=tcp,udp in a single rule. Forgetting the UDP rule is a classic half-open forwarding failure — TCP connects, UDP drops, service behaves strangely.

Hairpin NAT (NAT Loopback)

Hairpin NAT solves a specific problem: a LAN client tries to reach your server using your public IP address (e.g. 203.0.113.1:8080) instead of its private address. Without hairpin NAT, the packet reaches RouterOS from the inside, gets DST-NAT'd to 192.168.10.50, but the server's reply goes directly back to the LAN client without passing through the router — the client never sees a valid response because the source doesn't match.

The fix is a masquerade rule in the srcnat chain that applies only to this hairpin traffic, so the router mediates both sides of the connection.

RouterOS — Hairpin NAT srcnat rule (Winbox) WinBox — IP > Firewall > NAT × IP > Firewall > NAT Filter NAT Mangle + Add # CHAIN ACTION PROTO DST-ADDR DST-PORT IN-IFACE TO-ADDRESSES TO-PORTS COMMENT 0 srcnat masquerade ether1 masquerade WAN 1 dstnat dst-nat tcp 203.0.113.1 8080 ether1 192.168.10.50 80 Fwd 8080→web:80 2 srcnat masquerade tcp 80 bridge-lan 192.168.10.50 Hairpin NAT 3 rules (NAT) — dstnat rules processed before forward chain The hairpin srcnat rule (row 2) in the NAT table. It matches LAN-originating traffic destined for the server's LAN IP and masquerades the source so replies route back through the router.
RouterOS — hairpin NAT rule
[admin@MikroTik] > /ip firewall nat add \
    chain=srcnat \
    protocol=tcp \
    src-address=192.168.10.0/24 \
    dst-address=192.168.10.50 \
    dst-port=80 \
    out-interface=bridge-lan \
    action=masquerade \
    comment="Hairpin NAT — LAN clients reaching server via public IP"
# src-address= = your LAN subnet (where the client lives)
# dst-address= = the server's LAN IP (after DST-NAT has rewritten it)
# out-interface= = your LAN bridge or VLAN interface
# This rule only fires AFTER the DST-NAT rule has already rewritten dst-address.
# Repeat for each forwarded port if LAN clients need hairpin access to all of them.
✕ What Didn't Work — Using dst-address= with the Public IP in the Hairpin Rule

A common mistake is setting dst-address=203.0.113.1 (the public IP) in the hairpin srcnat rule. By the time the srcnat chain sees the packet, DST-NAT has already rewritten the destination to 192.168.10.50. The srcnat chain never sees the public IP. The rule must match the post-NAT LAN address, not the original public one.

04

Dynamic IP + DDNS

Most residential and many business connections change IP periodically. IP Cloud gives RouterOS a stable hostname that follows your address — no third-party DDNS service required.

When your WAN IP is dynamic, the DST-NAT rule with a hardcoded dst-address= breaks the moment your IP changes. The solution is to drop the dst-address= match entirely and rely on in-interface= alone — any traffic hitting your WAN interface on the specified port gets forwarded. RouterOS's built-in IP Cloud service then gives you a stable *.sn.mynetname.net hostname that always resolves to your current WAN IP.

RouterOS — IP › Cloud (Winbox) WinBox — IP > Cloud × IP > Cloud DDNS Enabled: yes DDNS Update Interval: 00:01:00 Update Time: Public Address: 203.0.113.1 DNS Name: a1b2c3d4e5f6.sn.mynetname.net Status: updated IP Cloud updates your DNS record every minute automatically IP › Cloud in Winbox. Enable DDNS and the router registers its current public IP with MikroTik's cloud service, updating every minute automatically.

Enable IP Cloud

RouterOS — enable IP Cloud DDNS
[admin@MikroTik] > /ip cloud set ddns-enabled=yes

[admin@MikroTik] > /ip cloud print
          ddns-enabled: yes
      ddns-update-interval: 00:01:00
           update-time: yes
         public-address: 203.0.113.1
  public-address-ipv6: (none)
             dns-name: a1b2c3d4e5f6.sn.mynetname.net
               status: updated
# dns-name is your permanent hostname. Share this with clients/team.
# It updates every minute by default. Status "updated" = working.
# Requires the router to have internet access (obviously).

DST-NAT Rule for Dynamic IP

With a dynamic IP, remove the dst-address= match. The rule now forwards based on interface and port only — correct behaviour, since you can't predict your next WAN IP.

RouterOS — DST-NAT without fixed dst-address
[admin@MikroTik] > /ip firewall nat add \
    chain=dstnat \
    protocol=tcp \
    dst-port=8080 \
    in-interface=ether1 \
    action=dst-nat \
    to-addresses=192.168.10.50 \
    to-ports=80 \
    comment="Forward 8080 → webserver:80 (dynamic IP)"
# No dst-address= — rule fires on any IP received on ether1.
# This is intentional for dynamic IP scenarios.
# The forward chain accept rule from Section 02 remains unchanged.
⚠ Gotcha — PPPoE Interface Name vs Physical Port

On fibre or DSL connections, your WAN IP lives on a pppoe-out1 interface, not on ether1. If you set in-interface=ether1 but your WAN is PPPoE, the rule will never match because inbound traffic arrives on pppoe-out1. Verify with /ip address print and /interface print — your WAN IP should show against the interface name you use in your NAT rules.

✕ What Didn't Work — Third-Party DDNS Scripts with Frequent IP Changes

Before IP Cloud was reliable, many guides suggested running a scheduled script that calls a third-party DDNS provider (No-IP, DynDNS, etc.) via /tool fetch. This works but introduces dependencies: the provider's API can change, credentials expire, and rate limits can cause update failures. IP Cloud is built into RouterOS, requires no credentials, and updates automatically. Use it unless you have a specific reason to use a third-party provider.

05

Starlink / CGNAT — When Forwarding Breaks

Starlink puts you behind Carrier-Grade NAT. You don't own a public IP — DST-NAT is structurally impossible. Here's the architecture that works instead.

Starlink (and many 4G/LTE mobile ISPs) use CGNAT: your router receives a private IP in the 100.64.0.0/10 range, and Starlink's infrastructure handles the final NAT to the public internet. You have no control over that outer NAT — you cannot forward ports through it because you don't own the public IP.

STARLINK CGNAT Internet tries to connect CGNAT Starlink owns this public IP no port control blocked RouterOS 100.64.x.x CGNAT private IP Tailscale overlay tunnel VPS / Jump Host 203.0.113.1 real public IP LAN Server 192.168.10.50 behind CGNAT client reaches VPS public IP VPS reverse-proxies via tunnel CGNAT blocks inbound. Solution: initiate the tunnel outbound from behind CGNAT, proxy inbound via a VPS with a real public IP.

There are two viable workarounds, in order of preference:

Option A — Tailscale funnel (simplest): Tailscale Funnel exposes a Tailscale-connected node to the public internet via Tailscale's infrastructure. No VPS needed. Limited to HTTPS on port 443. Good for a hotel dashboard, a camera preview page, or any web service.

Option B — VPS reverse proxy via Tailscale tunnel: A cheap VPS (€3–5/month) has a real public IP. Your RouterOS router connects to the VPS over Tailscale. The VPS runs nginx or HAProxy to receive inbound connections on any port and proxy them through the Tailscale tunnel to your LAN server. Full port control, any protocol.

Option A — Tailscale Funnel

Tailscale node (Linux) — enable Funnel
admin@lan-server:~$ tailscale funnel 443
# Exposes port 443 on this node to the public internet via Tailscale.
# Tailscale terminates TLS. Your node receives plain HTTP on 80 locally.
# Access URL: https://{node-name}.{tailnet}.ts.net
# Requires Tailscale account with Funnel enabled (free tier supports it).

admin@lan-server:~$ tailscale funnel status
https://lan-server.tail1234.ts.net/ proxy http://127.0.0.1:80

Option B — VPS Reverse Proxy

VPS (nginx) — reverse proxy via Tailscale tunnel
# On the VPS: nginx stream block proxying TCP 8080 → LAN server via Tailscale IP
root@vps:~$ cat /etc/nginx/stream.conf
stream {
    upstream lan_server {
        server 100.x.x.lan-server:80;  # Tailscale IP of your LAN server
    }
    server {
        listen 8080;
        proxy_pass lan_server;
    }
}
# Client connects to VPS public IP:8080.
# nginx proxies through Tailscale tunnel to your LAN server.
# Your LAN server never needs a public IP.
# The RouterOS router needs a running Tailscale instance (container or node on LAN).
⚠ Gotcha — Starlink Provides a Public IP on Some Business Plans

Starlink Business (not residential) can provide a real static public IP as an add-on. If you're deploying for a hotel or villa on a Business plan, check your account — you may actually have a routable public IP and can use standard DST-NAT. Verify with /ip address print: if your WAN IP starts with 100.64100.127, you're behind CGNAT. Any other address is likely public (confirm with whatismyip.com — compare to what RouterOS shows).

✕ What Didn't Work — Port Forwarding on the Starlink App

The Starlink app has no port forwarding option — this confuses many operators who expect it to work like a consumer router. The app controls dish settings, not NAT. Some guides suggest enabling "bypass mode" (putting the Starlink dish into bridge/passthrough mode and connecting RouterOS directly). This does pass through the WAN IP — but that IP is still the CGNAT 100.64.x.x address. Bypass mode does not give you a public IP. The workarounds above remain necessary.

06

Firewall Hardening for Forwarded Ports

Every open port is an attack surface. Scope your forward rules tightly, log hits, and rate-limit connection attempts before a guest device or internet scanner abuses your exposed service.

Restrict by Source IP Where Possible

If the service being forwarded is only accessed by known IPs — a remote admin, a specific office, a VPN exit node — add src-address= to both the NAT and forward rules. This is the single highest-impact hardening step and costs nothing.

RouterOS — restrict forwarded port to known source
[admin@MikroTik] > /ip firewall nat add \
    chain=dstnat \
    protocol=tcp \
    dst-port=3389 \
    in-interface=ether1 \
    src-address=198.51.100.0/24 \
    action=dst-nat \
    to-addresses=192.168.10.100 \
    to-ports=3389 \
    comment="RDP forward — office subnet only"
# Without src-address=, your RDP port is exposed to the entire internet.
# With it, only packets from 198.51.100.0/24 trigger the forward.
# Everyone else hits the default drop silently — no response, no banner.

Rate-Limit New Connections

For services that must be publicly accessible (a hotel PMS web interface, a camera portal), add a connection rate-limit rule in the forward chain to blunt brute-force and scanner traffic.

RouterOS — rate-limit new connections to forwarded port
[admin@MikroTik] > /ip firewall filter add \
    chain=forward \
    protocol=tcp \
    dst-address=192.168.10.50 \
    dst-port=80 \
    connection-state=new \
    connection-rate=30/1m \
    action=drop \
    comment="Drop excessive new connections to webserver"
# connection-rate=30/1m: drop if more than 30 new connections per minute
# from a single source. Legitimate users won't hit this.
# Place this rule BEFORE your accept rule for the same port.
# connection-rate matches on a per-source-IP basis.

Log and Alert on Forwarded Port Hits

RouterOS — log new connections to forwarded port
[admin@MikroTik] > /ip firewall filter add \
    chain=forward \
    protocol=tcp \
    dst-address=192.168.10.50 \
    dst-port=80 \
    connection-state=new \
    in-interface=ether1 \
    action=log \
    log-prefix="FWD-HIT" \
    comment="Log new inbound connections to webserver"
# action=log does NOT stop the packet — it logs and continues to the next rule.
# The accept rule for this port must still follow.
# Filter log entries: /log print where message~"FWD-HIT"
RouterOS — IP › Firewall › Filter Rules — forward chain (Winbox) WinBox — IP > Firewall > Filter Rules (forward chain) × IP > Firewall > Filter Rules + Add # CHAIN ACTION PROTO IN-IFACE DST-ADDR DST-PORT CONN-STATE COMMENT 0 forward fasttrack estab,related fasttrack 1 forward drop tcp ether1 192.168.10.50 80 new rate-limit new conns 2 forward log tcp ether1 192.168.10.50 80 new FWD-HIT log 3 forward accept tcp ether1 192.168.10.50 80 new Allow fwd→webserver 4 rules — order: rate-limit DROP before ACCEPT, LOG before ACCEPT Forward chain rules in order: fasttrack established → rate-limit drop → log → accept. The log and rate-limit rules must appear before the accept rule for the same port.
🛡 Defender's View — Hardening & Monitoring

Never forward RDP (3389) or SSH (22) without a source IP restriction. These are the highest-value ports for automated scanners. If you must expose them, move them to a non-standard port AND restrict by source IP. Better still: put them behind Tailscale and don't forward at all.

Use the connection table as your audit trail. /ip firewall connection print shows all active and recent connections through the router. Cross-reference with your log prefix entries to identify unexpected source IPs. Anything from outside your expected geographic range warrants investigation.

For hospitality networks: camera and PMS ports are high-value targets. IP cameras in particular are routinely compromised on exposed ports. If a vendor tells you to forward camera ports publicly, push back — ask about their VPN or cloud relay option. If forwarding is unavoidable, restrict to the vendor's known IP range and set a rate limit.

Audit your open ports periodically. /ip firewall nat print where chain=dstnat gives you a full list of what's currently forwarded. Run this quarterly — in active networks, rules accumulate and forgotten forwards are a real attack surface.

Takeaways

  1. Port forwarding always requires two rules. The DST-NAT rule rewrites the destination. The forward chain accept rule lets the rewritten packet through. Forgetting the second rule is the most common reason forwarding silently fails.
  2. Rule position is not optional — it's the logic. RouterOS processes rules top-down and stops at the first match. A misplaced accept rule after a broad drop rule does nothing. Always verify rule order with /ip firewall filter print after adding new rules.
  3. For dynamic IPs, drop dst-address= from the NAT rule and use IP Cloud for DDNS. Hardcoding your WAN IP in the NAT rule will silently break when it changes. IP Cloud is built in, free, and requires no external dependencies.
  4. Hairpin NAT requires the post-NAT LAN address, not the public IP. The srcnat chain sees traffic after DST-NAT has already rewritten the destination. Matching on the public IP in a hairpin rule will never fire.
  5. Starlink CGNAT is not a router configuration problem. No amount of DST-NAT rules will work when you don't own the public IP. The architecture shift — outbound Tailscale tunnel + VPS reverse proxy — is the correct solution, not a workaround.
  6. Every forwarded port needs a hardening review. At minimum: restrict by source IP where possible, add a connection rate limit for public-facing services, and log new connections. A forwarded port with no logging is invisible in your incident response trail.
  7. Audit your NAT rules quarterly. Accumulated DST-NAT rules from past deployments are a common forgotten attack surface. /ip firewall nat print where chain=dstnat takes five seconds and should be on your maintenance checklist.

Running a hotel, villa, or SMB in Crete?

NOCTIS audits and hardens network perimeters for hospitality properties — open ports, misconfigured NAT rules, exposed camera and PMS systems, and Starlink deployments that need proper remote access without public exposure. Book a 30-min discovery call to find out what's currently reachable from the internet on your network.

Book a Discovery Call →