Inbound Services¶
Expose port-forwarded services (LuCI on the router, SSH, a self-hosted website, a docker on the LAN) to the WAN while the NymVPN kill-switch is on.
Why this exists¶
When the kill-switch is on, every outbound packet that isn't explicitly allowed is sent through the VPN tunnel. That includes the reply to any inbound connection — so a port-forwarded service on the router or behind it will:
- Accept the inbound SYN on the WAN
- Try to reply
- Send the reply through the VPN tunnel
- Hit the wrong source IP at the egress, and the client connection dies
Inbound exemptions short-circuit that. For ports you declare here, replies are routed via the real WAN instead of the tunnel.
How it works¶
For each {proto, port} you declare:
- A
manglerule marks the inbound connection'sconntrackmark on the WAN interface, before DNAT runs, so the original public port is what matches. - The mark is restored onto the reply packet's mark via
meta mark set ct mark. - A routing rule at priority 90 —
ip rule fwmark 0x14e lookup main— sends marked replies via the main routing table (real WAN) instead of the tunnel routing table. - The kill-switch's filter chains get a
meta mark 0x14e acceptso the reject-all rule doesn't fire on the marked reply.
The exemption only adds a reply path. It does not weaken outbound enforcement: the daemon's own outbound traffic is unaffected, and a compromised exempt service cannot exfiltrate through the exempt port because the mark is only set on iif=wan ct state new — not on connections the router itself initiates.
When you need a port forward¶
NymVPN's inbound exemption only handles the reply routing. It does not set up the DNAT that delivers an inbound connection to a host on your LAN. Use OpenWrt's native firewall UI for that.
| Service location | Port forward needed? | Where to set it up |
|---|---|---|
| On the router itself (LuCI, SSH, WireGuard listener on the router) | No | — |
| On a LAN host (Jellyfin on a NAS, docker on a server, etc.) | Yes | Network → Firewall → Port Forwards in LuCI |
For LAN-hosted services, set up the port forward first. Then add the exemption with the WAN-side port. The exemption matches by (proto, WAN-port) before DNAT rewrites the destination — so the LAN target IP doesn't need to be declared on the NymVPN side.
CLI¶
# List configured exemptions
nym-vpnc inbound list
# Add an exemption with optional label
nym-vpnc inbound add tcp:443 --label "HTTPS reverse proxy"
nym-vpnc inbound add tcp:22222
nym-vpnc inbound add udp:51820 --label "WireGuard"
# Remove an exemption
nym-vpnc inbound del tcp:443
# Tunnel summary now shows the exemption list too
nym-vpnc tunnel get
# … Inbound exemptions: tcp/443, tcp/22222, udp/51820 …
Add and delete persist to /etc/nym/nym-vpnd.json and survive daemon restart. Changes apply on the next state transition; while the VPN is connected, the daemon debounces tunnel-settings updates by ~1s and then triggers a reconnect to re-apply the firewall and routing rules.
LuCI¶
The Inbound Services card sits between Tunnel Settings and DNS & Ad Blocking.
- Pick
TCPorUDP, type the port, optionally a label, press Save (orEnter). - Rows show proto / port / label / status (
● Activewhen kill-switch is on,● Inertwhen off) with a×to delete. - If the kill-switch is off, a warning banner explains that exemptions are inert — exemptions only matter when the tunnel default route is enforcing.
Recipes¶
Expose LuCI to the WAN¶
The router's web UI listens on :443.
# 1. Allow WAN input to TCP/443 in OpenWrt firewall
uci set firewall.luci_wan=rule
uci set firewall.luci_wan.name='Allow-LuCI-WAN'
uci set firewall.luci_wan.src='wan'
uci set firewall.luci_wan.proto='tcp'
uci set firewall.luci_wan.dest_port='443'
uci set firewall.luci_wan.target='ACCEPT'
uci commit firewall
fw4 reload
# 2. Allow LuCI to accept RFC1918-source requests (only needed if the
# test client is on a private network)
uci set uhttpd.main.rfc1918_filter='0'
uci commit uhttpd
/etc/init.d/uhttpd restart
# 3. Add the NymVPN exemption
nym-vpnc inbound add tcp:443 --label "LuCI"
Expose a LAN service (e.g. Jellyfin at 192.168.1.50:8096)¶
# 1. Create the port forward in OpenWrt — public:8096 → 192.168.1.50:8096
uci add firewall redirect
uci set firewall.@redirect[-1].name='Jellyfin'
uci set firewall.@redirect[-1].src='wan'
uci set firewall.@redirect[-1].src_dport='8096'
uci set firewall.@redirect[-1].dest='lan'
uci set firewall.@redirect[-1].dest_ip='192.168.1.50'
uci set firewall.@redirect[-1].dest_port='8096'
uci set firewall.@redirect[-1].proto='tcp'
uci set firewall.@redirect[-1].target='DNAT'
uci commit firewall
fw4 reload
# 2. Add the exemption for the WAN-side port (same number here)
nym-vpnc inbound add tcp:8096 --label "Jellyfin"
The exemption uses the public port (src_dport), not the internal one — even if the port forward remaps them.
Remap example (WAN:8443 → LAN:443)¶
uci add firewall redirect
uci set firewall.@redirect[-1].name='HomeAssistant'
uci set firewall.@redirect[-1].src='wan'
uci set firewall.@redirect[-1].src_dport='8443'
uci set firewall.@redirect[-1].dest='lan'
uci set firewall.@redirect[-1].dest_ip='192.168.1.20'
uci set firewall.@redirect[-1].dest_port='443'
uci set firewall.@redirect[-1].proto='tcp'
uci set firewall.@redirect[-1].target='DNAT'
uci commit firewall
fw4 reload
# Use the WAN port (8443), not the LAN port (443)
nym-vpnc inbound add tcp:8443 --label "Home Assistant"
Verification¶
# Mark-set rule in mangle PREROUTING (only present in Connecting/Connected)
nft list table inet nym | grep -A4 'chain mangle_prerouting'
# Mark restore in mangle output
nft list table inet nym | grep -A2 'chain mangle_output'
# Filter accepts for the exempt mark
nft list table inet nym | grep 'meta mark 0x'
# The fwmark routing rule at priority 90
ip rule | grep 0x14e
# 90: from all fwmark 0x14e lookup main
On fw3/iptables routers the equivalent is:
iptables -t mangle -L NYM_MANGLE_PREROUTING -v -n
iptables -t mangle -L NYM_MANGLE_OUTPUT -v -n
iptables -L NYM_OUTPUT -v -n | grep '0x14e'
Caveats¶
- Single WAN only in v1. If
mwan3reports more than one enabled WAN, the daemon logs a warning and the exemption may apply to the wrong egress. - Kernel module
kmod-ipt-conntrack-extra. The IPK declares this as a hard dependency onfw3builds; if you've manually stripped it, exemption rule application will fail atiptables-restoretime with a clear error. - Hot-apply takes ~1s. When you
addordelwhile connected, the daemon debounces the tunnel-settings update by ~1s and then reconnects to re-apply the firewall/routing — connections may briefly drop. Existing in-flight flows keep their original routing (no retroactive marking). - Exemption is not a port forward. If you
add tcp:8096without an OpenWrt port forward for8096, nothing forwards traffic to your LAN host — the exemption only fixes the reply path.
See also¶
- Split Tunneling with PBR — for outbound exclusions (route specific clients/destinations around the VPN). Inbound exemption and PBR can coexist; both use the daemon's published fwmark/table layout.
- LuCI Web Interface — Inbound Services card reference.
- CLI Usage — full
nym-vpncreference.