Hosting Behind CGNAT With Wireguard

Guide for setting up a server with a public IP to port forward to a host behind a pfsense.

The use case for this is you want to host a service and either you do not want to expose your public IP or your pfsense firewall is behind a private IP such as CG-NAT. For this demo I am using a Debian 12 system with a public IP but it should work with other Linux distributions mostly the same way but you will have to adjust the command accordingly.

Debian 12 Host With Public IP Setup

First we need to enable IP Forwarding. IP forwarding is the ability for an operating system to accept incoming network packets on one interface, recognize that it is not meant for the system itself, but that it should be passed on to another network. Edit the file /etc/sysctl.conf and change and uncomment to the line that says net.ipv4.ip_forward=1

Now reboot or run sysctl -p to activate the changes.

Install wireguard and iptables
apt install iptables wireguard-tools -y

Go to to the Wireguard config cd /etc/wireguard and then run the following command to generate the public and private keys for the server.
umask 077; wg genkey | tee privatekey | wg pubkey > publickey

The run cat privatekey and copy it so we can put it in to the server config file.

Create the /etc/wireguard/wg0.conf
vim /etc/wireguard/wg0.conf

Server wg0.conf file as used in the video

[Interface]
PrivateKey = <Server Private Key>
Address = 10.69.69.1/24
ListenPort = 51820

PostUp = iptables -t nat -A PREROUTING -p tcp --dport 19999 -j DNAT --to-destination 10.69.69.2:19999
PostUp = iptables -t nat -A POSTROUTING -p tcp -d 10.69.69.2 --dport 19999 -j SNAT --to-source 10.69.69.1
PostUp = iptables -A FORWARD -p tcp -d 10.69.69.2 --dport 19999 -j ACCEPT

PostDown = iptables -t nat -D PREROUTING -p tcp --dport 19999 -j DNAT --to-destination 10.69.69.2:19999
PostDown = iptables -t nat -D POSTROUTING -p tcp -d 10.69.69.2 --dport 19999 -j SNAT --to-source 10.69.69.1
PostDown = iptables -D FORWARD -p tcp -d 10.69.69.2 --dport 19999 -j ACCEPT

[Peer]
# pfsense
PublicKey= < pfsense public key>
  AllowedIPs=10.69.69.2/32
  PersistentKeepalive=25


Explanation of WireGuard iptables Rules

1. PREROUTING Rule

PostUp = iptables -t nat -A PREROUTING -p tcp --dport 19999 -j DNAT --to-destination 10.69.69.2:19999
  • Purpose: This rule ensures that incoming traffic on TCP port 19999 (destined for the public IP of the Debian Host) is forwarded to the WireGuard peer (pfSense) with the tunnel IP 10.69.69.2.
  • Mechanism:
    • The PREROUTING chain in the nat table is used to modify the destination address of packets as soon as they arrive.
    • In this case, any traffic arriving on the Debian Host on port 19999 will have its destination rewritten to 10.69.69.2:19999.

2. POSTROUTING Rule

PostUp = iptables -t nat -A POSTROUTING -p tcp -d 10.69.69.2 --dport 19999 -j SNAT --to-source 10.69.69.1
  • Purpose: Ensures that the source address of packets forwarded to 10.69.69.2:19999 is rewritten to the Debian Host WireGuard tunnel IP (10.69.69.1).
  • Why This is Needed:
    • Without this rule, the forwarded traffic would retain the original source IP of the client (e.g., a public IP of a remote client).
    • The pfSense behind the WireGuard tunnel would send return traffic directly to the client, bypassing the Debian Host, breaking the connection.
    • By rewriting the source IP to 10.69.69.1, the response traffic is sent back to the Debian Host, which can then forward it to the original client.

3. FORWARD Rule

PostUp = iptables -A FORWARD -p tcp -d 10.69.69.2 --dport 19999 -j ACCEPT
  • Purpose: Explicitly allows the Debian Host to forward traffic destined for 10.69.69.2:19999 through the kernel.
  • Why This is Needed:
    • By default, Linux systems might block traffic being forwarded between interfaces unless explicitly allowed.
    • This rule ensures that traffic arriving at the Debian Host for port 19999 is permitted to be forwarded to the WireGuard tunnel.

Summary of the Flow

  1. PREROUTING: Rewrites the destination of traffic destined for the Debian Host public IP on port 19999 to the WireGuard peer 10.69.69.2.
  2. POSTROUTING: Rewrites the source IP of forwarded packets to 10.69.69.1 to ensure return traffic is routed back through the Debian Host.
  3. FORWARD: Permits the Debian Host to forward the traffic between the public interface and the WireGuard tunnel interface.

The PostDown removes the rules when the wireguard tunnel is stopped.

To test that the server works run wg-quick up wg0 to bring up the interface. Running wg-quick down will bring the interface down.

If you want the wg0 interface to be active on boot you need to run
systemctl enable wg-quick@wg0

Then you can use to systemctl start wg-quick@wg0 start the server, systemctl stop wg-quick@wg0 stop the server and systemctl status wg-quick@wg0 to check the status.

The command wg show will give you the wireguard status

pfsense setup

As shown in the video:

  1. Create a new Wireguard Tunnel in pfsense
  2. Generate new keys and copy the pfsense public key into /etc/wireguard/wg0.conf on the Debian 12 Host With Public IP
  3. Create a peer for that tunnel, set the Endpoint to the Debian Server Public IP, add the public key from the Debian 12 Host from the /etc/wireguard/publickey , and be sure to use a Keep Alive time if the pfsense is behind CG-NAT as the host can not initiate the connection. Set allowed IP to 10.69.69.1/32
  4. Create interface in pfsense set IPv4 Configuration Type to static IP, set MTU and MSS to 1420, setup static ip to 10.69.69.2
  5. Under routing make sure Default gateway is not set to automatic and set to WAN (or a group if you have that configured)
  6. Create gateway in pfsense (this allows for the data to be routing back) choose the WG interface use 10.69.69.2 as the Gateway IP and either disable monitoring or set the monitor ip to 10.69.69.1
  7. Create port forward in pfsense to your server behind the pfsense (this will also create the proper firewall rules under the interface )

For this setup extra static routes are not needed as they are in my pfsense wiregaurd site to site tutorial here:

3 Likes

cool. i have done this using pfsense on both ends. That makes it a lot more easier, at least for me.

Top article! Congrats @LTS_Tom!
I have one question tho…

Since we lose the ability to log incoming traffic (mainly real source IPs) at the private server, do you know any good articles on how to log all traffic being rerouted by iptables?
Or maybe a followup video just for iptables logging?

My scenario:
I have NGINX running on the private server but, now, the logs show only the wireguard tunnel IP on public server. I would love to have the iptables traffic logs at the public server so I can match them programatically with the NGINX logs.

Thanks in advance, Tom.

Hugo

There is probably some way to forward the IP’s over but that is beyond my knowledge on how to do that with iptables. If you move the reverse proxy to the public IP you would know the public IP and then you could forward the connections back to your server.

1 Like

Not something I have tested but DB Tech did a video on Pangolin:

1 Like

My small contribution for those, like me, that need to match the logs on the reverse proxy with the iptables logs.
Please notice the first line on each of the 2 blocks “-j LOG”.
Also you need --log-prefix to help you identify the lines in the log.
This logs all forwards to journald and you can search for it live like this:

sudo journalctl -f -g “iptables-forward”

PostUp = iptables -A FORWARD -j LOG --log-prefix "iptables-forward: "
PostUp = iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 10.69.69.2:80
PostUp = iptables -t nat -A PREROUTING -p tcp --dport 443 -j DNAT --to-destination 10.69.69.2:443
PostUp = iptables -t nat -A POSTROUTING -p tcp -d 10.69.69.2 --dport 80 -j SNAT --to-source 10.69.69.1
PostUp = iptables -t nat -A POSTROUTING -p tcp -d 10.69.69.2 --dport 443 -j SNAT --to-source 10.69.69.1
PostUp = iptables -A FORWARD -p tcp -d 10.69.69.2 --dport 80 -j ACCEPT
PostUp = iptables -A FORWARD -p tcp -d 10.69.69.2 --dport 443 -j ACCEPT

PostDown = iptables -D FORWARD -j LOG --log-prefix "iptables-forward: "
PostDown = iptables -t nat -D PREROUTING -p tcp --dport 80 -j DNAT --to-destination 10.69.69.2:80
PostDown = iptables -t nat -D PREROUTING -p tcp --dport 443 -j DNAT --to-destination 10.69.69.2:443
PostDown = iptables -t nat -D POSTROUTING -p tcp -d 10.69.69.2 --dport 80 -j SNAT --to-source 10.69.69.1
PostDown = iptables -t nat -D POSTROUTING -p tcp -d 10.69.69.2 --dport 443 -j SNAT --to-source 10.69.69.1
PostDown = iptables -D FORWARD -p tcp -d 10.69.69.2 --dport 80 -j ACCEPT
PostDown = iptables -D FORWARD -p tcp -d 10.69.69.2 --dport 443 -j ACCEPT

Hope it helps some people out there!

Regards,
Hugo

1 Like

This is also a very nice solution although keeping it to iptables and wireguard only makes everything much faster. :+1: :+1: :+1: