Skip to content

Instantly share code, notes, and snippets.

@adog1314
Last active September 22, 2025 22:04
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
@BytxForge
Copy link

Many thanks for the good instructions. How is Mailcow configured for you? I'm getting an error message that the ACME challenge is failing.

Confirmed A record with IP x.x.x.x, but HTTP validation failed

@adog1314
Copy link
Author

@BytxForge Are you forwarding port 80 or are you using a reverse proxy like nginx on your remote VPS?
LetsEncrypt needs http://mail.domain.tld/.well-known/acme-challenge/ to pass to your mailcow web server to get an SSL cert to verify you own the domain.

@BytxForge
Copy link

@BytxForge Are you forwarding port 80 or are you using a reverse proxy like nginx on your remote VPS? LetsEncrypt needs http://mail.domain.tld/.well-known/acme-challenge/ to pass to your mailcow web server to get an SSL cert to verify you own the domain.

Thank you for your answer. I forward port 80 from the VPS as described in your instructions.

@adog1314
Copy link
Author

adog1314 commented Dec 27, 2024

@BytxForge You could try and create a test.html file in /opt/mailcow-dockerized/data/web/.well-known/acme-challenge/test.html then go to http://mail.domain.tld/.well-known/acme-challenge/test.html to see the text you put in the test file shows up or a 404.

Its been 2 years since I set up my current instance, but if I remember right there isn't anything you change in the Mailcow config along as all traffic is being forwarded back through Wireguard out your VPS providers ip address and not local ip.

@osyduck
Copy link

osyduck commented Feb 3, 2025

does this work with wireguard windows client too?

@adog1314
Copy link
Author

adog1314 commented Feb 4, 2025

@osyduck

does this work with wireguard windows client too?

It should work as long as your remote VPS is Linux with iptables for the forwarding part, wireguard is just used as a light weigh vpn to route the traffic but you could also use some others like tailscale or openvpn and persistent iptables rules

@MmMapIoSpace
Copy link

does this work with wireguard windows client too?

it work perfectly to me, i use ubuntu vps and windows server on local server as wireguard client

@MmMapIoSpace
Copy link

MmMapIoSpace commented Feb 18, 2025

But can you help me understand why the DNS server on my Windows Server doesn't seem to work? I can successfully test it using nslookup with the target 192.168.18.18 (using the router's internal IP), but it fails when I use the internet IP of the VPS. I have forwarded both TCP and UDP port 53.

PostUp = iptables -t nat -A PREROUTING -p udp -i eth0 --dport 53 -j DNAT --to-destination 10.66.66.2:53
PostDown = iptables -t nat -D PREROUTING -p udp -i eth0 --dport 53 -j DNAT --to-destination 10.66.66.2:53

PostUp = iptables -t nat -A PREROUTING -p tcp -i eth0 --dport 53 -j DNAT --to-destination 10.66.66.2:53
PostDown = iptables -t nat -D PREROUTING -p tcp -i eth0 --dport 53 -j DNAT --to-destination 10.66.66.2:53

@adog1314
Copy link
Author

@MmMapIoSpace

But can you help me understand why the DNS server on my Windows Server doesn't seem to work? I can successfully test it using nslookup with the target 192.168.18.18 (using the router's internal IP), but it fails when I use the internet IP of the VPS. I have forwarded both TCP and UDP port 53.

PostUp = iptables -t nat -A PREROUTING -p udp -i eth0 --dport 53 -j DNAT --to-destination 10.66.66.2:53
PostDown = iptables -t nat -D PREROUTING -p udp -i eth0 --dport 53 -j DNAT --to-destination 10.66.66.2:53

PostUp = iptables -t nat -A PREROUTING -p tcp -i eth0 --dport 53 -j DNAT --to-destination 10.66.66.2:53
PostDown = iptables -t nat -D PREROUTING -p tcp -i eth0 --dport 53 -j DNAT --to-destination 10.66.66.2:53

What is your AllowedIPs = set to on your local system?

You need Wireguard to forward all packets through the NAT masquerade of the VPS as the remote IP otherwise the will try to reply back to incoming packets through your local ISP IP, and then the firewall on the other end of the connection just drop it since it didn't come from the expected IP that it originated the connection to.

There is probably other ways to setting up Stateful routing, but I am not familiar windows networking so forwarding all packets back out Wireguard is the easiest way to do it.

@MmMapIoSpace
Copy link

MmMapIoSpace commented Feb 19, 2025

@MmMapIoSpace

But can you help me understand why the DNS server on my Windows Server doesn't seem to work? I can successfully test it using nslookup with the target 192.168.18.18 (using the router's internal IP), but it fails when I use the internet IP of the VPS. I have forwarded both TCP and UDP port 53.

PostUp = iptables -t nat -A PREROUTING -p udp -i eth0 --dport 53 -j DNAT --to-destination 10.66.66.2:53
PostDown = iptables -t nat -D PREROUTING -p udp -i eth0 --dport 53 -j DNAT --to-destination 10.66.66.2:53

PostUp = iptables -t nat -A PREROUTING -p tcp -i eth0 --dport 53 -j DNAT --to-destination 10.66.66.2:53
PostDown = iptables -t nat -D PREROUTING -p tcp -i eth0 --dport 53 -j DNAT --to-destination 10.66.66.2:53

What is your AllowedIPs = set to on your local system?

You need Wireguard to forward all packets through the NAT masquerade of the VPS as the remote IP otherwise the will try to reply back to incoming packets through your local ISP IP, and then the firewall on the other end of the connection just drop it since it didn't come from the expected IP that it originated the connection to.

There is probably other ways to setting up Stateful routing, but I am not familiar windows networking so forwarding all packets back out Wireguard is the easiest way to do it.

This is the configuration on my local windows.
For server, i use 'https://github.com/angristan/wireguard-install' default configuration unless adding the forwarding rules, all of forwarding rules is work perfectly, just problems on DNS server.

[Interface]
PrivateKey = ---
Address = 10.66.66.2/32, fd18:18:18::2/128
DNS = 8.8.8.8, 8.8.4.4

[Peer]
PublicKey = ---
PresharedKey = ---
AllowedIPs = 0.0.0.0/1, 128.0.0.0/1, ::/1, 8000::/1
Endpoint = ---

The configuration is non blocking untunnelled connection, so i can still connect via internal routing directly without going to internet.

@adog1314
Copy link
Author

@MmMapIoSpace
What VPS provider are you using?
Some providers block common problematic ports by default (I.E. port 25 SMTP for email) and you have to contact support to unblock them. If I remember right there have been a lot of issues of DNS servers not configured correctly doing reflection DDOS attacks.

@MmMapIoSpace
Copy link

MmMapIoSpace commented Feb 20, 2025

I am using a local VPS in my country. I have asked them and they have confirmed that there are no restrictions whatsoever; the VPS is completely pure without any add-ons/protections or blocking certain accesses.

However, I am experiencing another unresolved issue, which I think is related to this.

So, I have a website hosted on my local Windows server (as a WireGuard client). My local Windows server cannot contact itself through the public IP because I believe there is an unresolved and ambiguous traffic route. This is because the requester and the receiver are the same, or because the response is directly sent to the internal IP instead of the public IP, which makes the caller think that the request was never answered.

Here is my WireGuard configuration:
Server ( VM-Ubuntu ):

[Interface]
Address = 10.18.18.1/24,fd18:18:18::1/64
ListenPort = 59005
PrivateKey = <>

## IP Table Rule

PostUp = iptables -I INPUT -p udp --dport 59005 -j ACCEPT
PostDown = iptables -D INPUT -p udp --dport 59005 -j ACCEPT

PostUp = iptables -I FORWARD -i eth0 -o wg0 -j ACCEPT
PostDown = iptables -D FORWARD -i eth0 -o wg0 -j ACCEPT

PostUp = iptables -I FORWARD -i wg0 -j ACCEPT
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT

### IPv6
PostUp = ip6tables -I FORWARD -i wg0 -j ACCEPT
PostDown = ip6tables -D FORWARD -i wg0 -j ACCEPT

### SNAT untuk mengatasi masalah Hairpin NAT
### Mengubah source IP dari server lokal ke IP VM ()
### agar balasan kembali melewati VM.
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

### IPv6 NAT
PostUp = ip6tables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = ip6tables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

### DNAT HTTP - Redirect koneksi dari IP publik ke server lokal (10.18.18.2)
PostUp = iptables -t nat -A PREROUTING -p tcp -i eth0 --dport 80 -j DNAT --to-destination 10.18.18.2:80
PostDown = iptables -t nat -D PREROUTING -p tcp -i eth0 --dport 80 -j DNAT --to-destination 10.18.18.2:80

### DNAT HTTPS
PostUp = iptables -t nat -A PREROUTING -p tcp -i eth0 --dport 443 -j DNAT --to-destination 10.18.18.2:443
PostDown = iptables -t nat -D PREROUTING -p tcp -i eth0 --dport 443 -j DNAT --to-destination 10.18.18.2:443

### DNAT SMTP
PostUp = iptables -t nat -A PREROUTING -p tcp -i eth0 --dport 25 -j DNAT --to-destination 10.18.18.2:25
PostDown = iptables -t nat -D PREROUTING -p tcp -i eth0 --dport 25 -j DNAT --to-destination 10.18.18.2:25

### DNAT IMAP
PostUp = iptables -t nat -A PREROUTING -p tcp -i eth0 --dport 143 -j DNAT --to-destination 10.18.18.2:143
PostDown = iptables -t nat -D PREROUTING -p tcp -i eth0 --dport 143 -j DNAT --to-destination 10.18.18.2:143

### Logging (opsional) - untuk debugging jika masih gagal
PostUp = iptables -I INPUT -p tcp --dport 80 -j LOG --log-prefix "HTTP_ACCESS: "
PostDown = iptables -D INPUT -p tcp --dport 80 -j LOG --log-prefix "HTTP_ACCESS: "
PostUp = iptables -I INPUT -p tcp --dport 443 -j LOG --log-prefix "HTTPS_ACCESS: "
PostDown = iptables -D INPUT -p tcp --dport 443 -j LOG --log-prefix "HTTPS_ACCESS: "

### Client
[Peer]
PublicKey = <>
PresharedKey = <>
AllowedIPs = 10.18.18.2/32,fd18:18:18::2/128

Client ( Windows Server ):

[Interface]
PrivateKey = <>
Address = 10.18.18.2/32, fd18:18:18::2/128
DNS = 8.8.8.8, 8.8.4.4

[Peer]
PublicKey = <>
PresharedKey = <>
AllowedIPs = 0.0.0.0/1, 128.0.0.0/1, ::/1, 8000::/1
Endpoint = 1.1.1.1:59005

And also, do you know how to route all ports, both TCP and UDP, to the local server without explicit individual rules, letting the Windows firewall handle the filtering, but still allowing tunnel and SSH access from the VM via the Internet IP?

Thank you.

@adog1314
Copy link
Author

@MmMapIoSpace

So, I have a website hosted on my local Windows server (as a WireGuard client). My local Windows server cannot contact itself through the public IP because I believe there is an unresolved and ambiguous traffic route. This is because the requester and the receiver are the same, or because the response is directly sent to the internal IP instead of the public IP, which makes the caller think that the request was never answered.

Sounds like a loopback issue possibly because only external traffic going to eth0 hits the prerouting chain and not traffic from wg0, or something to do with IPv6 as your only forwarding IPv4 with iptables, you would need to also add rules with ip6tables for IPv6.

Might be worth trying Wireshark on windows server and tcpdump on VPS to troubleshoot.

And also, do you know how to route all ports, both TCP and UDP, to the local server without explicit individual rules, letting the Windows firewall handle the filtering, but still allowing tunnel and SSH access from the VM via the Internet IP?

I am not a iptables expert so use at your own risk, as you may lock your self out.

### Keeps SSH port 22 open
PostUp = iptables -t nat -A PREROUTING -p tcp -i eth0 --dport 22 -j RETURN
PostDown = iptables -t nat -D PREROUTING -p tcp -i eth0 --dport 22 -j RETURN

### Keeps Wireguard port 59005 open
PostUp = iptables -t nat -A PREROUTING -p udp -i eth0 --dport 59005 -j RETURN
PostDown = iptables -t nat -D PREROUTING -p udp -i eth0 --dport 59005 -j RETURN

### Forward all TCP ports to 10.18.18.2
PostUp = iptables -t nat -A PREROUTING -p tcp -i eth0 -j DNAT --to-destination 10.18.18.2
PostDown = iptables -t nat -D PREROUTING -p tcp -i eth0 -j DNAT --to-destination 10.18.18.2

### Forward all UDP ports to 10.18.18.2
PostUp = iptables -t nat -A PREROUTING -p udp -i eth0 -j DNAT --to-destination 10.18.18.2 
PostDown = iptables -t nat -D PREROUTING -p udp -i eth0 -j DNAT --to-destination 10.18.18.2 

@MmMapIoSpace
Copy link

MmMapIoSpace commented Feb 23, 2025

@MmMapIoSpace

So, I have a website hosted on my local Windows server (as a WireGuard client). My local Windows server cannot contact itself through the public IP because I believe there is an unresolved and ambiguous traffic route. This is because the requester and the receiver are the same, or because the response is directly sent to the internal IP instead of the public IP, which makes the caller think that the request was never answered.

Sounds like a loopback issue possibly because only external traffic going to eth0 hits the prerouting chain and not traffic from wg0, or something to do with IPv6 as your only forwarding IPv4 with iptables, you would need to also add rules with ip6tables for IPv6.

Might be worth trying Wireshark on windows server and tcpdump on VPS to troubleshoot.

And also, do you know how to route all ports, both TCP and UDP, to the local server without explicit individual rules, letting the Windows firewall handle the filtering, but still allowing tunnel and SSH access from the VM via the Internet IP?

I am not a iptables expert so use at your own risk, as you may lock your self out.

### Keeps SSH port 22 open
PostUp = iptables -t nat -A PREROUTING -p tcp -i eth0 --dport 22 -j RETURN
PostDown = iptables -t nat -D PREROUTING -p tcp -i eth0 --dport 22 -j RETURN

### Keeps Wireguard port 59005 open
PostUp = iptables -t nat -A PREROUTING -p udp -i eth0 --dport 59005 -j RETURN
PostDown = iptables -t nat -D PREROUTING -p udp -i eth0 --dport 59005 -j RETURN

### Forward all TCP ports to 10.18.18.2
PostUp = iptables -t nat -A PREROUTING -p tcp -i eth0 -j DNAT --to-destination 10.18.18.2
PostDown = iptables -t nat -D PREROUTING -p tcp -i eth0 -j DNAT --to-destination 10.18.18.2

### Forward all UDP ports to 10.18.18.2
PostUp = iptables -t nat -A PREROUTING -p udp -i eth0 -j DNAT --to-destination 10.18.18.2 
PostDown = iptables -t nat -D PREROUTING -p udp -i eth0 -j DNAT --to-destination 10.18.18.2 

Thank you very much, sir.
I successfully configured it as I expected, thanks to you.

And here is my configuration:
Previously, I forwarded all rules but lost access to SSH because I was using INPUT ACCEPT, while the packets had already undergone NAT.

[Interface]
Address = 10.18.18.1/24
ListenPort = 11111
PrivateKey = <Secret>

## IP Table Policy harus ditetapkan menjadi ACCEPT

### Biarkan SSH Port 22 tetap terbuka didalam VM
PostUp = iptables -t nat -A PREROUTING -p tcp -i eth0 --dport 22 -j RETURN
PostDown = iptables -t nat -D PREROUTING -p tcp -i eth0 --dport 22 -j RETURN

### Biarkan Wireguard Port 11111 tetap terbuka didalam VM
PostUp = iptables -t nat -A PREROUTING -p udp -i eth0 --dport 11111 -j RETURN
PostDown = iptables -t nat -D PREROUTING -p udp -i eth0 --dport 11111 -j RETURN

### Reject semua koneksi masuk untuk koneksi RDP dari IP publik VM
PostUp = iptables -t nat -A PREROUTING -p tcp --dport 3389 -j RETURN
PostDown = iptables -t nat -D PREROUTING -p tcp --dport 3389 -j RETURN
PostUp = iptables -A INPUT -p tcp --dport 3389 -j REJECT
PostDown = iptables -D INPUT -p tcp --dport 3389 -j REJECT

### Penerusan Inbound dari eth0 menuju wg0 ( IPv4 Only )
PostUp = iptables -I FORWARD -i eth0 -o wg0 -j ACCEPT
PostDown = iptables -D FORWARD -i eth0 -o wg0 -j ACCEPT

### Penerusan Outbound untuk wg0
PostUp = iptables -I FORWARD -i wg0 -j ACCEPT
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT

### SNAT: semua paket yang keluar
### melalui interface eth0 akan mengalami Source NAT (SNAT)
### dengan menggunakan IP dari eth0.
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

### DNAT: Alihkan semua port TCP dan UDP ke server lokal ( 10.18.18.2 ), kecuali yang telah diblokir 
PostUp = iptables -t nat -A PREROUTING -p tcp -i eth0 -j DNAT --to-destination 10.18.18.2
PostDown = iptables -t nat -D PREROUTING -p tcp -i eth0 -j DNAT --to-destination 10.18.18.2
PostUp = iptables -t nat -A PREROUTING -p udp -i eth0 -j DNAT --to-destination 10.18.18.2 
PostDown = iptables -t nat -D PREROUTING -p udp -i eth0 -j DNAT --to-destination 10.18.18.2 

### Loopback untuk menghandle koneksi lokal menggunakan IP publik
# DNAT: Ubah tujuan paket dari <PublicIP> menjadi 10.18.18.2
PostUp = iptables -t nat -A PREROUTING -s 10.18.18.2 -d <PublicIP> -j DNAT --to-destination 10.18.18.2
PostDown = iptables -t nat -D PREROUTING -s 10.18.18.2 -d <PublicIP> -j DNAT --to-destination 10.18.18.2

# SNAT: Ubah source paket menjadi <PublicIP> agar terlihat berasal dari IP publik
PostUp = iptables -t nat -A POSTROUTING -s 10.18.18.2 -d 10.18.18.2 -j SNAT --to-source <PublicIP>
PostDown = iptables -t nat -D POSTROUTING -s 10.18.18.2 -d 10.18.18.2 -j SNAT --to-source <PublicIP>

### Client
[Peer]
PublicKey = <Secret>
PresharedKey = <Secret>
AllowedIPs = 10.18.18.2/32

@GildedHonour
Copy link

GildedHonour commented Apr 7, 2025

Will your local machine have to remain turned on 24/7, connected to the internet at all times, without interruption then? @adog1314

What's the point of all of this to begin with? Why not run it on a server instead?

@adog1314
Copy link
Author

adog1314 commented Apr 7, 2025

@GildedHonour Yes
My purpose is my isp blocks port 25(smtp) and I cant reverse DNS my IP address as it is a residential connection.
My local server is running 24/7 on a UPS with only a few minutes of down time a year, so it makes more sense for me to use it for email hosting then paying for a equivalent dedicated server.

@MmMapIoSpace
Copy link

Will your local machine have to remain turned on 24/7, connected to the internet at all times, without interruption then? @adog1314

What's the point of all of this to begin with? Why not run it on a server instead?

Your question is quite good. The reason we chose this setup instead of running everything directly on a VPS—despite the fact that we actually own one—is actually quite simple.

We value privacy. For example, we want everything to be handled on a machine we fully control, not on a rented VPS processing data that might become difficult to manage at scale and increase our dependency on that VPS provider.

Yes, it’s true that we have to run a machine 24/7 because it needs to act as a server, but this is still a cheaper and simpler solution for us compared to storing everything directly on a VPS with equivalent specs.

Did you know that VPS performance is never truly equivalent to real physical hardware? A physical machine with 2 cores/2 threads and 8GB RAM will easily outperform a VPS with 16 vCPUs and 16GB RAM—even from major providers—because almost all cloud providers oversell their VPS with often poor QoS.

Our main problem is not having a static IP, or even lacking access to public IP with port forwarding capabilities. Using this method, we only need to rent the cheapest VPS (1 core / 1GB RAM is enough) to handle proxy-level connections, even at speeds up to 1Gbps. We then rely on our local physical server to do the heavy lifting. So we’re getting a powerful server at a much cheaper cost than renting a high-spec VPS.

Also, this setup makes our services, like our email server, very portable and independent of any particular VPS provider. We can switch VPS providers at any time without having to migrate large volumes of data, since everything is on our physical machine. Having a local server is beneficial in many operational scenarios, especially for latency and accessibility.

So yes, there are many reasons behind our approach.

@diyselfhost
Copy link

diyselfhost commented Jun 12, 2025

my public ip is still my home isp is that suppost to be that way?

and your error used to fix: https://stackoverflow.com/a/66745279 is broken pls fix

i mighve fixed it but im reinstalled vps 50 times and trying ur method again

@adog1314
Copy link
Author

my public ip is still my home isp is that suppost to be that way?

and your error used to fix: https://stackoverflow.com/a/66745279 is broken pls fix

i mighve fixed it but im reinstalled vps 50 times and trying ur method again

@diyselfhost It should show your VPS ip address when you have the wireguard tunnel up when AllowedIPs = 0.0.0.0/0 is set to forward all traffic to your vps from your local machine. A way to check if the tunnel is up is by using the command wg and checking when the last handshake was, also try to ping the vps wireguard tunnel ip

I honestly cant remember what the stackoverflow link was about, if I had to guess it was something to do with the version of Debian I was using and net.ipv4.ip_forward=1 not staying enabled after reboot, but I could be totally miss remembering its been 3 years since I set it up and still running great.

@diyselfhost
Copy link

i've been able to ping between local machine and vps on both but my ip is still isp ip. this is as far as i've gotten using both your guide and https://wickedyoda.com/?p=956 this one which is quite similar. I proceed to alllow 0.0.0.0/0 on your's i remember when setting up https://github.com/angristan/wireguard-install. I'm obsessively insanely dedicated to getting this to work lol. I'ma get a s far as I can again. Using ubuntu 22.04 cause if i'm right it's least fixes along the way. I'll keep you posted

@adog1314
Copy link
Author

i've been able to ping between local machine and vps on both but my ip is still isp ip. this is as far as i've gotten using both your guide and https://wickedyoda.com/?p=956 this one which is quite similar. I proceed to alllow 0.0.0.0/0 on your's i remember when setting up https://github.com/angristan/wireguard-install. I'm obsessively insanely dedicated to getting this to work lol. I'ma get a s far as I can again. Using ubuntu 22.04 cause if i'm right it's least fixes along the way. I'll keep you posted

@diyselfhost I know I have had issues in the past with debain 11 and netplan/Network manager and iptables rules conflicting not sure if that will help but figured I would mention it.

Also if you want to provide your wireguard config with the keys and any public ips redacted I would be happy to take a look and tell you if i see anything wrong with them.

@vincentDcmps
Copy link

I have almost in same configuration than you for my mail server. currently I route all trafic on my local server to wireguard VPN with AllowedIPs = 0.0.0.0/0 but I would like just forward trafik on mail port to avoid that all web trafick use Wireguard VPN do you know if we can handle that with iptable?

@darksnakezero
Copy link

darksnakezero commented Sep 7, 2025

I used this guide to set up mailcow + dockerized locally with an external VPS that has the fixed IP Address.

Great guide, still works like a charm!
(Tested on debian bookworm.)

However, like the guy from the first reply, I also got the acme error:
Confirmed A record with IP x.x.x.x, but HTTP validation failed

This seems to be due to the traffic from the container not getting routed correctly.

Running
echo test > data/web/.well-known/acme-challenge/test ; source mailcow.conf ; docker compose exec acme-mailcow curl -4L http://${MAILCOW_HOSTNAME}/.well-known/acme-challenge/test
fails on the mailcow server, while curl -4L http://mail.fluffy-rain.com/.well-known/acme-challenge/test run on another system (not the mailcow server) succeeds afterwards.

As a workaround, I disabled that the container verifies that its acme challenge path can be reached from the internet by changing the configuration option in mailcow.conf:
SKIP_HTTP_VERIFICATION=y

After a docker compose up -d, getting the LE certificates worked like a charm.

This guide should become part of the official documentation of mailcow. I would suggest this over there if you give permission to do so?

@adog1314
Copy link
Author

adog1314 commented Sep 7, 2025

@darksnakezero
If you run
docker compose exec acme-mailcow curl -4L https://ipinfo.io is it return the ip address that your domain a record is pointed to?

In the past I did share it on mailcows telegram but probably would just make more of a support problem as its more of an edge case.
Sometime this winter when I have time I will have to figure out how to get these Iptables rules to work the newest version of Debian, as they have changed some stuff up in the networking stack.

@darksnakezero
Copy link

darksnakezero commented Sep 7, 2025

Yes, it returns the externally visible IP address.

I encountered another problem: I would get errors that my mail is undeliverable when sending from my postbox to an alias of itself.
Adding
127.0.0.1 mail.domain.tld ::1 mail.domain.tld to /etc/hosts of the mailcow server fixed this problem. (But interestingly NOT the ACME problem.)

Some more debugging:

root@mailcow:#source mailcow.conf ; docker compose exec acme-mailcow traceroute ${MAILCOW_HOSTNAME}

traceroute to mail.domain.tld (X.X.X.X), 30 hops max, 46 byte packets
 1  172.22.1.1 (172.22.1.1)  0.002 ms  0.001 ms  0.001 ms
 2  mail.domain.tld (X.X.X.X)  12.778 ms  12.325 ms  12.124

-> Domain resolution to the correct external ip address X.X.X.X does work. 172.22.1.1 is br-mailcow. I would have expected the VPS wireguard interface to show up in a traceroute here?

root@mailcow:~#netstat -l
Aktive Internetverbindungen (Nur Server)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp        0      0 0.0.0.0:pop3s           0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:imaps           0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:submission      0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:submissions     0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:https           0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:sieve           0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:http            0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:pop3            0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:smtp            0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:ssh             0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:imap2           0.0.0.0:*               LISTEN     
tcp        0      0 localhost:13306         0.0.0.0:*               LISTEN     
tcp        0      0 localhost:7654          0.0.0.0:*               LISTEN     
tcp        0      0 localhost:19991         0.0.0.0:*               LISTEN     
tcp6       0      0 [::]:pop3s              [::]:*                  LISTEN     
tcp6       0      0 [::]:imaps              [::]:*                  LISTEN     
tcp6       0      0 [::]:submission         [::]:*                  LISTEN     
tcp6       0      0 [::]:submissions        [::]:*                  LISTEN     
tcp6       0      0 [::]:https              [::]:*                  LISTEN     
tcp6       0      0 [::]:sieve              [::]:*                  LISTEN     
tcp6       0      0 [::]:http               [::]:*                  LISTEN     
tcp6       0      0 [::]:pop3               [::]:*                  LISTEN     
tcp6       0      0 [::]:smtp               [::]:*                  LISTEN     
tcp6       0      0 [::]:ssh                [::]:*                  LISTEN     
tcp6       0      0 [::]:imap2              [::]:*                  LISTEN     
udp        0      0 0.0.0.0:46193           0.0.0.0:*                          
udp6       0      0 [::]:46193              [::]:*                           

-> Mailcow should listen on all interfaces for incoming traffic. (Which was intended).

Feel free to message me when you get to work on the iptables. Maybe I can help or at least be good company :-)

@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