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 } }