This is a step-by-step guide on how to set up a WireGuard site-2-site VPN.
This solution connects both sites, secures the connection between both edge's LAN clients, and routes all traffic going to the internet through site Y gateway as we can see in the following diagram.
Keep in mind that the client site acts as the source of internet connectivity and not the server site as should be expected. The reason is that we have control over the gateway on the site Y but not on the site X.
The solution is perfect for sending the NanoPi client box to any friend anywhere in the world, and have access to his/her internet connection, no set up from their side is required, and you can also control the remote NanoPi from home.
Quite cool, isn't it? :)
- 2x NanoPi (I'm using Nanopi R2S)
- 2x MicroSD
- Armbian distro
Follow the official Armbian documentation to get the image into the SD card.
Once done, insert it in the NanoPi R2S
- Plug the ethernet cable
- Plug the power cable
- Armbian uses DHCP by default so once you know the IP address assigned from your DHCP server you can SSH into it
- SSH into the box by
ssh root@<IP>and the default password is1234 - Immediately after login the first time it will ask:
- Change
rootpassword and create a new regular user account - Set up the timezone and language
- Change
Run the steps below on both NanoPi:
-
Set the hostname (replace
<NEW_HOSTNAME>with the name you desire)sed -i "s/$HOSTNAME/<NEW_HOSTNAME>/g" /etc/hostname /etc/hosts -
Upgrade the system to the latest version of all packages by running:
apt update && apt -y upgrade -
Install WireGuard
apt install -y wireguard
-
Install iptables
apt install -y iptables
-
Create a directory to store the keys and set strict permissions
mkdir /etc/wireguard/keys chmod 700 /etc/wireguard/keys
-
Generate the server's private key by running the following command
wg genkey > /etc/wireguard/keys/private.key -
Use the output from the previous command to generate the server's public key
cat /etc/wireguard/keys/private.key | wg pubkey > /etc/wireguard/keys/public.key
-
Set strict permissions on key files
chmod 400 /etc/wireguard/keys/*.key -
Create a WireGuard config file
nano /etc/wireguard/wg0.conf
Add the content:
[Interface] # Configuration for the server # Set the IP subnet that will be used for the WireGuard network. # 10.222.0.1 - 10.222.0.255 is a memorable preset that is unlikely to conflict. Address = 10.222.0.1/24 # The port that will be used to listen to connections. 51820 is the default ListenPort = 51820 # The output of `wg genkey` for the server. PrivateKey = <SERVER_PRIVATE_KEY> # Set DNS resolver to our VPN client, preventing DNS leaks. DNS = 10.222.0.2 # Set MTU MTU = 1420 # Enable ip forwarding in all interfaces PreUp = sysctl -w net.ipv4.ip_forward=1 # Allowing any traffic from <LAN_NETWORK_INTERFACE> (internal) to go over %i (tunnel): PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o <LAN_NETWORK_INTERFACE> -j MASQUERADE PostUp = iptables -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o <LAN_NETWORK_INTERFACE> -j MASQUERADE PostDown = sysctl -w net.ipv4.ip_forward=0 [Peer] # Configuration for the server's client Peer # The output of `echo "client private key" > wg pubkey`. PublicKey = <CLIENT_PUBLIC_KEY> # The IP address that this client is allowed to use. AllowedIPs = 0.0.0.0/0 # Ensures that your home router does not kill the tunnel, by sending a ping # every 25 seconds. PersistentKeepalive = 25Pay attention to the
<CLIENT_PUBLIC_KEY>because we still don't have this. Caution: don't use the one from the server.Replace
<LAN_NETWORK_INTERFACE>for the name of the interface where the server is connected. On the NanoPi R2S,end0is the WAN port andlan0is the LAN port, set the one you're using.
-
Create a directory to store the keys and set strict permissions
mkdir /etc/wireguard/keys chmod 700 /etc/wireguard/keys
-
Generate client's private key
wg genkey > /etc/wireguard/keys/private.key -
Use the output from the previous command to generate the client's public key
cat /etc/wireguard/keys/private.key | wg pubkey > /etc/wireguard/keys/public.key
At this point, you can take the content of the client's public key and add it to the server's WireGuard config on the previous section.
-
Set strict permissions on key files
chmod 400 /etc/wireguard/keys/*.key -
Create a WireGuard config file
nano /etc/wireguard/wg0.conf
Add the content:
[Interface] # Configuration for the client # The IP address that this client will have on the WireGuard network. Address = 10.222.0.2/24,<LOCAL_IP_NET> # (Such as 192.168.1.0/24) # Set MTU MTU = 1492 # The private key you generated for the client previously. PrivateKey = <CLIENT_PRIVATE_KEY> # Enable traffic to be passed from the server network to the private subnet of the client PreUp = sysctl -w net.ipv4.ip_forward=1 PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o <LAN_NETWORK_INTERFACE> -j MASQUERADE PostUp = iptables -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o <LAN_NETWORK_INTERFACE> -j MASQUERADE PostDown = sysctl -w net.ipv4.ip_forward=0 [Peer] # Configuration for the server to connect to # The public key you generated for the server previously. PublicKey = <SERVER_PUBLIC_KEY> # The WireGuard server to connect to. Endpoint = <SERVER_PUBLIC_ENDPOINT>:<SERVER_PUBLIC_PORT> # The subnet this WireGuard VPN is in control of. AllowedIPs = 0.0.0.0/0 # Ensures that your home router does not kill the tunnel, by sending a ping # every 25 seconds. PersistentKeepalive = 25Replace the following values:
<SERVER_PUBLIC_KEY>with the public key generated in the server machine.<SERVER_PUBLIC_ENDPOINT>with the public DNS name of the WireGuard server.<SERVER_PUBLIC_PORT>with the port exposed on the server network.<LAN_NETWORK_INTERFACE>for the name of the interface where the server is connected. On the NanoPi R2S,end0is the WAN port andlan0is the LAN port, set the one you're using. -
Now that we've set up both server and client we can start WireGuard on both machines:
wg-quick up wg0
You should see an output like:
[#] ip link add wg0 type wireguard [#] wg setconf wg0 /dev/fd/63 [#] ip -4 address add 10.222.0.1/24 dev wg0 [#] ip link set mtu 1420 up dev wg0
In case you see an error like the following, please reboot your NanoPi by running
reboot:[#] ip link add wg0 type wireguard Error: Unknown device type. Unable to access interface: Protocol not supported [#] ip link delete dev wg0 Cannot find device "wg0"
-
Check WireGuard interface
# wg show interface: wg0 public key: <SERVER_PUBLIC_KEY> private key: (hidden) listening port: 51820 peer: <CLIENT_PUBLIC_KEY> endpoint: <CLIENT_IP>:36010 allowed ips: 10.222.0.2/32 latest handshake: 32 seconds ago transfer: 732 B received, 500 B sent persistent keepalive: every 25 seconds
If you don't have any
peerdefinition means that the tunnel didn't work.At this point, you should be able to bring up both Wireguard interfaces and ping across both ends by:
Running this in both ends:
wg-quick up wg0
And then try to ping the other host
ping <REMOTE_WG_IP>
-
Enable SystemD interface
To make sure that systemd creates the interface every time the system starts, we have to enable it by:
# systemctl enable wg-quick@wg0 Created symlink /etc/systemd/system/multi-user.target.wants/wg-quick@wg0.service /lib/systemd/system/wg-quick@.service.
At this point, you should be able to ping the server from the client and through your new VPN.
TIP: In case we do changes in the WireGuard config and we want to apply them without interrupting the actual connection, run: wg syncconf wg0 <(wg-quick strip wg0)
A dynamic DNS server is useful when we can't have static IP addresses on the public network. This solution assumes that we don't have them and we actually don't need them because it is enough to have a dynamic DNS name set up to be good to go. I'm personally using YDNS, but there are hundreds of services available out there.
We have to run this in both boxes with different names (of course).
-
Installing curl
apt install -y curl
-
Get the YDNS updater
curl -o /usr/local/bin/updater.sh https://raw.githubusercontent.com/ydns/bash-updater/master/updater.sh
-
Give it execution permissions
chmod +x /usr/local/bin/updater.sh
-
Edit the file and set your information
# nano /usr/local/bin/updater.sh [...] YDNS_USER="<EMAIL>" YDNS_PASSWD="<SECRET>" YDNS_HOST="<HOST>" # This have to be different on both boxes [...]
-
Add the script as a PreUp condition for WireGuard config
nano /etc/wireguard/wg0.conf
Add the following content inside the
[Interface]sectionWith the content:
PreUp = /usr/local/bin/updater.sh -V
To test the network bandwidth between the two Wireguard nodes we will use iperf3. Meaning, we will need to install it on both, the Wireguard client and the server:
sudo apt install iperf3Once we have the tool installed we can proceed by running the following command on the Wireguard client to listed to iperf requests:
iperf3 -sAnd on the Wireguard server we run the iperf client
iperf3 -c 10.222.0.2We should see on both sides something like:
Connecting to host 10.222.0.2, port 5201
[ 5] local 10.222.0.1 port 46702 connected to 10.222.0.2 port 5201
[ ID] Interval Transfer Bitrate Retr Cwnd
[ 5] 0.00-1.00 sec 16.8 MBytes 140 Mbits/sec 0 7.24 MBytes
[ 5] 1.00-2.00 sec 19.5 MBytes 164 Mbits/sec 0 7.30 MBytes
[ 5] 2.00-3.00 sec 21.1 MBytes 177 Mbits/sec 0 7.33 MBytes
[ 5] 3.00-4.00 sec 19.4 MBytes 163 Mbits/sec 0 7.33 MBytes
[ 5] 4.00-5.00 sec 20.9 MBytes 175 Mbits/sec 0 7.33 MBytes
[ 5] 5.00-6.00 sec 19.5 MBytes 164 Mbits/sec 0 7.33 MBytes
[ 5] 6.00-7.00 sec 20.8 MBytes 174 Mbits/sec 0 7.33 MBytes
[ 5] 7.00-8.00 sec 20.6 MBytes 173 Mbits/sec 0 7.33 MBytes
[ 5] 8.00-9.00 sec 19.5 MBytes 164 Mbits/sec 0 7.33 MBytes
[ 5] 9.00-10.00 sec 20.9 MBytes 175 Mbits/sec 0 7.33 MBytes
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval Transfer Bitrate Retr
[ 5] 0.00-10.00 sec 199 MBytes 167 Mbits/sec 0 sender
[ 5] 0.00-10.17 sec 198 MBytes 163 Mbits/sec receiver
iperf Done.A watchdog is an electronic timer used for monitoring hardware and software functionality. The software uses a watchdog timer to detect and recover fatal failures.
We use a watchdog to make sure we have a functional VPN. If a problem comes up, the computer should be able to recover itself back to a functional state. We will configure the board to reboot if WireGuard link is down for too long, or a specific process isn’t running anymore.
-
Install the watchdog software
apt install watchdog
-
Configure the watchdog to monitor WireGuard network
nano /etc/watchdog.conf
Edit the following lines:
log-dir = /var/log.hdd/watchdog interface = wg0 ping = <REMOTE_WG_IP> retry-timeout = 300 interval = 30 -
Enable and start the service
systemctl stop watchdog systemctl enable watchdog systemctl start watchdog
Security updates are crucial to keep our system safe from threats. Even tho we don't have so many services open to the world, one bug is enough to allow attackers to break into our system.
apt install -y unattended-upgradesThe default set up of this package installs security updates for the current release. If you want to update all packages when available, take a look at the /etc/apt/apt.conf.d/50unattended-upgrades.
To test the package behavior, we can run:
unattended-upgrade --debug --dry-runNanopi has a very limited amount of store and it's preferable to send logs to a remote log server.
The setup of a remote log server is out of the scope of this how-to but there are plenty of good documentation out there. We assume that you already have a server ready.
Edit /etc/rsyslog.conf and add this at the beginning of the file:
*.- @@<LOG_SERVER_IP>:514The two @@ symbols indicates the usage of TCP protocol. Use only one symbol in case you need UDP.
Armbian in NanoPi has the logs located in two directories. The first is a ramdisk (/var/log/) which is usually around 50MB size. This is definitely not enough to keep our logs for more than a week, and depending on how much connection we have a day will not even hold 24h of logs before you start getting errors such as:
cannot write to log file '/var/log/xxx.log': No space left on deviceThe second one is located in the root partition (/var/log.hdd/).
The good practice here would be to save all logs in the disk, or at least safekeeping a compressed copy in the disk for security.
But if you're using this at home and you don't care much about them apart from realtime debugging when errors happen, then you can basically discard all logs after a day using logrotate :)
Let's start by increasing the /var/log ramdisk from 50MB to 100MB.
Edit /etc/default/armbian-ramlog and set SIZE to 100M.
apply the changes by running systemctl restart armbian-ramlog.service
Now, let's move to Logrotate. The main config file is located at /etc/logrotate.conf and then all sort of directory specific Logrotate definitions inside /etc/logrotate.d, let's first edit the default behaviour by:
nano /etc/logrotate.confReplace the content of the file for this:
# rotate log files daily
daily
# Old versions are removed
rotate 0
# create new (empty) log files after rotating old ones
create
# uncomment this if you want your log files compressed
compress
# packages drop log rotation information into this directory
include /etc/logrotate.d
Now let's see what we have inside /etc/logrotate.d/ directory:
ls /etc/logrotate.d/
alternatives apt armbian-hardware-monitor btmp chrony dpkg rsyslog wtmpAnd what I'm going to do here is delete everything and create a new config file called nanopi. So let's remove everything:
rm /etc/logrotate.d/*And now let's create the new config file at /etc/logrotate.d/nanopi with the following content:
/var/log.hdd/*.log /var/log.hdd/*/*.log {
daily
rotate 0
create
missingok
}
/var/log/*.log /var/log/*/*.log {
daily
rotate 0
create
missingok
}
What this config is going to do is rotate all log files in /var/log/ and /var/log.hdd as well their child directories.
This can be tested by:
logrotate -d /etc/logrotate.d/nanopiThe -d flag will list each log file it is considering to rotate.
As Logrotate is set up to run daily via CronD we don't have to do any further change.
These are just some good practices to hardening our SSH daemons, especially when they are publically available.
Add those lines somewhere inside the /etc/ssh/sshd_config file:
# Disable root login
PermitRootLogin no
# Disable password authentication
PasswordAuthentication noTo apply the previous config, just restart the SSH daemon:
systemctl restart ssh-
Disable unused SystemD services
systemctl stop wpa_supplicant systemd-rfkill.service systemd-rfkill.socket hostapd systemctl disable wpa_supplicant systemd-rfkill.service systemd-rfkill.socket hostapd
To build this guide, I've used several references, from blogs, other how-to and man pages.
