User Tools

K) 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 technical cookies only. No information is shared with anybody or used in any way but provide the website in your browser.

More information