Skip to content

Instantly share code, notes, and snippets.

@adog1314
Last active October 4, 2025 21:24
Show Gist options
  • Save adog1314/97bf494d74f56bfff51da9bb4bff8ed0 to your computer and use it in GitHub Desktop.
Save adog1314/97bf494d74f56bfff51da9bb4bff8ed0 to your computer and use it in GitHub Desktop.
Port forward over wireguard to VPS with static IP

Port forward over wireguard to VPS with static IP

This is write up is on how to port forward over wireguard. I am going to be port forwarding a mail server running MailCow on my local server, but really any service can be port forwared with some modifications to the IPTables commands in the wireguard file.

I am using a cheap Vultr VPS as my proxy server, if your intrested heres a referral link https://www.vultr.com/?ref=9019507 where I get $10 or if you plan to spend more then $35 on your account you will get $100 and I will get $35 https://www.vultr.com/?ref=9019508-8H

My Setup

  • Debain 10 Buster
  • Tunnel subnet: 10.1.1.0
  • Proxy-VPS Tunnel IP: 10.1.1.1
  • Peer [Mail Server] Tunnel IP: 10.1.1.2

WireGuard Setup on the remote VPS

I installed WireGuard using this script: https://github.com/angristan/wireguard-install

Had an error used to fix: https://stackoverflow.com/a/66745279

Allow wireguard on port 51820(make sure the port is correct, the script assign's a random port): E.g.

sudo ufw allow 51820/udp 

Start the WireGuard service at boot time using the systemctl command, run:

sudo systemctl enable wg-quick@wg0

Port Forwarding

  • Destination NAT(DNAT) only rewrites the destination IP address, this should be used for incoming trafic headed to a peer.
  • Source NAT(SNAT) rewrites the source IP address to and should happen automatically for outbound traffic that passes through the Wg Tunnel.

Services Port Forwarded

[Peer - Mail - 10.1.1.2]

  • Http on port 80
  • Https on port 443
  • Postfix SMTP on port 25
  • Postfix SMTPS on port 465
  • Postfix Submission on port 587
  • Dovecot IMAP on port 143
  • Dovecot IMAPS on port 993
  • Dovecot POP3 on port 110
  • Dovecot POP3S on port 995
  • Dovecot ManageSieve on port 4190

Also add ufw for each port on Proxy-VPS E.g. sudo ufw allow 25/tcp

Run and find the WAN interface name and replace enp1s0 blow to the correct interface name we want the portforward to listen on

ip addr 

Place in /etc/wireguard/wg0.conf on Proxy-VPS just above [Peer]

### Http on port 80
PostUp = iptables -t nat -A PREROUTING -p tcp -i enp1s0 --dport 80 -j DNAT --to-destination 10.1.1.2:80
PostDown = iptables -t nat -D PREROUTING -p tcp -i enp1s0 --dport 80 -j DNAT --to-destination 10.1.1.2:80
### Https on port 443
PostUp = iptables -t nat -A PREROUTING -p tcp -i enp1s0 --dport 443 -j DNAT --to-destination 10.1.1.2:443
PostDown = iptables -t nat -D PREROUTING -p tcp -i enp1s0 --dport 443 -j DNAT --to-destination 10.1.1.2:443
### Postfix SMTP on port 25
PostUp = iptables -t nat -A PREROUTING -p tcp -i enp1s0 --dport 25 -j DNAT --to-destination 10.1.1.2:25
PostDown = iptables -t nat -D PREROUTING -p tcp -i enp1s0 --dport 25 -j DNAT --to-destination 10.1.1.2:25
### Postfix SMTPS on port 465
PostUp = iptables -t nat -A PREROUTING -p tcp -i enp1s0 --dport 465 -j DNAT --to-destination 10.1.1.2:465
PostDown = iptables -t nat -D PREROUTING -p tcp -i enp1s0 --dport 465 -j DNAT --to-destination 10.1.1.2:465
### Postfix Submission on port 587
PostUp = iptables -t nat -A PREROUTING -p tcp -i enp1s0 --dport 587 -j DNAT --to-destination 10.1.1.2:587
PostDown = iptables -t nat -D PREROUTING -p tcp -i enp1s0 --dport 587 -j DNAT --to-destination 10.1.1.2:587
### Dovecot IMAP on port 143
PostUp = iptables -t nat -A PREROUTING -p tcp -i enp1s0 --dport 143 -j DNAT --to-destination 10.1.1.2:143
PostDown = iptables -t nat -D PREROUTING -p tcp -i enp1s0 --dport 143 -j DNAT --to-destination 10.1.1.2:143
### Dovecot IMAPS on port 993
PostUp = iptables -t nat -A PREROUTING -p tcp -i enp1s0 --dport 993 -j DNAT --to-destination 10.1.1.2:993
PostDown = iptables -t nat -D PREROUTING -p tcp -i enp1s0 --dport 993 -j DNAT --to-destination 10.1.1.2:993
### Dovecot POP3 on port 110
PostUp = iptables -t nat -A PREROUTING -p tcp -i enp1s0 --dport 110 -j DNAT --to-destination 10.1.1.2:110
PostDown = iptables -t nat -D PREROUTING -p tcp -i enp1s0 --dport 110 -j DNAT --to-destination 10.1.1.2:110
### Dovecot POP3S on port 995
PostUp = iptables -t nat -A PREROUTING -p tcp -i enp1s0 --dport 995 -j DNAT --to-destination 10.1.1.2:995
PostDown = iptables -t nat -D PREROUTING -p tcp -i enp1s0 --dport 995 -j DNAT --to-destination 10.1.1.2:995
### Dovecot ManageSieve on port 4190 
PostUp = iptables -t nat -A PREROUTING -p tcp -i enp1s0 --dport 4190 -j DNAT --to-destination 10.1.1.2:4190
PostDown = iptables -t nat -D PREROUTING -p tcp -i enp1s0 --dport 4190 -j DNAT --to-destination 10.1.1.2:4190

WireGuard on local mail server

sudo apt install wireguard

Transfer the file created by the script from the remote VPS to /etc/wireguard/wg0.conf

On the local machine we kept encountering an issue where the tunnel would die after several minutes, once the handshake connection expired. To fix this I added PersistentKeepAlive = 25 to the bottom of the wg0.conf file

I left AllowedIPs = 0.0.0.0/0 to forward all traffic of all my mail server through the remote VPS IP address, due to it needing to send outgoing SMTP email through the NAT masquerade as the remote IP instead of my home networks IP

Start the WireGuard service at boot time using the systemctl command, run:

sudo systemctl enable wg-quick@wg0

Start WireGuard the tunnel:

sudo wg-quick up wg0
@darksnakezero
Copy link

I managed to get full dual stack working [IPv6 and IPv4] (building on your great guide).

I tried for about 100hrs to get dualstack working with wireguard but the routes and/or iptaables were problematic and I could not get them fixed even manually (maybe someone else will). I finally went for a DUal-Vendor strategy:
VPS with fixed IPv4 using wireguard and port forward with this guide. And a static IPv6 from tunnelbroker.net/Hurricane electric (free for non commercial, max 100mbit, best effort/shared bandwidth).

Somehow, wireguard would always mess up the routes/iptables on my mailcow server. I failed to track down the problem and finally went to use netplan and define all of the routes by myself.

Local Network is redacted to L.L.L.X, L.L.L.1 is the gateway/router, local DNS server is L.L.L.5, mailcow is L.L.L.13, W.W.W.W is the wireguard VPS IPv4 and H.H.H.H is the ipv4 of the HE Tunnel endpoint. The VPS wireguard is configured with the above guide.
`/etc/netplan/50-init-network.yaml

network:
version: 2
ethernets:
eth0:
match:
macaddress: "xx:xx:xx:xx:xx:xx" #redacted
addresses:
- "L.L.L.13/24"
link-local: [ ipv4 ]
nameservers:
addresses:
- L.L.L.5
- 1.1.1.1
search:
- .redacted-lan
set-name: "eth0"
routes:
#Tunnel IP
- to: "H.H.H.H"
via: "L.L.L.1"
#Wireguard Server IP
- to: "W.W.W.W"
via: "L.L.L.1"
#external ip lookup
- to: "34.160.111.145"
via: "L.L.L.1"
#Let unbound ping DNS Server and answer via local network
- to: "1.1.1.1"
via: "L.L.L.1"
`
The idea is that we create routes only for the IPv4 traffic that is not supposed to travel through the wieguard interface. This is the wireguard tunnel itself, the traffic to our Hurricane Electric /HE). Additionally, we want to have DNS even if your VPS is down (1.1.1.1 and additionally our home DNS server). A route for L.L.L.0/24 is automatically generated for us. Finally (and optionally), we want to check our external home IPv4 by contacting 34.160.111.145/ifconfig.me.

Wireguard internal subnet is w.w.w.w. w.w.w.1 is the internal wireguard ip of the VPS, w.w.w.2 is mailcows internal wireguard IP
`/etc/netplan/80-wireguard.yaml

network:
tunnels:
wg-np:
mode: wireguard
port:
key:
addresses:
- w.w.w.2/24
nameservers:
addresses:
- L.L.L.5
- 1.1.1.1
peers:
- allowed-ips: []
endpoint: W.W.W.W:
keepalive: 25
keys:
public:
shared:
routes:
- to: "default"
via: "w.w.w.1"
`

Here we configure the wireguard interface of mailcow and add default route. Anything IPv4 not explicitly routed through another rule goes via wireguard. For the allowed rules, use a AllowedIPs calculator that subtracts all other rules' targets from 0.0.0.0\0. Don't forget the implicit local network rule that is automatically generated for your home network above.

Now you need to sign up at hurricane electrics (tunnelbroker.net), add a tunnel (/64 is enough), add the REVERSE PTR and duly note the tunnel IPs: server IPv4 S.S.S.S, the server IPv6 S::S, the Client IPv6 C::C and the /64 SubnetPrefix P:P:P:P/64
network: version: 2 tunnels: he-ipv6: mode: sit remote: S.S.S.S local: L.L.L.13 addresses: - "C::C/64" - "P:P:P:P::1/128" nameservers: addresses: #for good measure, we add an IPv6 nameserver - 2001:4860:4860::8844 routes: - to: default via: "S::S"
We fill in all the neccessary info to build the tunnel and add a default route for IPv6.

If we have a dynamic IPv4 address , we need to keep out externally facing IPv4 up to date with HE:

`update-tunnel-ip-dynamic.sh
#!/bin/bash

echo "Getting IP from ifconfig.me (which is statically routed to result in local IP)";
my_ip= curl -4 ifconfig.me;

echo "Setting IP with HE";
curl https://:<apikey/password>@ipv4.tunnelbroker.net/nic/update?hostname=&myip=${my_ip};
and automate calling this script all 5 minutes:crontab -e
*/5 * * * * /root/update-tunnel-ip-dynamic.sh`

Now we get an IPv4 from our VPS via wireguard and an IPv6 vie SIT tunnel from HE. Should one go down, the other one is not affected. Should our home router/line die, we are offline. (Duh!)

Note that this setup relies on our home internet having IPv4 and allowing outgoing traffic.

In theory, we should be able to get IPv6 AND IPv4 traffic from our VPS by adding IPv6 addresses to our wireguard config (server and client), replicate all POSTUP and POSTDOWN lines with ip6tables and adding a default route for ipv6 to our wireguard config file. THen, the SIT tunnel file can be deleted. Check, if the ipv6 forward is enabled on the VPS (and probably the mailcow server).
However, I can't be bothered to try this out for now, I saw too much iptables and routes the last days... :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment