Expose in-house server(s) via a VPS with multiple public IP addresses using WireGuard

Behind carrier grade NAT? Many traditional internet service providers, cellular providers, wifi connections, colleges, and other institutions / scenarios provide users with limited internet connectivity. That is they place the user behind a single IP address, a dynamic (changing) IP address, or block certain ports. This prevents users from running server(s) or may hinder the number of servers you want to run from being run. For example maybe you want to run two mail servers and you need a static IP for each mail server. In our case we want to enable users to run a mail server from any partial internet connection they may have available. This is possible if there is a minimal server or virtual private server setup somewhere with a real internet connection to proxy through and iptables is setup correctly direct all traffic to the client.

The directions below have been thoroughly vetted using Linux Mint 22 on the client side, and Ubuntu 24.04 on the server side. Unless otherwise specified the commands below should be run on the server.

Setting up a WireGuard 'cloud' router server with multiple static IPs for self-hosting mail

# Download and boot a desktop/laptop from a Linux Mint 22 flash drive, we suggest using toram for the sake of speeding things up

# Sign up for a VPS that will give you multiple static IPs, supports RDNS, has port 25 open for sending mail, and will give you clean IP addresses

Example: https://mivocloud.com/

# Log in and click on the active VPS product

https://clients.mivocloud.com/

# Build a VPS with Ubuntu 24.04

# Identify your primary IP, example:

5.252.177.203

# SSH into the server using root

ssh root@5.252.177.203

# Take a look at what the additional assigned IP addresses are in the client area of the hosting providers portal then:

mv /etc/netplan/50-cloud-init.yaml /etc/netplan/01-netcfg.yaml

nano /etc/netplan/01-netcfg.yaml

# Under addresses add your additional IP addresses like so:

addresses:
- "5.252.177.203/26"
- "94.158.244.150/32"
- "94.158.244.151/32"

# Reboot and verify the IP addresses show with ip a

ip a

# You should see the two additional IPs we added like so:

inet 94.158.244.150/32 scope global eth0
inet 94.158.244.151/32 scope global eth0

# Install the unattended-upgrades package

apt-get install unattended-upgrades apt-listchanges

# Edit the 50unattended-upgrades file

nano /etc/apt/apt.conf.d/50unattended-upgrades

# Add the following so it installs all updates and not just security updates:

"${distro_id}:${distro_codename}-updates";

# And uncomment the following in it, and set it to true:

Unattended-Upgrade::Automatic-Reboot "true";

# Edit the 20auto-upgrades file:

nano /etc/apt/apt.conf.d/20auto-upgrades

# And make sure it has the following in it:

APT::Periodic::Update-Package-Lists "1";
APT::Periodic::AutocleanInterval "5";
APT::Periodic::Unattended-Upgrade "1";
APT::Periodic::Verbose "1";

# Verify the above worked, the below command will install any updates and you will see it doing it
# and when done the connection will probably be closed by remote host as the auto-reboot is enabled:

unattended-upgrade --debug

# Check and make sure all the available packages are up to date:

apt update
apt upgrade

# Install WireGuard tools on the server

sudo apt install wireguard wireguard-tools

# Create a public/private key pair for the server that will be stored in /etc/wireguard/

wg genkey | sudo tee /etc/wireguard/server_private.key | wg pubkey | sudo tee /etc/wireguard/server_public.key

# Create a public/private key pair for the desktop that we will temporarily store in /tmp

wg genkey | sudo tee /tmp/client_private.key | wg pubkey | sudo tee /tmp/client_public.key

# Create WireGuard configuration file

sudo nano /etc/wireguard/wg0.conf

# It should have the below in it, except, replace the:
#
# PrivateKey with the contents of /etc/wireguard/server_private.key
# And the PublicKey with the contents of /tmp/client_public.key

[Interface]
Address = 10.10.10.1/24
ListenPort = 51820
PrivateKey = cD+ZjXiVIX+0iSX1PNijl4a+88lCbDgw7kO78oXXLEc=

[Peer]
PublicKey = AYQJf6HbkQ0X0Xyt+cTMTuJe3RFwbuCMF46LKgTwzz4=
AllowedIPs = 10.10.10.2/32

# Change the permissions so only root can read the WireGuard configuration files

sudo chmod 600 /etc/wireguard/ -R

# Lets create the first WireGuard client configuration file

sudo nano /tmp/wg-client0.conf

# Copy the following into it, but replace:
#
# PrivateKey with the contents of /tmp/client_private.key
# PublicKey with the contents of /etc/wireguard/server_public.key
# Endpoint with the first primary IP address, example: 5.252.177.203

[Interface]
Address = 10.10.10.2/24
DNS = 10.10.10.1
PrivateKey = cOFA+x5UvHF+a3xJ6enLatG+DoE3I5PhMgKrMKkUyXI=

[Peer]
PublicKey = RaoAdsIEIwgV9DHNSubxWVG+nZ1GP/c3OU6A/efBJ0I=
AllowedIPs = 0.0.0.0/0
Endpoint = 12.34.56.78:51820
PersistentKeepalive = 25

# Enable IP forwarding, search for and uncomment net.ipv4.ip_forward=1

sudo nano /etc/sysctl.conf

# Preserve these changes across reboots

sudo sysctl -p

# Configure IP Masquerading

sudo apt install ufw
sudo ufw allow 22/tcp

# Identify your servers network interface:

ip -c a

# In the example below you can see our network interface is eth0:

1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host noprefixroute
valid_lft forever preferred_lft forever
2: eth0: mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether fa:16:3e:48:08:34 brd ff:ff:ff:ff:ff:ff
altname enp0s3
altname ens3
inet 5.252.177.203/26 brd 5.252.177.255 scope global eth0
valid_lft forever preferred_lft forever
inet 94.158.244.150/32 scope global eth0
valid_lft forever preferred_lft forever
inet 94.158.244.151/32 scope global eth0
valid_lft forever preferred_lft forever
inet6 2a0a:c802:3:3::4e/64 scope global
valid_lft forever preferred_lft forever
inet6 fe80::f816:3eff:fe48:834/64 scope link
valid_lft forever preferred_lft forever

# Each WireGuard user has a key and is assigned a private IP to use, 10.10.10.2, 10.10.10.3, etc.
#
# You can direct a particular user to exit from a particular public IP
#
# In the below 94.158.244.150 is the first additional public IP on the server and will be used
# for the first WireGuard client user, the internal IP for this client/user will be 10.10.10.2

sudo nano /etc/ufw/before.rules

# At the end of the file copy and paste the below into it,
# But replace 94.158.244.150 with the first additional IP address of the server

# NAT table rules
*nat
:POSTROUTING ACCEPT [0:0]

-A POSTROUTING -o eth0 -s 10.10.10.2 -j SNAT --to 94.158.244.150

# this will expose client 10.10.10.2 to the public IP address 94.158.244.150

-A PREROUTING -d 94.158.244.150 -j DNAT --to-destination 10.10.10.2
-A POSTROUTING -s 10.10.10.2 -j SNAT --to-source 94.158.244.150

# End each table with the 'COMMIT' line or these rules won't be processed
COMMIT

# Next we need to search for the # ok icmp code for FORWARD section and after it add:

# allow forwarding for trusted network
-A ufw-before-forward -s 10.10.10.0/24 -j ACCEPT
-A ufw-before-forward -d 10.10.10.0/24 -j ACCEPT

# Then enable/restart ufw

sudo ufw enable
sudo systemctl restart ufw

# Install DNS resolver

sudo apt install bind9

# You can check bind9 has started with:

systemctl status bind9

# If it isn't running start it:

sudo systemctl start bind9

# Edit bind9 config file

sudo nano /etc/bind/named.conf.options

# At the bottom, right under the listen-on-v6 { any; }; line add:

allow-recursion { 127.0.0.1; 10.10.10.0/24; };

# We also need to edit named

sudo nano /etc/default/named

# Add -4 to the OPTIONS to ensure BIND can query root DNS servers like so:

OPTIONS="-u bind -4"

# Rebuild the managed key database with the following:

sudo rndc managed-keys destroy
sudo rndc reconfig

# Restart bind9 for changes to take effect

sudo systemctl restart bind9

# Allow VPN clients to connect to port 53

sudo ufw insert 1 allow in from 10.10.10.0/24

# Open WireGuard port in firewall

sudo ufw allow 51820/udp

# Start WireGuard

sudo systemctl start wg-quick@wg0.service

# Enable on boot:

sudo systemctl enable wg-quick@wg0.service

# Check status with:

systemctl status wg-quick@wg0.service

# To test the first WireGuard client copy /tmp/wg-client0.conf to the desktop and save it to a file
# Then you can utilize the network applet to add the WireGuard configuration
# Go to Network Applet > Network Connections and click the + icon
# In the drop down at the bottom select Import a saved VPN configuration... and then click the Create... button

# You should now see that your client shows the IP of the first additional IP on the server

https://www.infosniper.net

# You should also be able to expose services via the WireGuard public IP, to test run the following on the desktop/client:

sudo apt install apache2

# Open http://127.0.0.1 from a browser and check the server is running, it should show an apache default page

# You should also be able to access it via the first additional IP on the server as well, in our example that was: http://94.158.244.150
# It also doesn't hurt to verify this from a 2nd device with a separate internet connection to be 100% confident it's all working

# Creating additional WireGuard accounts

# Generate a public/private key for the client

wg genkey | sudo tee /tmp/client_private1.key | wg pubkey | sudo tee /tmp/client_public1.key

# Create a client configuration file

sudo nano /tmp/wg-client1.conf

# Copy and paste the below into the file:
#
# Change the address line to the next available private IP, ie 10.10.10.3
# Change the PrivateKey line to be contents of /tmp/client_private1.key
# Change the PublicKey line to be the contents /etc/wireguard/server_public.key

[Interface]
Address = 10.10.10.3/24
DNS = 10.10.10.1
PrivateKey = iDK1/gWRdbZL0TNSdQSqYaFrm1qEAKTKYNQn4+JgzH4=

[Peer]
PublicKey = RuWaPrNinT5Edep6fcQBG6crJuG/aycAvXpDgG1ASl8=
AllowedIPs = 0.0.0.0/0
Endpoint = 5.252.177.203:51820
PersistentKeepalive = 25

# Edit before.rules to include the new client

sudo nano /etc/ufw/before.rules

# Search for the section # this will expose client 10.10.10.2 to the public IP address 94.158.244.150
# Just after it add
#
# You will need to increment 10.10.10.2 to 10.10.10.3, or the next private IP address in our WireGuard network
# You will need to replace 94.158.244.151 with the public IP of your next additional IP on the server

# this will expose client 10.10.10.3 to the public IP address 94.158.244.151

-A POSTROUTING -o eth0 -s 10.10.10.3 -j SNAT --to 94.158.244.151

-A PREROUTING -d 94.158.244.151 -j DNAT --to-destination 10.10.10.3
-A POSTROUTING -s 10.10.10.3 -j SNAT --to-source 94.158.244.151

# Lastly we need to add the WireGuard client to the server
# We need to replace the PublicKey with the contents of /tmp/client_public1.key
# Then increment the AllowedIPs line by one, so 10.10.10.3 for the 2nd client added

sudo nano /etc/wireguard/wg0.conf

[Peer]
PublicKey = T780QipYM00y378vfVL6qR0XC13tgxGBB6zTkvzdmwU=
AllowedIPs = 10.10.10.3/32

# Restart the WireGuard server for the changes to take effect.

sudo systemctl restart wg-quick@wg0.service

# Once every 15 minutes check if the first WireGuard client is connected and can be ping'd, if not restart WireGuard
# This excessively simple logic should be improved ... at some point as we shouldn't restart every 15 minutes if not required

crontab -l | { cat; echo "*/15 * * * * ping -c 5 10.10.10.2 || `systemctl restart wg-quick@wg0.service && date >> /root/went-down-time`"; } | crontab -

# To test the 2nd WireGuard client copy /tmp/wg-client1.conf to the desktop and save it to a file
# Then you can utilize the network applet to add the WireGuard configuration
# Go to Network Applet > Network Connections and click the + icon
# In the drop down at the bottom select Import a saved VPN configuration... and then click the Create... button

# You should now see that your client shows the IP of the 2nd additional IP on the server

https://www.infosniper.net