A public gateway already runs WireGuard on UDP 51820, SSH on 22, and one application that must be reachable from the internet. The tempting rule forwards every port to the peer and calls it done. On a WireGuard gateway that rule can capture the tunnel's own transport and lock you out of the machine that terminates it. Publication is a routing decision with a control-plane exception, not a single DNAT line.

Public Entry, Tunnel Entry: Pick One

Publishing a service creates a new internet entry point. It is not the same as granting VPN membership to a peer, because the visitor never joins the tunnel and the gateway becomes the public face of the backend. That distinction decides the rule's audience and its certificate.

Administrative interfaces rarely belong in that position. An admin page reached by authorized VPN clients keeps its attack surface inside the tunnel and avoids exposing a login form to arbitrary scanning. Public application endpoints still need their own TLS certificate, authentication and rate limiting; the encrypted WireGuard hop does not protect the visitor's leg to the gateway.

If the service genuinely must be public, write down the hostname, the certificate that covers it and the visitor ranges that should reach it. Those three facts decide the rule shape and the filter policy later. Anything outside them is scope creep on a public listener.

One Port In, One Backend Out

Use a single public port and one backend service. In this example eth0 carries the public address 198.51.100.20, the gateway's tunnel address is 10.200.0.1, and the peer runs the application on 10.200.0.7. Public TCP 9443 maps to the peer's TCP 8443:

iptables -t nat -A PREROUTING -i eth0 -d 198.51.100.20 -p tcp --dport 9443 -j DNAT --to-destination 10.200.0.7:8443
iptables -t nat -A POSTROUTING -o wg0 -d 10.200.0.7 -p tcp --dport 8443 -m conntrack --ctorigdst 198.51.100.20 --ctorigdstport 9443 -j SNAT --to-source 10.200.0.1

The SNAT rule matches the translated backend and the original public destination, so another connection to the same backend service stays outside this mapping. It also hides the visitor's address from the peer, which is the trade-off that makes a source-preserving routed return a separate design. The iptables extensions reference documents both targets and their matching options.

Forwarding still needs to be enabled, and the filter chain needs an allowance for the translated destination and port plus established replies. The FORWARD chain sees the translated destination after DNAT, so a rule written against the public address does not match the forwarded packet. Write the allowance against the backend tuple, not the public one.

The peer must accept the service connection from the gateway's tunnel address and send the reply back through the tunnel. Its AllowedIPs has to authorize the source it actually receives, which under source translation is the gateway's tunnel address, and a broad 0.0.0.0/0 entry on every peer is not required for this design. What drops the return traffic is a peer whose list omits that address, or a source-preserving design that forwards the visitor's own address instead, so check the source the peer really sees rather than the one in your rule.

Keep the gateway's own listeners out of the rule

Swap the port match for a rule covering every TCP and UDP destination, and the gateway's own WireGuard socket falls inside it. Destination translation happens before the machine decides a packet is local, so UDP 51820 can end up at the peer rather than at the process holding every peer connection. The rule loads successfully and the tunnel dies, which is a difficult failure to read from the peer side.

SSH, monitoring agents and any other service hosted on the gateway have the same exposure. A rule that forwards everything does not distinguish the control plane from the published application, and the first reboot after the change is when the lockout becomes obvious. The catch-all does not warn about the service it silently takes over. You discover it when the next connection fails.

If broad forwarding is genuinely needed, reserve the control ports before the catch-all or use a separate public address for publication. The second option is cleaner because the control address keeps its own policy, but the provider has to deliver that address; adding a DNS record does not allocate one.

Broad TCP and UDP forwarding is also not all protocols. ICMP, IPv6, path-MTU feedback and the outer VPN transport need their own policy, and a rule that covers TCP and UDP leaves those cases to the default chain.

Choose the model before writing rules

Four shapes cover most gateway publications, and the choice depends on how many services you run and how much control-plane risk you can tolerate. The table below orders them from the narrowest to the broadest.

ModelWhat it protectsCostUse when
Narrow port forwardEvery other listener, including the VPNOne rule per serviceFew services, stable ports
Catch-all with reserved portsPorts named in the exception listRule order becomes criticalMany services and disciplined review
Separate public addressControl address fully isolatedProvider must supply the addressHigh-value publication
VPN-only accessNo public entry at allVisitors need the tunnelAdmin interfaces and internal tools

Start with the narrow model even if you expect more services later. It is the easiest to audit, and converting a set of narrow rules into a reviewed catch-all is simpler than discovering which listener a broad rule captured. Whichever model you choose, keep the rules with the firewall owner. Applying iptables lines next to a UFW or nftables configuration that also manages the same chains produces a policy that changes depending on boot order.

Prove Both the Publication and the Control Path

Test from an authorized outside client using the approved hostname and a certificate valid for it. Confirm which backend answered, then check that unrelated public ports remain denied. A disabled certificate check is not a passing HTTPS test, and a connection that reaches the wrong backend is worse than a failed one.

After that, verify the gateway itself: connect a WireGuard peer, open an SSH session and confirm monitoring still reports. The published service can work while the control plane is broken, so both checks belong in the same change window. Reboot once to prove the rules are reapplied in the expected order, then remove the publication and confirm the removal actually matches the addition. A large set of startup additions with incomplete teardown is how a temporary test becomes a permanent opening.

Inyandiko zihuye