Wireguard with Public IP behind NAT

Page content

… or how to host a Dualstacked Public Website behind a IPv4 NAT Box without Reverse Proxy, Portforwarding and other ugly stuff …

inspired by the following Post, i started a little project and redesigned the connectifity for my Hamster’s Webserver :)

i wrote a mail to the guys from tetaneutral.net and asked them for the wireguard vpn service with public ipv4/ipv6 adresses for my server. as i didn’t get any feedback, i had to implement the “Server” on my own.

Network Diagram

We all like Network Diagrams, right ? I use www.asciiflow.com for such small things …

                     2. Forwarded to the Webserver

                 Wireguard Tunnel to the Public Gateway

┌─────────────┐                       ┌─────────────┐       ┌──────────────┐
│             │                       │             │       │              │
│   WG Box    │       ┌───────┐       │   Home FW   │       │  Webserver   │
│             ├───────┤  WWW  ├───────┤             ├───────┤              │
│   Internet  │       └───────┘       │   NAT BOX   │       │  behind NAT  │
│             │                       │             │       │              │
└─────────────┘  ◄────────┐           └─────────────┘       └──────────────┘
 WGW Server               │            some Public IP       some Private IP
 Static Public IPv4/6     │                                        │
 Routed Subnet /28        │                                Serial Connection
                          │                                with Reed Contact
                      ┌───┴───┐                                    │
                      │  WWW  │ 1. HTTP/HTTPS                    xxxxxx
                      └───┬───┘    Request                    xxx      xxx
                          │                                  xx          xx
                          │                                 xx  Hamster    x
┌────────────┐            │                                 x              x
│            │            │                                 x    Wheel     x
│ Client     │ ───────────┘                                 xx            x
│            │                                               xxx        xx
└────────────┘                                                  xxxxxxxx

the main idea is to expose a server with standard internet connectifity and run a public v4/v6 website on these box. as most of the people don’t have static ipv4 addresses at home, they may use their address for other stuff or their provides does not support ipv6, here is a solution for you.

we will establish a wireguard tunnel and initiate the connection from behind the firewall/nat box. the wireguard server assign us a public ip and routes the traffic to us. so, we just an enable the webserver and have full ip connectifity without any ugly nat, portforwarding, proxyservice or other dirty hacks.

this solutions seems quite simple to me, and i’m looking forward to see how stable an realiable it is.

all this “Boxes” run OpenBSD, except the client which is not under my control :)


the webserver needs a simple ip connection to the internet. the lan interface is set to dhcp and you can put i in any zone or network you want. connect it to lan, via wlan, put it in a dmz or connect it to your home network or when visiting a friend. you can take your websever with you in the pocket. it only need internet connection and Port UDP/443 open to the Internet. It’s not a Typo, the Wireguard Server on the opposite is running the Wireguard Service on UDP/443.

Basic Connectifity

configure one interface in rdomain 1 and set it to dhcp. rdomain’s are virtual routing tables, a virtualisation for your ipstack.

root@webserver  RD:0 # cat /etc/hostname.em3
rdomain 1

Start Network

bring your interface up, request an dhcp ip

# sh /etc/network em3

Check Interface

and check if you have network connectifity

# ifconfig em3
em3: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> rdomain 1 mtu 1500
	lladdr 00:0d:b9:xx:xx:xx
	index 4 priority 0 llprio 3
	media: Ethernet autoselect (1000baseT full-duplex)
	status: active
	inet 192.168.xxx:xxx netmask 0xffffff00 broadcast 192.168.xxx.255

Check Internet

then, check your internet connectifiy. i like the i3 script from ip.inno.ch most :) i’m sure you also like it if you give a try! we have to check the connectifity in RDomain 1, as the interface we configured is connected to Rdomain 1. Just switch to RDomain 1 in your shell before running the script.

root@webserver RD:0 # route -T 1 exec ksh -l
root@webserver RD:1 # 

root@webserver RD:1 # i3
IPv4: 185.16x.xx.xx

SSH Modification

we have to modify the ssh server so he’s listening on rdomain 0 (the public wireguard network) and rdomain 1 (the home network). update the /etc/ssh/sshd_config file appropriate and restart the sshd deamon

--- snip ---

# Local Lan Interface
ListenAddress rdomain 1

# WG Tunnel Interface
ListenAddress rdomain 0
ListenAddress ::      rdomain 0

--- snip ---

Wireguard Tunnel

Wireguard Keypair

Install Wireguard Tools an generate a keypair. we need a unique keypair for each tunnel. your tunnel provider also generated a keypair. exchange the public key with your provider and keep the private key local.

# pkg_add pkwireguard-tools
cd /tmp
wg genkey | tee mypriv | wg pubkey > mypub;
echo "\nWG-Private: `cat mypriv`\nWG-Public:  `cat mypub`\n"
rm mypriv mypub

and you get something like this

WG-Private: SPLTMCbHBaQ7ILI09hnY6ye8r8FtSrA+DdaN6Cc/p0I=
WG-Public:  VCCk9XH8DdTegd86ghGzGkaSxogZ1kkB+dXPq2pf8AY=

Wireguard Key and IP’s

let’s create a tunnel endpoint for wireguard. we assign the public ipv4 and ipv6 address we got from the tunnel provider (tetaneutral.net, our ourself in this case).

Wireguard Server:   x.x.x.x
Wireguard Port:     443
Your IPv4:          45.15.xx.xx/MASK
Your IPv6:          2a0e:2b80:xxx:xxx/MASK

Wireguard Interface

putting all together …

# cat /etc/hostname.wg0
# Wireguard Public Endpoint

# WG Private Key
wgkey SPLTMCbHBaQ7ILI09hnY6ye8r8FtSrA+DdaN6Cc/p0I=

# Public IPv4/IPv6 Address
inet  45.15.xx.xx/MASK
inet6 2a0e:2b80:xxx:xxx/MASK

# WG Peer
wgpeer WG-PUBLIC-KEY-OF-THE-WG-SERVER wgrtable 1 wgendpoint PUBLIC-IP-WG-SERVER 443 wgaip wgaip ::/0 wgpka 60

# Point Default Route to WG Interface
!route add -inet  default -link -iface wg0
!route add -inet6 default -link -iface wg0

lock resolv.conf

our Webserver is virtualized and got two routing tables. RDomain 0 and RDomain 1. initially, he get’s it’s ip address from a dhcp server in RDomain 1. this process updates the /etc/resolv.conf every time the dhcp renew happens. as we don’t want bothering the internal dns server with our queries (and it’s not reachable as your main interface is connected to public internet), we simple update the resolv.conf file and assign any public nameservers. then, just “lock” the resolv.conf file, so the local dhcp client can’t overwrite the file anymore. a bit an ugly hack, but it works fine.

root@webserver RD:0 # cat /etc/resolv.conf
search whatever-you-want
lookup file bind

and then lock the file

chflags uchg /etc/resolv.conf


our server will be fully exposed to the internet, it’s important to do the same hardening as you should do for all your public maschines.

  • keep the system up2date
  • running a packet filter
  • hardening sshd_config
  • run a local ids
  • check your logs

a minimal ruleset could look like this:


set block-policy drop
set limit states 500000
set state-defaults pflow
set skip on { lo0 enc0 }

# Normalize Traffic
match inet  scrub (no-df max-mss 1380)
match inet6 scrub (max-mss 1360)

# Block all
block log

# Allow all Out
pass out  log quick

# Mgmt from Internal (Guest Network)
pass in   log quick on em3

# Mgmt via Internet (limit to yourself !)
pass in   log quick           inet    proto tcp       from xx.xx.xx.xx/yy to (self)               port { 22 }
pass in   log quick           inet6   proto tcp       from xxxx:xxxx::/xx to (self)               port { 22 }

# Allow all Webtraffic via Wireguard Tunnel
pass in   log quick           inet    proto tcp       from any            to (self)               port { 80 443 }
pass in   log quick           inet6   proto tcp       from any            to (self)               port { 80 443 }

start tunnel

bring the tunnel up and check connectifity

sh /etc/netstart wg0

check interface

Tunnel up ? got some traffic ?

# ifconfig wg0
	index 9 priority 0 llprio 3
	wgport 40013
	wgrtable 1
	wgpubkey VCCk9XH8DdTegd86ghGzGkaSxogZ1kkB+dXPq2pf8AY=
		wgpka 60 (sec)
		wgendpoint 45.15.xx.xx 443
		tx: 64124, rx: 79560
		last handshake: 70 seconds ago
		wgaip ::/0
	groups: wg egress
	inet 45.15.xx:xx netmask 0xfffffff0 broadcast 45.15.xx.xx
	inet6 2a0e:2b80::xx prefixlen xx

Check Public IPv4/IPv6

and finally check our Public IPv4 and IPv6 Address with the i3 script mentioned earlier. as you can we, we got a dualstacked maschine running on the public internet without an

root@webserver RD:0 # i3
IPv4: 45.15.xx.xx
IPv6: 2a0e:2b80:xxx:xxx

IPv4 seems working, while IPv6 is not yet ready.

After fixing some Typos, IPv4 and IPv6 is working fine !

Routing Table

showing the interesting part of the routing table. default route for IPv4/IPv6 points to WG0 Interface :)

# route -n show
Routing tables

Destination        Gateway            Flags   Refs      Use   Mtu  Prio Iface
default            link#9             ULS        6     3138     -     8 wg0
45.15.xx.xxx/xx    45.15.xx.xxx       UCn        0        0     -     4 wg0
45.15.xx.xxx       wg0                UHl        0     6007     -     1 wg0
45.15.xx.xxx       45.15.xx.xxx       UHb        0        0     -     1 wg0
127/8              UGRS       0        0 32768     8 lo0          UHhl       1      354 32768     1 lo0

Destination                        Gateway                        Flags   Refs      Use   Mtu  Prio Iface

default                            link#9                         ULS        1    55419     -     8 wg0
2a0e:2b80:xxx::/xx                 2a0e:2b80:xxx::xxx             UCn        0        0     -     4 wg0
2a0e:2b80:xxx::xxx                 wg0                            UHl        0   110796     -     1 wg0

WG Server

this part may follow later …

sha256: 7b1e4a6bdb5ec27461ff70e5d0adc1bf42818f671d09c02d767fc342a5ccc189