Self-Hosted VPN Options

 


As a follow-up to my 2021 test, I wanted to compare various VPN options in regards to bandwidth speed performance. My requirements: self-hosted Linux-based server/exit node (Debian 12, amd64, 1 Xeon Gold CPU @ 3.0 GHz, 1 GB RAM, 10 Gbps bandwidth [real-world average 353 Mbps]) and an Android client (Pixel 8, Android 16, always-on VPN, block connections without VPN). The Android device was 775 miles away from the Linux exit node to stress test the latency. Note: all tests were performed as root with no firewall -- in production you would want to apply appropriate security and control measures.

________________________________________________________________________

WireGuard + dsnet

apt install -y nano wget iptables wireguard qrencode

wget https://github.com/naggie/dsnet/releases/download/v0.8.1/dsnet-linux-amd64 -O /usr/local/bin/dsnet

chmod +x /usr/local/bin/dsnet

dsnet init

nano /etc/dsnetconfig.json

# Add "0.0.0.0/0" to the 'Networks' array and "8.8.8.8" as the 'DNS' setting

echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf

sysctl -p

NETDEV=$(ip -o route get 8.8.8.8 | cut -f 5 -d " ")

iptables -t nat -A POSTROUTING -o $NETDEV -j MASQUERADE

iptables -A FORWARD -i dsnet -o $NETDEV -j ACCEPT

iptables -A FORWARD -i $NETDEV -o dsnet -m state --state RELATED,ESTABLISHED -j ACCEPT 

dsnet up

dsnet add example | qrencode -t ansiutf8

# Android device: download WireGuard app from Play Store and scan QR code


Results:

VPN off: 580, 530, 560, 570, 550 = average 558 Mbps

VPN on: 87, 79, 98, 78, 62 = average 81 Mbps

________________________________________________________________________

HeadscaleTailscale

Warning: if you use the commercial Tailscale platform always run with "--no-logs-no-support", otherwise they monitor your traffic.

apt install -y wireguard-tools networkd-dispatcher ethtool

curl -sLO https://github.com/juanfont/headscale/releases/download/v0.26.1/headscale_0.26.1_linux_amd64.deb

apt install ./headscale*.deb

echo '{"acls":[{"action":"accept","src":["*"],"dst":["*:*"]}]}' > /etc/headscale/acl.hujson

chmod 644 /etc/headscale/acl.hujson

umask 077

wg genkey > /etc/headscale/private.key

systemctl enable headscale

nano /etc/headscale/config.yaml

# make these edits:

  • server_url: https://vpn.mydomain.com:443
  • listen_addr: 0.0.0.0:443
  • grpc_listen_addr: 0.0.0.0:50443
  • allocation: random
  • acme_email: "youremail@mydomain.com"
  • tls_letsencrypt_hostname: "vpn.mydomain.com"
  • policy > path: "/etc/headscale/acl.hujson"
  • base_domain: mydomain.com
  • magic_dns: false

systemctl restart headscale && sleep 10 && systemctl status headscale

headscale users create example

curl -fsSL https://tailscale.com/install.sh | sh

echo 'net.ipv4.ip_forward = 1' | tee -a /etc/sysctl.d/99-tailscale.conf

echo 'net.ipv6.conf.all.forwarding = 1' | tee -a /etc/sysctl.d/99-tailscale.conf

sysctl -p /etc/sysctl.d/99-tailscale.conf

printf '#!/bin/sh\n\nethtool -K %s rx-udp-gro-forwarding on rx-gro-list off \n' "$(ip route show 0/0 | cut -f5 -d" " | head -n1)" | tee /etc/networkd-dispatcher/routable.d/50-tailscale

chmod 755 /etc/networkd-dispatcher/routable.d/50-tailscale

/etc/networkd-dispatcher/routable.d/50-tailscale

tailscale up --advertise-exit-node --accept-dns=false --login-server=https://vpn.mydomain.com:443

# It will provide a URL for you to open. Click on it and copy the command to authenticate the request. Paste the command into your Headscale server terminal (changing USERNAME to the user you created in the step above).

headscale nodes approve-routes --identifier 1 --routes 0.0.0.0/0

# Android device: install Tailscale from the Google Play Store, open it. Tap "Get Started". Tap "OK" at the VPN prompt. At the screen with the "Log in" button, quickly tap the gear icon in the top-right corner. (If you wait too long and it redirects you to a login page with various SSO options, tap the back button to go back to the prior screen). On the Settings page, tap Accounts. On the Accounts page, tap the three vertical button icon in the top-right corner and select 'Use an alternate server'. Type https://vpn.mydomain.com and then tap "Add account". Copy the resulting command shown to your Headscale server to authenticate. Once you get the Node yournode registered message, go back to the mobile app. In the EXIT NODE dropdown, select the exit node you created above. It should now appear in blue at the top of the Tailscale app screen and you should now be connected!


Results:

VPN off: 550, 600, 590, 620, 570 = average 586 Mbps

VPN on: 86, 90, 78, 110, 94 = average 92 Mbps

________________________________________________________________________

OpenConnect ocserv (or AnyLink) + Cisco Secure Client (AnyConnect)

apt install -y iptables-persistent certbot ocserv

tee -a /etc/sysctl.conf <<EOF

# Enable routing

net.ipv4.ip_forward = 1

# Protect from IP Spoofing  

net.ipv4.conf.all.rp_filter = 1

net.ipv4.conf.default.rp_filter = 1

# Ignore ICMP broadcast requests

net.ipv4.icmp_echo_ignore_broadcasts = 1

# Protect from bad icmp error messages

net.ipv4.icmp_ignore_bogus_error_responses = 1

# Disable source packet routing

net.ipv4.conf.all.accept_source_route = 0

net.ipv6.conf.all.accept_source_route = 0

net.ipv4.conf.default.accept_source_route = 0

net.ipv6.conf.default.accept_source_route = 0

# Block SYN attacks

net.ipv4.tcp_syncookies = 1

net.ipv4.tcp_max_syn_backlog = 2048

net.ipv4.tcp_synack_retries = 2

net.ipv4.tcp_syn_retries = 5

# Log Martians  

net.ipv4.conf.all.log_martians = 1

net.ipv4.icmp_ignore_bogus_error_responses = 1

# Ignore send redirects

net.ipv4.conf.all.send_redirects = 0

net.ipv4.conf.default.send_redirects = 0

# Ignore ICMP redirects

net.ipv4.conf.all.accept_redirects = 0

net.ipv6.conf.all.accept_redirects = 0

net.ipv4.conf.default.accept_redirects = 0

net.ipv6.conf.default.accept_redirects = 0

net.ipv4.conf.all.secure_redirects = 0

net.ipv4.conf.default.secure_redirects = 0

EOF

sysctl -p

iptables -t nat -A POSTROUTING -j MASQUERADE

iptables-save > /etc/iptables/rules.v4

mkdir /root/certificates
chmod 700 /root/certificates
cd /root/certificates

tee ca.template <<EOF
organization = OID-6eec9b9f
cn = Example CA
serial = `date "+%s"`
expiration_days = -1
ca
signing_key
cert_signing_key
crl_signing_key
EOF

sleep 1

tee example.template <<EOF
organization = OID-6eec9b9f
cn = Example Router
dns_name = vpn.mydomain.com
ip_address = $(curl -4 ifconfig.me)
serial = `date "+%s"`
expiration_days = -1
signing_key
encryption_key
tls_www_server
EOF

certtool --generate-privkey --outfile ca-privkey.pem
certtool --generate-self-signed --load-privkey ca-privkey.pem --template ca.template --outfile ca-certificate.pem

certtool --generate-privkey --outfile example-privkey.pem
certtool --generate-certificate --load-privkey example-privkey.pem --load-ca-certificate ca-certificate.pem --load-ca-privkey ca-privkey.pem --template example.template --outfile example-certificate.pem

mkdir -p /etc/ocserv/
cp ca-certificate.pem example-certificate.pem example-privkey.pem /etc/ocserv/

tee /etc/ocserv/ocserv.conf <<EOF
# ocserv config options: https://ocserv.gitlab.io/www/manual.html#files

auth = "certificate"
tcp-port = 443
udp-port = 443
run-as-user = nobody
run-as-group = daemon
use-occtl = true
isolate-workers = false
socket-file = /run/ocserv-socket
server-cert = /etc/ocserv/example-certificate.pem
server-key = /etc/ocserv/example-privkey.pem
ca-cert = /etc/ocserv/ca-certificate.pem

# read user id from cert
cert-user-oid = 0.9.2342.19200300.100.1.1

# read organization from cert
cert-group-oid = 2.5.4.11

# log level
#   0 default (same as basic)
#   1 basic
#   2 info
#   3 debug
#   4 http
#   8 sensitive
#   9 TLS
log-level = 1

ipv4-network = 10.20.30.0/24  
ipv4-netmask = 255.255.255.0

# set router as internet gateway
default-domain = vpn.mydomain.com
predictable-ips = true
route = default
restrict-user-to-routes = true
tunnel-all-dns = true
dns = 8.8.8.8

# tun name
device = example

# gnutls priority string: https://gnutls.org/manual/html_node/Priority-Strings.html
tls-priorities = "SECURE128:+SECURE192:-VERS-SSL3.0:-VERS-TLS1.0:-VERS-TLS1.1:-DHE-RSA"

# DoS protection
min-reauth-time = 300
max-ban-score = 80
ban-reset-time = 1200
cookie-timeout = 300
deny-roaming = true
rekey-time = 172800
rekey-method = ssl

# prevent legacy CISCO clients and openconnect clients < 7.08
cisco-client-compat = false
dtls-legacy = false
client-bypass-protocol = false

# other various settings
auth-timeout = 240
idle-timeout = 1200
session-timeout = 86400
mobile-idle-timeout = 2400
rate-limit-ms = 100
stats-report-time = 360
server-stats-reset-time = 604800
keepalive = 32400
dpd = 90
mobile-dpd = 1800
switch-to-tcp-timeout = 25

# unused settings
#mtu = 1420
#max-clients = 50
#max-same-clients = 2

EOF

systemctl start ocserv.service
systemctl status ocserv.service
systemctl enable ocserv.service

ocpasswd -c /etc/ocserv/ocpasswd jdoe <<< `openssl rand -base64 47`

certtool --generate-privkey --outfile jdoe-privkey.pem

tee jdoe.template <<EOF
organization = OID-6eec9b9f
cn = John Doe
uid = jdoe

tls_www_client
signing_key
encryption_key

# Apple cert expiration max is 397: https://support.apple.com/en-us/HT211025
expiration_days = 397
EOF

certtool --generate-certificate --load-privkey jdoe-privkey.pem --load-ca-certificate ca-certificate.pem --load-ca-privkey ca-privkey.pem --template jdoe.template --outfile jdoe-certificate.pem

# iOS supported ciphers: AES128, DES, 3DES, CAST, RC4 
# https://opensource.apple.com/source/CommonCrypto/CommonCrypto-36064/CommonCrypto/CommonCryptor.h

# Cisco client supported ciphers: AES, 3DES, MD5, SHA1
# https://www.cisco.com/c/en/us/td/docs/iosxr/ncs5500/security/72x/b-system-security-cg-ncs5500-72x/implementing-secure-shell.html#concept_79A76470F2C24487844E3B42F0B7B1B7

# Triple DES is deprecated for all new applications and usage is disallowed after 2023
# https://www.cryptomathic.com/news-events/blog/3des-is-officially-being-retired

# ...so we'll use aes-128

certtool --to-p12 --load-privkey jdoe-privkey.pem --load-certificate jdoe-certificate.pem --pkcs-cipher aes-128 --outfile jdoe.p12 --outder --password=YOURCERTPASSWORDHERE --p12-name=jdoe

# Copy the jdoe.p12 file to the Android device. Install the Cisco Secure Client-AnyConnect from the Play Store and open it. Tap the three dots in the top-right and choose 'Settings' then uncheck 'Block Untrusted Servers'. Go back to the main screen and tap on 'Connections' then the plus sign (+). Add a description, then the server address vpn.mydomain.com. Tap on 'Advanced Preferences' then 'Certificate' then the blue 'Import' button at the bottom then 'File' then select the jdoe.p12 certificate. After the certificate is imported into the Cisco app storage, tap on it in the list to select it, then tap 'Done' twice and the back button to get to the main app screen. Tap the 'AnyConnect VPN' slider to enable the VPN.


Result: I couldn't get this to work with the Cisco Secure Client 5.1.9.115 in the Play Store

________________________________________________________________________

OpenZiti

echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf

sysctl -p

apt install -y jq

export ZITI_NETWORK="example"

export ZITI_CTRL_EDGE_ADVERTISED_ADDRESS=vpn.mydomain.com

export ZITI_CTRL_EDGE_ADVERTISED_PORT=8800

export ZITI_ROUTER_ADVERTISED_ADDRESS=vpn.mydomain.com

export ZITI_ROUTER_PORT=9090

source /dev/stdin <<< "$(wget -qO- https://get.openziti.io/ziti-cli-functions.sh)"; expressInstall

startController

startRouter

zitiLogin

mv /root/.ziti/quickstart/example/ziti-bin/ziti-v*/ziti /usr/local/bin/

ziti edge create identity "NewUser" -o NewUser.jwt

# Android device: copy the NewUser.jwt file to the device and then install the 'Ziti Mobile Edge' app from the Play Store and open. Tap the 'Add Identity' icon in the top-right corner and choose 'Select JWT File' and locate the NewUser.jwt file.


Result: Says connected but no Internet access on the Android device.

________________________________________________________________________


echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf

sysctl -p

apt install -y jq

curl -fsSL https://get.docker.com | sh

export NETBIRD_DOMAIN=vpn.mydomain.com; curl -fsSL https://github.com/netbirdio/netbird/releases/latest/download/getting-started-with-zitadel.sh | bash

# Follow the instructions from the output of the prior command to login to the NetBird portal and change the default admin password

curl -fsSL https://pkgs.netbird.io/install.sh | sh

netbird up --management-url https://vpn.mydomain.com

# Click the three dots to the right of the newly added peer and choose 'Set Up Exit Node' then in the groups dropdown choose All.

Android device: install the NetBird app from the Play Store and open. Tap the three-bar icon in the top-left corner and choose 'Change Server' (tap Yes when prompted for confirmation). In the Server textbox set https://vpn.mydomain.com:443   Once verified, you can tap the big grey circle in the middle of the app to connect. If you get an error, disable Always-On VPN and then try again and you should be prompted to login with your admin credentials. After you authenticate you can restore the Always-On VPN settings.


Results:

VPN off: 600, 550, 590, 630, 550 = average 584 Mbps

VPN on: 79, 89, 91, 35, 64 = average 72 Mbps

________________________________________________________________________


echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
sysctl -p

iptables -I INPUT -j ACCEPT

SB_IMAGE=oreoluwa/shadowbox:daily sudo --preserve-env bash -c "$(wget -qO- https://raw.githubusercontent.com/EricQmore/installer/main/install_server.sh)" install_server.sh

# on desktop:
#   https://s3.amazonaws.com/outline-releases/manager/linux/stable/Outline-Manager.AppImage
#   ./Outline-Manager.AppImage --no-sandbox
#   click on "Set Up" on "Set up Outline anywhere tile
#   paste code from Oracle instance terminal
#   click on the share icon on one of the keys in the Access Keys list
#   download Outline VPN app from Play Store and paste access key into it to connect

Results:

VPN off: 610, 580, 610, 600, 590 = average 598 Mbps

VPN on: 15, 12, 15, 31, 15 = average 18 Mbps

________________________________________________________________________


# add a Wildcard DNS record to your domain (i.e. an 'A' record of '*' with IP address of your server IP address)

wget -qO /root/nm-quick.sh https://raw.githubusercontent.com/gravitl/netmaker/master/scripts/nm-quick.sh && sudo chmod +x /root/nm-quick.sh && sudo /root/nm-quick.sh

# Select option 2 to use a custom domain and enter mydomain.com

# After the installation completes, you will be prompted to login to the dashboard with a provided URL. Create a login and verify the server is listed as a Gateway

Android device: install the Netmaker RAC app from the Play Store and open. Ensure the 'Self-hosted' box is highlighted and then enter server api.mydomain.com with the username and password you created in the previous step to login to the dashboard.


Result: throws a generic error message; if I use an IP address instead of the domain it says failure in SSL library

________________________________________________________________________


Result: their documentation says "You can host your own roots ("moons") in addition to ZeroTier's, but we can't provide support for removing ZeroTier's roots except to enterprise customers. The mobile apps don't currently support custom roots."

________________________________________________________________________


apt install -y iptables

echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
sysctl -p

iptables -t nat -A POSTROUTING -j MASQUERADE

useradd -m -s /bin/bash n3n
echo "n3n ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers

cd /tmp
wget https://github.com/n42n/n3n/releases/download/3.4.4/n3n_3.4.4-1_amd64.deb
apt install ./n3n_3.4.4-1_amd64.deb

systemctl start n3n-supernode && systemctl enable n3n-supernode && systemctl status n3n-supernode

tee /etc/n3n/examplenetwork.conf <<EOF
[community]
name=examplenetwork
key=examplepassword
supernode=$(curl -s -4 ifconfig.me):7654
EOF

sed -i 's/%i/%i -r/' /usr/lib/systemd/system/n3n-edge@.service

systemctl start n3n-edge@examplenetwork && systemctl enable n3n-edge@examplenetwork && systemctl status n3n-edge@examplenetwork

n3nctl -s examplenetwork supernodes
n3nctl -s examplenetwork edges

ip a
# Note the non-routable IP address of the 'examplenetwork' network interface (e.g. 10.1.2.3)

Android device: install the Hin2n apk file from GitHub and open. click + in top right, give VPN name, set N2N version to "v3", set supernode address to public IP of your server and port 7654, community is examplenetwork and encrypt key is examplepassword, and check 'Get IP from supernode'. Check the box 'More settings' and set the Gateway IP address to the 'ip a' value above and set a DNS server IP address (e.g. 8.8.8.8), then Save. Click on link icon to connect and it will say '[OK] edge'.



Results:

VPN off: 630, 600, 560, 630, 560 = average 596 Mbps

VPN on: 28, 35, 37, 18, 18 = average 27 Mbps


________________________________________________________________________


apt update
apt full-upgrade -y

reboot

apt install -y software-properties-common python3-launchpadlib gnupg2 linux-headers-$(uname -r) iptables qrencode openresolv

apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 57290828
echo "deb https://ppa.launchpadcontent.net/amnezia/ppa/ubuntu focal main" | tee -a /etc/apt/sources.list
echo "deb-src https://ppa.launchpadcontent.net/amnezia/ppa/ubuntu focal main" | tee -a /etc/apt/sources.list
apt update
apt install -y amneziawg

reboot

echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
sysctl -p

mkdir -p /etc/amnezia/amneziawg/clients

umask 077

awg genkey | tee /etc/amnezia/amneziawg/server.key | awg pubkey > /etc/amnezia/amneziawg/server.key.pub
awg genkey | tee /etc/amnezia/amneziawg/clients/mobile.key | awg pubkey | tee /etc/amnezia/amneziawg/clients/mobile.key.pub

# server

tee -a /etc/amnezia/amneziawg/wg0.conf <<EOF
[Interface]
PrivateKey = $(cat /etc/amnezia/amneziawg/server.key)
Address = 10.0.0.1/32
ListenPort = 51820
DNS = 8.8.8.8, 8.8.4.4
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
SaveConfig = true
Jc = 4
Jmin = 8
Jmax = 80
S1 = 25
S2 = 75
H1 = 123
H2 = 456
H3 = 789
H4 = 101112

[Peer]
PublicKey = $(cat /etc/amnezia/amneziawg/clients/mobile.key.pub)
AllowedIPs = 10.0.0.0/24
PersistentKeepalive = 25
EOF

awg-quick up wg0
systemctl enable awg-quick@wg0

# client

tee -a /etc/amnezia/amneziawg/clients/mobile.conf <<EOF
[Interface]
PrivateKey = $(cat /etc/amnezia/amneziawg/clients/mobile.key)
Address = 10.0.0.2/32
DNS = 8.8.8.8, 8.8.4.4
Jc = 4
Jmin = 8
Jmax = 80
S1 = 25
S2 = 75
H1 = 123
H2 = 456
H3 = 789
H4 = 101112

[Peer]
PublicKey = $(cat /etc/amnezia/amneziawg/server.key.pub)
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = $(curl -s -4 ifconfig.me):51820
PersistentKeepalive = 25
EOF

qrencode -t ansiutf8 < /etc/amnezia/amneziawg/clients/mobile.conf

Android device: install the WG Tunnel app from the Play Store and open. Tap the plus (+) button to load the configuration via QR code. You should then be able to connect.


Results:

VPN off: 560, 630, 550, 600, 550 = average 578 Mbps

VPN on: 79, 89, 91, 35, 64 = average 72 Mbps


Note: AmneziaWG has their own app on the Play Store but I found it to be slower than WG Tunnel.

________________________________________________________________________

OpenVPN

wget https://git.io/vpn -O openvpn-install.sh && bash openvpn-install.sh

# select UDP mode

# transfer client ovpn file to Android device

# Android device: download OpenVPN Connect app from Play Store, open, and agree to terms. On the 'Upload File' tab, tap 'Browse' and select the ovpn file you downloaded in the prior step. Then tap 'Connect'.


Results:

VPN off: 610, 620, 590, 570, 580 = average 594 Mbps

VPN on: 58, 61, 69, 52, 62 = average 61 Mbps

________________________________________________________________________


wget https://get.vpnsetup.net -O vpn.sh && sh vpn.sh

# transfer vpnclient.sswan file to Android device

# Android device: download strongSwan VPN Client app from Play Store, open, and tap the three dots in the top-right corner and choose 'Import VPN profile' and select the vpnclient.sswan file in the prior step. Tap 'Import Certificate From VPN Profile', keep default of 'VPN & app user certificate' option and then tap 'OK' twice. Then a new screen will prompt to 'Choose certificate' and you'll be given the option of the newly imported certificate; choose it and tap 'Select' then tap 'Import' in the top-right corner. Finally, tap the newly added VPN option in the list and accept all the prompts. You should now be connected.


Results:

VPN off: 580, 610, 580, 640, 570 = average 596 Mbps

VPN on: 67, 68, 57, 68, 31 = average 58 Mbps

________________________________________________________________________

VpnHood

su -c "bash <( wget -qO- https://github.com/vpnhood/VpnHood.App.Server/releases/latest/download/VpnHoodServer-linux-x64.sh)"

/opt/VpnHoodServer/vhserver gen

# copy the '--- AccessKey ---' string that starts with 'vh://' to a secure location

# Android device: download the 'VpnHood! Client' app from Play Store (not the 'VpnHood! Connect' app) and open. Tap on 'Server' at the bottom then tap 'Add server' and paste the 'vh://....' value from the previous step. If you get an error while initializing, temporarily disable Always-on VPN in the Android settings and retry.


Results:

VPN off: 590, 540, 630, 590, 590 = average 588 Mbps

VPN on: 75, 77, 80, 63, 73 = average 74 Mbps

________________________________________________________________________

Brook

echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf

sysctl -p

bash <(curl https://bash.ooo/nami.sh)

nami install joker brook

joker brook server --listen :9999 --password hello

# Android device: download the Android APK file and install it. A free account via email is required (you can use an anonymous email service like TempMail). Once logged in, tap the plus (+) icon in the top-right corner. Enter the IP address of the server and the port the server is listening on (9999 in this example) as well as the password indicated in the server command above ('hello' in this example), then tap 'Add'. Tap 'Connect' and you should see an astronaut sitting on a rocket indicating the connection was successful.

Note: the app crashed once during my testing but otherwise seemed stable.


Results:

VPN off: 560, 570, 460, 460, 590 = average 528 Mbps

VPN on: 300, 282, 252, 298, 321 = average 291 Mbps


Comments

Popular Posts