How to build your own VPN in 2026, for free or for a few euros per month
How to build your own VPN in 2026, for free or for a few euros per month

[!NOTE] Prices and Free Tier terms were checked on September 22, 2026, and may change.

What do you actually need before typing the first command?

Building your own VPN server in 2026 no longer requires days of kernel module compilation or wrestling with hundreds of lines of incomprehensible OpenVPN certificates. Still, there is no one-size-fits-all solution. Before renting a server or downloading software, define your main goal:

  1. Secure access to your home network while traveling, including a NAS, cameras, and smart home: You need a home VPN server or mesh overlay such as Tailscale or NetBird. It costs 0 EUR if you already have a device and internet connection, and your home devices remain invisible to the public internet.
  2. Protection on public Wi-Fi and a stable IP address outside your home country: A small VPS (Virtual Private Server) in a trusted European data center with WireGuard is enough. Expect to pay between 3.50 EUR and 6 EUR per month.
  3. Connecting several physical locations, such as an office, vacation home, and cloud servers: You need a site-to-site tunnel or WireGuard mesh architecture with centralized access policies (ACLs).
  4. Bypassing restrictive firewalls that block VPN protocols: Standard WireGuard may be detected and blocked on networks that actively filter its protocol signature with Deep Packet Inspection (DPI). AmneziaWG or an encrypted proxy such as Outline may help with this.

Clearly distinguish the categories of network tools

In modern use, the term "VPN" covers four different categories that are often confused:

  • Traditional VPN tunnels (WireGuard, OpenVPN, strongSwan/IKEv2): Point-to-point or client-server architecture. Traffic from your device is routed to a central server (gateway), which decrypts it and forwards it to the internet or a local network.
  • Mesh VPNs and private overlays (Tailscale, Headscale, NetBird, ZeroTier, Netmaker): Devices communicate directly peer to peer whenever network conditions allow, without routing through a central server. A coordination server helps exchange keys and traverse NAT.
  • Zero Trust Network Access (ZTNA) (Firezone, Defguard, Cloudflare Mesh): Access is granted to individual services and resources based on user identity, device, and context, including SSO and MFA policies, rather than to the entire network at the network layer.
  • Anti-DPI and censorship circumvention tools (AmneziaWG, Outline): Encrypted tunnels and proxies specifically adapted to disguise packet structure so traffic can pass through packet inspection on restrictive networks.

Privacy limits: your own VPN does not make you anonymous

One of the most common misconceptions is treating a private VPN server as complete anonymity on the internet.

  • Commercial VPN services: Hundreds or thousands of users share the same public IP addresses at the same time. Your traffic is mixed with other people's traffic, which can make external correlation harder, but you must place complete trust in the company operating the server and its integrity.
  • Your own VPS: You are the only customer using that public IP address. The hosting provider, such as Hetzner, OVH, Scaleway, or DigitalOcean, has your name, payment card details, and the exact time your instance was allocated. When you visit websites through your VPS, your traffic is protected from your local internet service provider (ISP) and snooping on public Wi-Fi. However, the destination website sees an IP address clearly associated with your hosting account.
  • A home VPN: Your outgoing IP address is the one assigned to your home connection. Wherever you are in the world, websites see you as if you were sitting in your living room.

Your own VPN protects data in transit. It can protect DNS traffic from the local network if the client is configured correctly to send queries through the tunnel, lets you work safely on untrusted networks, and gives you control over network routing. It does not guarantee anonymity, prevent identification through browser fingerprinting or logged-in accounts, or protect you from malicious code downloaded from the internet.

Data center IP address vs. residential IP address

Cloud servers use address ranges registered to hosting companies, identified by the provider's Autonomous System Number (ASN), such as Hetzner, OVH, or AWS. Many services, including banks, streaming platforms, anti-fraud systems, and ticketing websites, recognize these ranges automatically. They may block access or request additional checks because ordinary users do not browse from data center servers.

By contrast, a home residential IP address may reduce problems with services that treat IP ranges belonging to known hosting and data center providers more strictly. It does not guarantee access or bypass those services' security or geographic policies.

Technically, WireGuard can carry any network protocol, including P2P and BitTorrent. However, torrent traffic through a rented VPS is subject to the selected hosting provider's Acceptable Use Policy (AUP) and Terms of Service (ToS). Most cloud providers automatically forward DMCA copyright complaints to the account holder or suspend an instance following network abuse. Your own VPN does not change your legal obligations or responsibility to the service provider.


Free cloud options and their technical pitfalls

If you do not want to use home hardware, cloud providers offer free resource tiers. Each has clear technical limits that directly affect how useful it is as a VPN.

ProviderResources offeredPublic IPv4 addressOutbound traffic (egress)Suitability for VPN
Oracle Cloud Always FreeUp to 2 x E2.1.Micro or Ampere A1 instances, up to 1,500 OCPU hours and 9,000 GB hours monthly, equivalent to 2 OCPU and 12 GB RAM1 public IPv4 address included10 TB per monthHigh, with a risk of capacity shortages in the region and rules for shutting down inactive resources
Google Cloud Free Tier1 x e2-micro instance in supported US regions: Oregon, Iowa, and South CarolinaAbout $3.65 per month. Standard VMs are charged $0.005 per hour for a static or ephemeral IPv4 address. Only 1 hour per month is included30 GB disk; 1 GB of outbound traffic per month to supported destinationsNot truly 0 EUR for an IPv4 VPN. Compute is free, but a normal IPv4 VPN has about $3.65 in monthly IP costs, plus a 1 GB transfer limit
AWS Free Tier for new accounts$100 in initial credits plus up to $100 more; Free account plan for up to 6 monthsStandard rate is $0.005 per hour, about $3.65 per month. During the Free plan, the cost is covered by credits100 GB per monthTemporarily free while the allocated credits last

Important notes about free cloud resources

  • Oracle Cloud Always Free: Under the official rules, instances Oracle classifies as inactive, for example with average CPU use below 20%, low network traffic, and minimal disk activity over an extended period, may be marked for shutdown and deletion. Keep regular backups of your configuration and account for these criteria. Creating Ampere A1 instances in popular European regions may also fail because the data center has no physical capacity available.
  • Google Cloud Platform (GCP): The e2-micro instance provides free compute in certain US regions, but external IPv4 on a standard virtual machine is no longer free. It costs $0.005 per hour, about $3.65 per month, and the Free Tier covers only 1 hour per month. The additional limit of only 1 GB of outbound traffic from North America makes GCP impractical for a full-tunnel VPN unless you are prepared for extra charges. External IPv6 is free, but a standard IPv4 VPN on Google Cloud is not completely free.
  • Amazon Web Services (AWS): For new accounts, AWS uses a credit-based signup model, with $100 initially, the possibility of an additional $100, and a Free account plan lasting up to 6 months. Public IPv4 is normally charged at $0.005 per hour, about $3.65 per month. During the promotional credit period, this cost is deducted from the remaining account balance, so no direct charge occurs until the credits expire.

Review and comparison of commercial VPS providers

When free options are not acceptable because of stability or location, a VPS gives you full control and predictable network performance.

[!IMPORTANT] The table shows the actual monthly cost of a complete server, including CPU, memory, disk, and a public IPv4 address, checked on September 22, 2026. Prices are before VAT unless stated otherwise.

ProviderPlanCPU / RAM / DiskPublic IPv4 addressTraffic and network portActual monthly priceMain advantage
OVHcloudVPS-12 vCore / 4 GB / 40 GB NVMeIncluded (1 x IPv4 + /128 IPv6)Unlimited (500 Mbps port)3.81 EUR / monthBest balance of resources and price; unlimited traffic
Hetzner CloudCX23 (x86)2 vCPU / 4 GB / 40 GB NVMeExtra 0.50 EUR / month20 TB on a 1 Gbps port5.99 EUR / month (5.49 EUR + 0.50 EUR)Highly reliable network, data centers in Germany and Finland
ScalewayStardust1 vCPU / 1 GB / 10 GB local SSDExtra about 2.92 EUR / month (0.004 EUR/h)Unlimited on a 100 Mbps portAbout 4.31 EUR / month (combined)Flexible, but you need to add compute, IP, and storage costs
DigitalOceanDroplet Basic1 vCPU / 512 MB / 10 GB SSDIncluded500 GB on a 1 Gbps port$4.00 / monthSimple platform, clear control panel, and API
Akamai (Linode)Nanode 1GB1 vCPU / 1 GB / 25 GB SSDIncluded1 TB on a 1 Gbps port$5.00 / monthStable prices and global presence
VultrCloud Compute1 vCPU / 512 MB / 10 GB SSDIPv4 included (or $2.50 for IPv6 only)500 GB transfer$3.50 / monthGlobal network, with a $2.50/month IPv6-only plan

Cost breakdown (example: Scaleway Stardust)

Some providers advertise a CPU price of only about 0.44 EUR per month, with STARDUST1-S at 0.0006 EUR/h. However, once you add a Flexible IPv4 address at 0.004 EUR/h, about 2.92 EUR per month, and a minimum 10 GB local SSD at about 0.0013 EUR/h, or 0.95 EUR per month, the actual cost based on an average of 730 hours per month is:

text
0.0006 EUR/h (compute) + 0.004 EUR/h (IPv4) + 0.0013 EUR/h (storage) = 0.0059 EUR/h
0.0059 EUR/h x 730 h ≈ 4.31 EUR per month (before VAT)

Always calculate the combined cost of all server components instead of looking only at the advertised CPU price.


Home VPN server: a free option and the CGNAT challenge

Running a VPN server on home hardware, such as a mini PC, Raspberry Pi, older computer, or router running OpenWrt, lets you create a private network without additional hosting costs.

Advantages of a home VPN

  • No monthly hosting charge (0 EUR).
  • A residential IP address from your provider.
  • Direct access to local network resources such as a LAN, NAS, or home automation system.

Limitations of a home VPN

  • Asymmetric connection and upload speed: If your home internet plan provides 500/50 Mbps, download speed through the VPN while you are away is physically limited by the home's 50 Mbps upload speed, minus network protocol overhead. Symmetrical fiber connections perform much better.
  • Local reliability: A power outage or home internet outage stops the VPN.

Detecting and resolving CGNAT

Many providers, especially mobile carriers and some fixed-line ISPs, use CGNAT (Carrier-Grade NAT) because public IPv4 addresses are scarce. In this case, your router does not get a true public address. It receives a private address from the provider's address space instead.

To check whether you are behind CGNAT:

  1. Sign in to the router directly connected to your provider's service and check its WAN IP address.
  2. Open a public IP lookup service in a browser.
  3. If the router's WAN address is in the 100.64.0.0/10 range, from 100.64.0.1 to 100.127.255.254 according to RFC 6598, that is clear evidence that you are behind CGNAT. A difference between the router's WAN address and your public IP also indicates upstream NAT. Keep in mind that the difference can also be caused by a local double NAT, for example if your Wi-Fi router sits behind a separate provider modem.
text
                 STUN / endpoint discovery
                            ↓
               attempt a direct UDP connection
                            ↓
                       if it fails:
                    Peer Relay (GA 2026)
                            ↓
                    if unavailable:
                        DERP relay

How Tailscale and NetBird handle this in practice:

  1. STUN (Session Traversal Utilities for NAT): Used only to check the external IP address, assigned port, and NAT behavior. STUN does not carry user VPN traffic.
  2. Direct connection (P2P): Using the STUN data, the client and server attempt direct UDP communication through hole punching. If it works, traffic flows directly with minimal latency and full speed.
  3. Peer Relay (new in 2026): If direct UDP is not possible, for example because both ends are behind restrictive symmetric NAT, traffic can be forwarded through another node on your private network with better connectivity, avoiding central relays.
  4. DERP relay (Designated Encrypted Relay for Packets): The last line of defense. Traffic passes through encrypted relay servers. The relay sees only routing headers. The content remains encrypted with the clients' WireGuard keys.

When you are behind CGNAT, ordinary router port forwarding cannot accept incoming requests from the internet.

CGNAT workarounds

  1. Ask your provider for a static or public dynamic IP address. This may involve a small monthly fee.
  2. Use IPv6: If your provider offers a working IPv6 prefix, your server gets a public IPv6 address and can be reached directly from networks that also support IPv6.
  3. Use a WireGuard mesh system such as Tailscale or NetBird: STUN lets nodes check their network position, discover public ports, and attempt a direct connection. If that fails, Tailscale uses Peer Relay or DERP relays to reliably carry already-encrypted WireGuard traffic without opening ports.

Dynamic DNS (DDNS) and port forwarding for home WireGuard

If your home internet connection has a public dynamic IP address and you are not behind CGNAT, that address can change periodically when you restart the modem or the provider renews your session. If you put the current IP address in the client configuration, you will lose VPN access as soon as it changes.

The solution is to set up Dynamic DNS (DDNS):

text
[Home Server / Router] ──(Reports current IP periodically)──→ [DDNS Service (DuckDNS, Cloudflare, No-IP)]
                                                                            │
[Mobile Client] ──(Connects to vpn.yourdomain.rs:51820)─────────────────────┘

Practical setup steps

  1. Register a DDNS hostname: Register a free domain or subdomain, for example through DuckDNS, FreeDNS, or a Cloudflare API script. One example is vpn.yourdomain.duckdns.org.
  2. Configure an update client: On your home server or router, set up a service that checks your public IP address every 5 to 10 minutes and updates the DDNS record:
    bash
    # Example DuckDNS cron job
    */5 * * * * curl -s "https://www.duckdns.org/update?domains=YOUR_DOMAIN&token=YOUR_TOKEN&ip=" >/dev/null 2>&1
    
  3. Set up UDP port forwarding on the router: If the WireGuard server is on a separate device on your LAN, such as a Raspberry Pi at 192.168.1.50, rather than on the router itself, open the router's administration panel and forward:
    • Protocol: UDP
    • External port: 51820
    • Internal IP: 192.168.1.50
    • Internal port: 51820
  4. Configure the client profile: In the client's Endpoint field, enter the DDNS hostname instead of an IP address:
    ini
    [Peer]
    Endpoint = vpn.yourdomain.duckdns.org:51820
    
    When your provider changes your home IP address, the DDNS script updates the DNS record within a few minutes, and clients reconnect to the new address automatically.

Protocols and solutions: detailed technical review

WireGuard

The modern VPN industry standard, integrated into the Linux kernel since version 5.6. It uses proven cryptographic primitives: Curve25519 for key exchange, ChaCha20 for symmetric encryption, and Poly1305 for message authentication. A codebase of about 4,000 lines delivers high performance and low battery use on mobile devices, with reliable roaming between Wi-Fi and mobile networks.

  • Limitations: Fixed headers make it easier for DPI systems to identify. It has no built-in dynamic address assignment or user interface.

Tailscale

A mesh VPN platform based on WireGuard. It uses a central coordination server to exchange public keys, while data travels directly between machines whenever possible.

  • Personal plan: Free for up to 6 users, with unlimited devices per user, MagicDNS, Exit Nodes, and subnet routing.
  • Connection architecture: STUN is used only to discover public IP addresses and ports. Data uses the best available route: direct UDP → Peer Relay (generally available since February 2026) → DERP relay.

Headscale

An open source, self-hosted coordination server that works with official Tailscale clients. It provides full control over the control plane.

  • Important note about DERP relays: Headscale has an embedded DERP server, but it is disabled by default and the standard configuration uses the official Tailscale DERP map. For fully independent infrastructure with no contact with Tailscale servers, enable embedded DERP and remove the default external DERP URLs.

NetBird

An open source WireGuard mesh networking system with peer-to-peer connections, a central web panel, and flexible access policies.

  • Free cloud plan: Up to 5 users and 100 machines.
  • Self-hosted edition: The self-hosted version is free and has no user or device limit. However, it does not offer full feature parity with Cloud Enterprise. Advanced automation such as SCIM synchronization with identity providers, event streaming, and EDR/XDR security integrations is reserved for the commercial version.

ZeroTier

A system that simulates a virtual Ethernet switch, or Layer 2 network overlay. It can carry protocols that do not work exclusively through standard IP routing, such as mDNS and specialized industrial or LAN protocols.

  • Personal plan: Supports up to 10 devices for free. Configuring an Exit Node takes more steps than with WireGuard-based options.

AmneziaWG (version 3.1)

A variant of the WireGuard protocol designed to get around DPI blocking. It adds packet obfuscation parameters, including randomized headers, insertion of random-length packets, and variable packet size ranges, to make it harder for network providers to identify WireGuard.

OpenVPN (version 2.7)

A long-established solution with support for the Data Channel Offload (DCO) module in the Linux kernel, which has significantly improved performance.

  • Network transport: Supports both TCP and UDP. It can use TCP port 443 and share the port with a web server through port-share, which can help it pass through strict firewalls that block UDP. However, using port 443 does not make OpenVPN traffic identical to real HTTPS traffic, and advanced DPI systems can distinguish it. UDP remains the recommended choice under normal network conditions because it has lower latency.

strongSwan (IKEv2/IPsec, version 6.1.0)

An IPsec implementation for Unix-like operating systems. Its main advantage is built-in support in Windows, macOS, iOS, and Android, so installing an additional client is not required.

  • Version 6.1.0 status, released September 7, 2026: IKEv1 is disabled by default, which prevents the use of outdated cryptographic profiles. Support for post-quantum key exchange algorithms (ML-KEM) and hybrid mechanisms was introduced in the strongSwan 6.0.0 branch and continues in current releases.

Firezone and Defguard

  • Firezone: A WireGuard-based ZTNA platform that grants access to specific resources based on user identity instead of opening an entire subnet.
  • Defguard: A security solution for teams with integrated identity management, multi-factor authentication (MFA), and controlled WireGuard session lifetimes.

Outline VPN

Developed by Jigsaw, Outline is an encrypted proxy based on the Shadowsocks protocol, built primarily to bypass internet censorship. It uses access keys and is simple to run in Docker, but it is not a full Layer 3 VPN adapter for the entire operating system.

PiVPN and Algo VPN

  • PiVPN: The project went through an announced end-of-development phase and is now in basic community maintenance mode. For new production deployments, use plain WireGuard or Docker containers.
  • Algo VPN (Trail of Bits): A collection of Ansible scripts that quickly deploy secure WireGuard or IPsec servers on cloud providers. It is intended for technically advanced users.

Other relevant options to know

Cloudflare Mesh (currently in beta)

Cloudflare Mesh is a private Layer 3 and Layer 4 network overlay within the Cloudflare One Zero Trust ecosystem, available on the free plan as well.

  • Protocol: It uses the MASQUE protocol (Multiplexed Application Substrate over QUIC Encryption) to tunnel traffic over HTTP/3 and QUIC encryption.
  • Key architectural difference: Unlike Tailscale, where devices communicate directly peer to peer, all network traffic in Cloudflare Mesh always passes through Cloudflare's network infrastructure, where security policies are checked. It supports post-quantum encryption in transit.

Netmaker

Netmaker is an advanced WireGuard mesh platform built to connect large infrastructures, including multiple cloud providers, local data centers, and distributed servers. It provides a central control panel, dynamic route management, gateway node support, ACL rules, and integration with OAuth providers. Netmaker is unnecessary complexity for connecting a phone and one laptop, but it is a powerful tool for DevOps teams and homelab enthusiasts managing dozens of servers.

SoftEther VPN (version 4.44 Build 9807 RTM)

SoftEther VPN is a powerful multiprotocol VPN server developed at the University of Tsukuba in Japan. The current stable release is 4.44 Build 9807 RTM, with built-in protection against TunnelCrack traffic leaks. Its greatest strength is flexibility: one server can serve clients over OpenVPN, IPsec/L2TP, MS-SSTP, and its own SoftEther SSL-VPN protocol. SoftEther has its own TLS/SSL VPN mode designed specifically to pass through restrictive HTTP proxies and firewalls. However, its large codebase and more complex configuration make native WireGuard easier and faster to maintain for a new personal setup.


VPN directly on a router: OpenWrt, MikroTik, OPNsense, and pfSense

In many situations, the most efficient option is to install a WireGuard server directly on a home or office network gateway, such as a router or dedicated firewall.

text
[Internet] ──→ [Router / Firewall with WireGuard] ──→ [Local LAN devices]

Advantages of a router-based setup

  • No extra computer needed: You do not need a Raspberry Pi or mini PC that must stay on.
  • Optimal routing: The router already controls NAT and routing tables, so VPN packets can move directly to the local LAN without extra forwarding.

Supported platforms

  • OpenWrt: The current official OpenWrt server guide installs luci-proto-wireguard, which pulls in the necessary network protocols and LuCI web interface, and wireguard-tools. The optional qrencode package generates QR codes in a terminal.
  • MikroTik (RouterOS v7+): WireGuard has been built into RouterOS since version 7. Setup is quick in WinBox or the terminal: create a WireGuard interface, add a peer, and configure firewall filter rules.
  • OPNsense and pfSense: Both provide official WireGuard plugins with full integration into system policies, aliases, and firewall rules.

[!WARNING] Inexpensive plastic home routers with weak single-core or dual-core low-frequency MIPS/ARM processors can become a bottleneck when encrypting traffic above 100 to 200 Mbps. Never expose a router's administrative web interface on its WAN side.


Post-quantum cryptography in VPN systems (2026)

With practical implementations of post-quantum algorithms appearing, a common question is: "Is ordinary WireGuard safe from future quantum computers using a Harvest Now, Decrypt Later strategy?"

Technical facts:

  1. Standard WireGuard: Uses Curve25519 for asymmetric key exchange. Elliptic-curve cryptography is theoretically vulnerable to Shor's algorithm on a sufficiently powerful quantum computer. Therefore, ordinary WireGuard is not a post-quantum protocol by itself.
  2. WireGuard PresharedKey (an additional symmetric shield): WireGuard supports an optional symmetric pre-shared key (PresharedKey = ...). Under Grover's algorithm, 256-bit symmetric cryptography retains 128-bit security, providing effective post-quantum protection in transit. Creating a PSK for every peer is recommended.
  3. strongSwan 6.x (ML-KEM/Kyber): Full support has been added for IKEv2 hybrid key exchange, combining classical Diffie-Hellman or ECDH algorithms with the post-quantum standard ML-KEM, previously known as Kyber.
  4. Cloudflare Mesh PQ: Post-quantum cryptography is built into network traffic.

Network routing, DNS, and packet security

Full tunnel vs. split tunnel

The mode is defined by the AllowedIPs parameter in the client configuration:

ini
# FULL TUNNEL: All network traffic passes through the VPN server
AllowedIPs = 0.0.0.0/0, ::/0
  • Use case: Untrusted hotel or cafe Wi-Fi, or when you want the VPN server's address to replace your public IP address completely.
ini
# SPLIT TUNNEL: Only traffic to private subnets goes through the VPN
AllowedIPs = 10.66.66.0/24, 192.168.1.0/24
  • Use case: Access home or work servers while regular internet traffic continues through your local provider, without using VPN bandwidth.

DNS architecture and leak prevention

A common configuration mistake is to encrypt general traffic while the operating system continues sending DNS queries to the local router. Third parties can then still see the list of domains you visit.

  1. Model A (privacy and ad blocking): Client → WireGuard tunnel → AdGuard Home / Pi-hole (on the server) → Encrypted DNS (DoT / DoH to Quad9 or Cloudflare)
  2. Model B (independent recursive resolution): Client → WireGuard tunnel → AdGuard Home / Pi-hole → Unbound (recursive queries to root and TLD servers)

[!CAUTION] Do not expose port 53 (TCP/UDP) to the public internet! An unrestricted DNS service listening on a public IP address becomes an open resolver and can quickly be abused for large DNS amplification DDoS attacks. The DNS service should listen only on the VPN interface, for example 10.66.66.1, or inside a protected private subnet.

IPv6 strategy and the Linux Router Advertisement trap (accept_ra)

If a client's local network assigns a public IPv6 address but the VPN is configured only for IPv4, the operating system will use its local IPv6 connection for services that support it, bypassing the VPN.

Use a dual-stack tunnel for complete coverage. However, enabling IPv6 forwarding on a Linux server introduces an important networking pitfall:

[!WARNING] When you set net.ipv6.conf.all.forwarding = 1 in the kernel, Linux, by default behavior based on router RFC standards, stops accepting Router Advertisements (RA) on network interfaces. If your VPS provider assigns the IPv6 default gateway through SLAAC/RA, the server may lose its outbound IPv6 internet connection after you apply this setting.

To fix this, explicitly force the WAN interface to accept RA messages by setting accept_ra = 2:

text
net.ipv6.conf.all.forwarding = 1
net.ipv6.conf.default.forwarding = 1
net.ipv6.conf.WAN_INTERFACE.accept_ra = 2

After enabling this, always check ip -6 route to make sure that the default via ... route is still present.

Setting the MTU

An MTU of 1420 bytes is a common and reliable starting point for WireGuard over standard 1500-byte internet connections. It is not a universal fixed standard, though. If you do not explicitly set MTU in the configuration, wg-quick attempts to determine an optimal value automatically by reading the gateway MTU from the default route to the defined endpoint, then subtracting space for IPv4 or IPv6 and UDP/WireGuard headers.

If small pages load but services with long TLS certificates or large file transfers stall, for example on home PPPoE connections with an MTU of 1492 or on some mobile networks, explicitly set a lower MTU in the client profile:

ini
[Interface]
MTU = 1360

Kill switch mechanisms

A kill switch prevents unencrypted packets from reaching the network if the VPN tunnel suddenly disconnects.

Windows (WireGuard client)

The official Windows app uses the Windows Filtering Platform (WFP). When Block untunneled traffic (kill-switch) is enabled with AllowedIPs = 0.0.0.0/0, ::/0, the client installs /0 routes that send all traffic through the tunnel. WFP policies block all outbound traffic outside the tunnel interface, except the UDP packets sent to the configured server.

Android

Android has built-in platform-level support:

  1. Open Settings → Network & internet → VPN.
  2. Tap the gear icon next to the WireGuard or Tailscale profile.
  3. Enable Always-on VPN and Block connections without VPN.

The operating system then blocks outbound internet traffic at the kernel level whenever the tunnel is not active.

Apple (iOS and macOS)

  • A standard personal VPN profile on iOS does not have a universal system kill switch. Some system processes, such as Apple Push notifications, may communicate outside the VPN tunnel by design.
  • Full Always-On VPN on iOS is available only on MDM-supervised devices in Supervised mode.
  • On macOS, you can configure a reliable kill switch with the system pf firewall, dropping all traffic outside the utun interface.

Linux

The cleanest option is an nftables firewall configuration that drops all outbound traffic on the physical network interface unless the destination is the VPN server's public IP address and port.


Practical tutorial 1: Tailscale exit node at home (0 EUR)

Turn any home Linux computer, such as an older laptop, Raspberry Pi running Raspberry Pi OS, or Ubuntu machine, into an internet exit node for your devices. You do not need to open ports on the router, and it works even if you are behind CGNAT.

Step 1: Enable packet forwarding on the Linux server

Sign in to the server and enable packet forwarding in the kernel:

bash
sudo tee /etc/sysctl.d/99-tailscale.conf <<EOF
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
EOF

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

Step 2: Install the Tailscale package

Install the official Tailscale repository and client:

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

Step 3: Start the service and advertise routes

Start Tailscale and advertise the machine as an Exit Node and router for your local subnet, for example 192.168.1.0/24:

bash
sudo tailscale up --advertise-exit-node --advertise-routes=192.168.1.0/24

Open the link printed by the command and sign in to your account.

Step 4: Approve the settings in the admin panel

  1. Sign in at login.tailscale.com.
  2. Find your server under Machines.
  3. Click the three-dot menu (...) and choose Edit route settings.
  4. Confirm Use as exit node and select the advertised subnet, 192.168.1.0/24.
  5. Key expiration setting: In the same menu, you can select Disable key expiry. This prevents the registered device's node key from expiring automatically. That is convenient for unattended servers running for months, but increases risk if the server is compromised because the machine will not require reauthentication after the normal expiry period.

Step 5: Use it from a phone or laptop

  1. Install Tailscale on your phone or laptop and sign in with the same account.
  2. In the app, select Exit Node and choose your home server.
  3. Check your public IP address in a browser. You should see your home IP address.

Practical tutorial 2: VPS + wg-easy (version 15.4.0)

This tutorial uses the current stable release, wg-easy v15.4.0, on Ubuntu 24.04 LTS.

[!NOTE] These steps are for Ubuntu 24.04 LTS. If you use Debian 13 (Trixie) or Debian 12 (Bookworm), replace ubuntu with debian in the repository commands: https://download.docker.com/linux/debian.

Step 1: Install the official Docker Engine (Ubuntu 24.04 LTS)

Connect to the VPS:

bash
sudo apt update && sudo apt upgrade -y
sudo apt install -y ca-certificates curl gnupg lsb-release

# Add the official Docker GPG key and repository
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Step 2: Configure the compose.yaml file

The current v15 architecture in the official compose.yaml uses an isolated Docker network, kernel module bindings, and explicit network settings.

Create a working directory and file:

bash
mkdir -p ~/wg-easy && cd ~/wg-easy
nano compose.yaml

Add this verified v15.4.0 configuration, which binds the admin panel only to 127.0.0.1:

yaml
services:
  wg-easy:
    image: ghcr.io/wg-easy/wg-easy:15.4.0
    container_name: wg-easy
    environment:
      # Allow the web panel over HTTP behind a local SSH tunnel
      - INSECURE=true
      - PORT=51821
    volumes:
      - ./data:/etc/wireguard
      - /lib/modules:/lib/modules:ro
    ports:
      # UDP port for client traffic
      - "51820:51820/udp"
      # Security: bind the web UI only to 127.0.0.1 (localhost)
      - "127.0.0.1:51821:51821/tcp"
    restart: unless-stopped
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    sysctls:
      - net.ipv4.ip_forward=1
      - net.ipv4.conf.all.src_valid_mark=1
      - net.ipv6.conf.all.forwarding=1
      - net.ipv6.conf.all.disable_ipv6=0
    networks:
      wg-easy-network:
        ipv4_address: 10.8.0.2
        ipv6_address: fdcc:ad94:b242::2

networks:
  wg-easy-network:
    enable_ipv6: true
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 10.8.0.0/24
        - subnet: fdcc:ad94:b242::/64

Step 3: Start the container

bash
sudo docker compose up -d

Step 4: Connect securely through an SSH tunnel

Since port 51821 is not exposed to the public internet, open a terminal on your local computer and create an SSH tunnel:

bash
ssh -N -L 51821:127.0.0.1:51821 vpnadmin@YOUR_PUBLIC_VPS_IP

Open a browser on your computer and visit http://127.0.0.1:51821.

The app opens the Setup Wizard:

  1. Enter an admin password for the panel.
  2. In the Host field, enter your VPS public IP address or domain.
  3. Set the DNS resolver for clients, for example Quad9: 9.9.9.9.
  4. When setup is complete, sign in, create a client profile, and scan its QR code with the WireGuard mobile app.

Practical tutorial 3: native dual-stack WireGuard protected by nftables

For production environments where you want a clean system without Docker and full IPv4 and IPv6 (dual-stack) support.

[!IMPORTANT] IPv6 assumption: This example assumes your VPS has an assigned public IPv6 address and a working default IPv6 route to the internet. Check with ip -6 addr and ip -6 route. The private ULA range (fd42:42:42::/64) and NAT66 isolate clients, but require working IPv6 on the host. If your VPS has no IPv6, use only the IPv4 settings and set AllowedIPs = 0.0.0.0/0 on clients while disabling the IPv6 adapter to prevent leaks.

Parameters used in this tutorial:

  • IPv4 VPN subnet: 10.66.66.0/24 (server: 10.66.66.1, client: 10.66.66.2)
  • IPv6 VPN subnet (ULA range): fd42:42:42::/64 (server: fd42:42:42::1, client: fd42:42:42::2)
  • UDP port: 51820
  • Physical WAN interface: replace WAN_INTERFACE with the name of your adapter. Check with ip -br link, for example eth0 or ens3.

Good security practice is to generate the client's private key on the client device itself, not on the server. The server should never see or store a client's private key.

A) On the server:

bash
sudo apt update && sudo apt install -y wireguard wireguard-tools

umask 077
mkdir -p /etc/wireguard/keys
cd /etc/wireguard/keys

# Generate the server keys
wg genkey | tee server_private.key | wg pubkey > server_public.key

# Optional pre-shared key for additional protection
wg genpsk > preshared.key

B) On the client (laptop or phone):

  • On phones, the official WireGuard app generates its own private and public keys when you tap Create from scratch.
  • On a Linux or macOS client:
bash
umask 077
wg genkey | tee client_private.key | wg pubkey > client_public.key

Copy only the client's public key (client_public.key) to the server.

Step 2: Configure the server (/etc/wireguard/wg0.conf)

ini
[Interface]
Address = 10.66.66.1/24, fd42:42:42::1/64
ListenPort = 51820
PrivateKey = SERVER_PRIVATE_KEY_HERE

[Peer]
# Public key received from the client
PublicKey = CLIENT_PUBLIC_KEY_HERE
PresharedKey = PRESHARED_KEY_HERE
AllowedIPs = 10.66.66.2/32, fd42:42:42::2/128

Step 3: Enable IPv4 and IPv6 forwarding (with accept_ra protection)

bash
sudo tee /etc/sysctl.d/99-wireguard-dualstack.conf <<EOF
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
net.ipv6.conf.default.forwarding = 1
# Prevent loss of the IPv6 default route when forwarding is enabled
net.ipv6.conf.all.accept_ra = 2
net.ipv6.conf.default.accept_ra = 2
EOF

sudo sysctl -p /etc/sysctl.d/99-wireguard-dualstack.conf

Step 4: Configure the nftables firewall

[!WARNING] Before applying these rules: This example assumes a dedicated WireGuard VPS where nftables is the only central firewall. The flush ruleset command deletes all existing rules. Do not apply it blindly on a Docker host, a production server with existing rules, or a system you can access through only one SSH session. Before applying, check the syntax with nft -c -f /etc/nftables.conf, keep a backup SSH session open, and make sure you can reach your hosting provider's web console.

Edit /etc/nftables.conf:

nftables
#!/usr/sbin/nft -f

flush ruleset

table inet filter {
    chain input {
        type filter hook input priority filter; policy drop;

        # Immediately drop invalid packets
        ct state invalid drop

        # Allow loopback traffic
        iif "lo" accept

        # Established and related connections
        ct state established,related accept

        # ICMP and ICMPv6 diagnostics, robust against extension headers
        ip protocol icmp accept
        meta l4proto ipv6-icmp accept

        # SSH access
        tcp dport 22 accept

        # Incoming WireGuard port
        udp dport 51820 accept

        # NOTE: Clients use an external public DNS service, such as Quad9.
        # Add port 53 rules ONLY if you install AdGuard Home or Pi-hole locally on this VPS:
        # iifname "wg0" udp dport 53 accept
        # iifname "wg0" tcp dport 53 accept
    }

    chain forward {
        type filter hook forward priority filter; policy drop;

        ct state established,related accept

        # Forward traffic from the VPN tunnel to the WAN interface
        iifname "wg0" oifname "WAN_INTERFACE" accept
    }

    chain postrouting {
        type nat hook postrouting priority srcnat; policy accept;

        # Masquerade IPv4 and IPv6 traffic
        oifname "WAN_INTERFACE" ip saddr 10.66.66.0/24 masquerade
        oifname "WAN_INTERFACE" ip6 saddr fd42:42:42::/64 masquerade
    }
}

Test and enable the firewall:

bash
sudo nft -c -f /etc/nftables.conf
sudo systemctl enable --now nftables
sudo nft -f /etc/nftables.conf

Step 5: Start the WireGuard service

bash
sudo systemctl enable --now wg-quick@wg0

Step 6: Configure the client (client.conf)

Create the configuration on the client device. The client receives fixed host addresses /32 and /128 that match the server's AllowedIPs entry:

ini
[Interface]
Address = 10.66.66.2/32, fd42:42:42::2/128
PrivateKey = CLIENT_PRIVATE_KEY_HERE
DNS = 9.9.9.9, 149.112.112.112

[Peer]
PublicKey = SERVER_PUBLIC_KEY_HERE
PresharedKey = PRESHARED_KEY_HERE
Endpoint = YOUR_PUBLIC_VPS_IP:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25

Connecting locations: site-to-site tunnels and LAN routing

A site-to-site architecture allows devices at two separate physical locations to communicate transparently using their original private IP addresses.

text
Office A                                                     Office B
Local LAN: 192.168.10.0/24                                  Local LAN: 192.168.20.0/24
Gateway A: 10.200.0.1/30 <==== [ WireGuard Tunnel ] ====> Gateway B: 10.200.0.2/30
Public IP: 198.51.100.1                                     Public IP: 203.0.113.1

[!IMPORTANT] Critical routing rule: The subnets at the connected locations must not overlap. If both locations use 192.168.1.0/24, routing will not work.

IP forwarding alone on the WireGuard gateway is not enough. If the WireGuard gateway is not also the local network's default gateway, the home router at location A needs a static route: 192.168.20.0/24 via IP_GATEWAY_A and the router at location B needs: 192.168.10.0/24 via IP_GATEWAY_B Without these static routes, LAN devices will send packets for the remote network to their usual router, which will not know where that remote VPN range is.

Gateway A (/etc/wireguard/wg-site.conf)

ini
[Interface]
Address = 10.200.0.1/30
ListenPort = 51820
PrivateKey = GATEWAY_A_PRIVATE_KEY

[Peer]
PublicKey = GATEWAY_B_PUBLIC_KEY
Endpoint = 203.0.113.1:51820
AllowedIPs = 10.200.0.2/32, 192.168.20.0/24
PersistentKeepalive = 25

Gateway B (/etc/wireguard/wg-site.conf)

ini
[Interface]
Address = 10.200.0.2/30
ListenPort = 51820
PrivateKey = GATEWAY_B_PRIVATE_KEY

[Peer]
PublicKey = GATEWAY_A_PUBLIC_KEY
Endpoint = 198.51.100.1:51820
AllowedIPs = 10.200.0.1/32, 192.168.10.0/24
PersistentKeepalive = 25

In this scenario, you do not need NAT address translation. You only need to allow packet forwarding in the firewall between the local LAN interfaces and the wg-site adapter on both gateways.


High availability (HA) and multiple exit nodes

What if the VPN gateway fails?

Most individual users and small teams do not need complex enterprise protocols. You can handle availability at three levels:

  1. Individual user (two VPN profiles): This is the most robust and least expensive option. Configure two profiles on the client: Primary-VPS, for example Hetzner, and Backup-VPS, for example OVH. If the primary server becomes unavailable, activate the backup profile with one tap.
  2. Tailscale HA subnet routers: In a Tailscale network, you can set up two or more subnet routers at the same location, advertising the same network prefix, such as 192.168.1.0/24. Tailscale's coordination mechanism distributes traffic and redirects it to a remaining node if one stops responding.
  3. Local or data center HA (Keepalived/VRRP): For two servers on the same local Layer 2 network, or broadcast domain, Keepalived implements VRRP to maintain a virtual IP address (VIP). If the primary gateway fails, the secondary takes over the virtual IP within seconds. VRRP works only within the same network segment and cannot be used directly for failover between two different cloud providers.

Multiple exit nodes (multi-exit architecture)

One of the biggest advantages of a mesh architecture such as Tailscale or Headscale is the ability to define multiple exit points on the same private network:

  • HOME-NODE: A home mini PC provides your ISP's residential IP address while you are traveling.
  • VPS-GERMANY: A server in Germany provides high bandwidth for stable European internet traffic.
  • VPS-USA: A server in North America provides an exit IP address for access to specific US services.

In the client app, you simply choose which node to use for internet access. Communication with local homelab devices continues to work in the background.


Server hardening

SSH protection

Use cryptographic keys only and disable password login.

Generate a key on your local computer:

bash
ssh-keygen -t ed25519 -C "admin-laptop-2026"
ssh-copy-id -i ~/.ssh/id_ed25519.pub vpnadmin@YOUR_PUBLIC_VPS_IP

Server configuration (/etc/ssh/sshd_config.d/99-hardened.conf):

text
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
AllowUsers vpnadmin
MaxAuthTries 3

Check the syntax before restarting the service:

bash
sudo sshd -t

[!TIP] Keep the current SSH session open while you test logging in with the new key in another window.

Docker and the firewall

  • Control published ports: When Docker publishes ports, for example with -p 51821:51821, it modifies network tables and may bypass standard UFW rules. For this reason, always bind admin panels explicitly to loopback: -p 127.0.0.1:51821:51821.
  • Why iptables: false is not recommended: Disabling Docker's firewall management in /etc/docker/daemon.json breaks automatic container routing and DNS resolution in standard installations. Instead, filter traffic to containers through the official DOCKER-USER chain in the firewall or control the interfaces on which containers listen.

Automatic security updates

bash
sudo apt install -y unattended-upgrades update-notifier-common
sudo dpkg-reconfigure --priority=low unattended-upgrades
sudo systemctl status apt-daily-upgrade.timer

Check whether a system restart is required:

bash
test -f /var/run/reboot-required && echo "A REBOOT IS REQUIRED" || echo "System is up to date"

Automation and disaster recovery

Infrastructure as Code (IaC)

  • Terraform/OpenTofu: Create virtual machines, public IP addresses, DNS records, and external cloud firewall rules.
  • Cloud-Init: Configure a machine on first boot, including user accounts, SSH keys, and packages.
  • Ansible: Maintain configurations and distribute WireGuard keys and firewall rules.

Example: hardened cloud-init configuration

yaml
#cloud-config
package_update: true
package_upgrade: true

ssh_pwauth: false
disable_root: true

users:
  - name: vpnadmin
    gecos: VPN Administrator
    sudo: ALL=(ALL) NOPASSWD:ALL
    shell: /bin/bash
    lock_passwd: true
    ssh_authorized_keys:
      - ssh-ed25519 YOUR_ED25519_PUBLIC_KEY

packages:
  - wireguard
  - wireguard-tools
  - nftables
  - unattended-upgrades
  - curl
  - jq

write_files:
  - path: /etc/sysctl.d/99-ipforward.conf
    permissions: '0644'
    content: |
      net.ipv4.ip_forward = 1
      net.ipv6.conf.all.forwarding = 1
      net.ipv6.conf.all.accept_ra = 2

runcmd:
  - sysctl -p /etc/sysctl.d/99-ipforward.conf
  - systemctl enable --now unattended-upgrades
  - systemctl enable --now nftables

Disaster recovery strategy

  1. Encrypt backups: Configuration files such as /etc/wireguard/ and /etc/nftables.conf contain private keys and must be encrypted before being uploaded to backup storage.
  2. Use a DNS name instead of a fixed IP address: In client configurations, use an FQDN in the Endpoint field, for example vpn.yourdomain.app:51820, with a low TTL such as 300 seconds. If you migrate to a new VPS provider, change the DNS A/AAAA record instead of reconfiguring every client device.

Diagnostics, testing, and monitoring

The Connected status in a client app only means that the local network interface has been created. WireGuard uses UDP without establishing a traditional session. It is stateless, so the app may show a connection status even if the server is unavailable.

How to check that the tunnel works

WireGuard is designed to stay silent when there is no active traffic. Therefore, latest handshake: 2 hours ago by itself does not mean the connection is broken if the device has not sent any packets in the meantime. The transferred byte counters (RX/TX) also increase only while traffic is actually flowing.

For a reliable connection test:

  1. Generate active traffic through the tunnel:
    bash
    ping -c 3 10.66.66.1
    
  2. Then check the status:
    bash
    sudo wg show
    
    If the tunnel is working and packets are getting through, you will see a recent handshake from a few seconds ago and the RX/TX counters will increase.

Diagnostic commands

bash
# WireGuard interface status
sudo wg show

# Check listening ports
sudo ss -ulpn | grep 51820

# Check routing tables
ip route show
ip rule show

# Watch packets in real time
sudo tcpdump -n -i wg0

# Measure actual throughput through the tunnel
# On the server:
iperf3 -s
# On the client:
iperf3 -c 10.66.66.1
# Test the reverse direction (download):
iperf3 -c 10.66.66.1 -R

Monitoring: how can you tell if the VPN server has a problem?

  • Basic check: Regularly run systemctl status wg-quick@wg0. Keep in mind that an active systemd service does not guarantee internet connectivity.
  • Active test: Periodically ping a gateway or check DNS resolution through the tunnel from a client.
  • Server metrics (Prometheus Node Exporter): Use this for advanced resource monitoring, including disk usage, CPU load, memory, and network errors. Important security rule: never expose Node Exporter's port (9100/TCP) to the public internet. Bind it only to the WireGuard interface or access it through a closed private network.

Revoke a compromised device

  1. Open /etc/wireguard/wg0.conf on the server.
  2. Delete the [Peer] section for the lost device.
  3. Apply the change without disconnecting other users:
    bash
    sudo wg syncconf wg0 <(wg-quick strip wg0)
    

Decision tree: which VPN is right for you?

text
What is your primary goal?
│
├── Access a home network, NAS, or cameras
│   ├── Do you have a public IP address?
│   │   ├── NO (CGNAT / no port forwarding) ──→ Tailscale Exit Node / NetBird (0 EUR)
│   │   └── YES (fiber internet / public IP) ──→ WireGuard on a home router or Linux server (0 EUR)
│
├── Stay protected while traveling and get a stable IP in another country
│   ├── Want a web panel and simple device setup with a QR code ──→ VPS + wg-easy (v15.4.0) (about 3.8 to 6 EUR/month)
│   └── Want a clean, minimal system with maximum performance ──→ VPS + native dual-stack WireGuard + nftables
│
└── Special network conditions
    ├── Restrictive network or DPI actively blocks VPN protocols ──→ AmneziaWG (v3.1) or Outline
    ├── Team or larger business, SSO/MFA, and access controls ──→ NetBird / Defguard / Firezone
    ├── Native OS VPN with no extra app installation ──→ IKEv2 / strongSwan 6.1
    └── Broad backward compatibility and legacy network infrastructure ──→ OpenVPN 2.7

  1. Setup A (0 EUR, home access behind CGNAT): A home computer or Raspberry Pi + Tailscale Personal plan, advertising Exit Node and Subnet Route options. No port forwarding is required, and you get a residential IP address.
  2. Setup B (0 EUR, native home server with a public address): Home internet with a public dynamic address + WireGuard on an OpenWrt router or dedicated home server, using Dynamic DNS (DDNS).
  3. Setup C (low-cost cloud VPN for travel): Hetzner CX23 or OVHcloud VPS-1 + wg-easy v15.4.0 in Docker, with the admin panel bound to 127.0.0.1 and accessed over an SSH tunnel.
  4. Setup D (homelab control): Headscale on a small VPS + Tailscale clients on home machines + AdGuard Home and Unbound recursive DNS resolver.
  5. Setup E (teams and small businesses): NetBird or Defguard with an integrated identity provider (OIDC), mandatory MFA, and granular ZTNA access policies.

Pre-deployment checklist

  • Operating system: Install a current LTS release and enable automatic security updates with unattended-upgrades.
  • SSH access: Disable password authentication and root login. Use an Ed25519 key.
  • Firewall: Set the default policy to DROP. Open only the WireGuard UDP port and SSH port.
  • DNS protection: Do not expose port 53 to the internet. Test for DNS leaks on the client.
  • Panel isolation: If you use a graphical panel such as wg-easy, bind its port only to 127.0.0.1 and protect it with an SSH tunnel.
  • IPv6 protection: Either configure IPv6 fully through the tunnel with ::/0 and accept_ra = 2, or disable IPv6 on the client to prevent leaks.
  • User profiles: Give each device its own locally generated key pair and IP address inside the tunnel. Never share one profile between multiple devices at the same time.
  • Restart test: Reboot the server with sudo reboot and confirm that all services (nftables, wireguard, docker) start automatically.

Sources and documentation