User Tools

WireGuard Port Forwarding

I am assuming that you have already setup WireGuard both on the internal and on the external server. See this page for more details on the topic.

Using WireGuard for port-forwarding between an external, public accessible host, and an internal non-accessible host (like behind a CGNAT) is a less known topic and i will cover it here.

You will need to use NFTables, see here for more details to better understand the following.

Assumptions:

  • You have one internal host which is behind CGNAT, and is not accessible from the internet
  • You have one external host which has a public IP address
  • Both hosts have WireGuard setup according to the configuration above
  • Internal host expose port 22 for SSH (optional: port 8443 for HTTPS and port 80 for HTTP)
  • External host needs to route those three ports to the internal host
  • The WireGuard subnet is 10.100.0.0/24 with internal host being 10.100.0.1 and external host 10.100.0.2
  • External host public network interface is called enp1s0
  • Both hosts WireGuard network iunterface is called wg0

What we need to do is create nftable rules to ensure that:

  • packets reaching external host on enp1s0 on port 2022 get routed to 10.100.0.1 port 22
  • packets reaching external host on enp1s0 on port 443 get routed to 10.100.0.1 port 8443 (optional)
  • packets reaching external host on enp1s0 on port 80 get routed to 10.100.0.1 port 80 (optional)
  • return packets from the internal host get properly re-routed to the original sender out from enp1s0

Note: forwarding ports for HTTP and HTTPS is only required if you don't plan to host a reverse proxy on the external server, which is the recomended approach. See this page for more details on chaining proxies.

Note: see here on why i use port 8443 instead of port 443 on the internal server. This is to differentiate internal connections, which have lesser security, from external connections which get additional SSO layers.

Note: if you forward HTTPS, you must also forward HTTP (80), otherwise Let's Encrypt will be unable to renew your certificates.

What we need:

  • A dedicated table called wg
  • A prerouting chain to apply DNAT to incoming packes
  • Rules to route port 2022/80/443 to the wg tunnel ports
  • A postrouting chain to ensure that all reply packets are properly SNAT back to outside
  • Return masquerading rules to ensure the return packets get sent back out of enp1s0

Create the wg table:

nft add table ip wg

Create the base chains:

nft 'add chain ip wg prerouting { type nat hook prerouting priority -100 ; }'
nft 'add chain ip wg postrouting { type nat hook postrouting priority 100 ; }'

Create the in-bound rules:

# nft 'add rule ip wg prerouting iifname enp1s0 tcp dport 80 counter' # <<- optional for debugging purposes
# nft 'add rule ip wg prerouting iifname enp1s0 tcp dport 443 counter' # <<- optional for debugging purposes
# nft 'add rule ip wg prerouting iifname enp1s0 tcp dport 2022 counter' # <<- optional for debugging purposes
nft 'add rule ip wg prerouting iifname enp1s0 dnat to tcp dport map { 2022 : 10.100.0.1 . 22 }'
#nft 'add rule ip wg prerouting iifname enp1s0 dnat to tcp dport map { 80 : 10.100.0.1 . 80 }' optional
#nft 'add rule ip wg prerouting iifname enp1s0 dnat to tcp dport map { 443 : 10.100.0.1 . 8443 }' optional

Create the SNAT return rule (the counter rule is only for debugging, you can omit that rule):

# nft 'add rule ip wg postrouting ip daddr 10.100.0.1 counter' # <<- optional for debugging purposes
nft 'add rule ip wg postrouting ip daddr 10.100.0.1 masquerade'

This is the resulting NFTables setup:

nft list table wg
table ip wg {
        chain prerouting {
                type nat hook prerouting priority dstnat; policy accept;
                iifname "enp1s0" tcp dport 443 counter packets 100 bytes 5712
                iifname "enp1s0" tcp dport 2022 counter packets 0 bytes 0
                iifname "enp1s0" tcp dport 80 counter packets 0 bytes 0
                iifname "enp1s0" dnat ip to tcp dport map { 2022 : 10.100.0.1 . 22 }
                iifname "enp1s0" dnat ip to tcp dport map { 80 : 10.100.0.1 . 80 }
                iifname "enp1s0" dnat ip to tcp dport map { 443 : 10.100.0.1 . 8443 }
        }
 
        chain postrouting {
                type nat hook postrouting priority srcnat; policy accept;
                ip daddr 10.100.0.1 counter packets 390 bytes 25945
                ip daddr 10.100.0.1 masquerade
        }
}

This website uses cookies. By using the website, you agree with storing cookies on your computer. Also, you acknowledge that you have read and understand our Privacy Policy. If you do not agree, please leave the website.

More information