What WireGuard Is
WireGuard is a kernel-level VPN. It runs inside the Linux kernel as a virtual network interface — not as a userspace daemon negotiating cipher suites and parsing configuration files over a TLS control channel. The entire codebase is roughly 4,000 lines of C. OpenVPN exceeds 100,000 lines. IPsec implementations are larger still.
This is not a cosmetic difference. Fewer lines of code mean a smaller attack surface, faster code audits, and fewer places for vulnerabilities to hide. WireGuard has been formally verified using the Tamarin prover for its cryptographic handshake protocol. It entered the mainline Linux kernel at version 5.6 — no out-of-tree modules, no DKMS builds, no kernel header dependencies on modern Ubuntu.
The cryptographic primitives are fixed. WireGuard uses the Noise protocol framework with Curve25519 for key exchange, ChaCha20 for symmetric encryption, Poly1305 for message authentication, and BLAKE2s for hashing. There is no cipher negotiation. You cannot select weaker algorithms. You cannot misconfigure a downgrade path. This eliminates an entire class of vulnerabilities — the kind where a legacy client forces a cipher suite downgrade that nobody notices until a breach report names it.
The performance difference is measurable. WireGuard achieves throughput in the range of 1 Gbps on modest hardware — comparable to native network speeds on most server-class machines. OpenVPN, running in userspace and performing context switches for every packet, typically reaches 200--400 Mbps under the same conditions. IPsec with hardware acceleration can match WireGuard's raw throughput, but the configuration complexity is an order of magnitude higher. For most deployments, WireGuard offers the best ratio of performance to operational simplicity.
Connection establishment is fast. A WireGuard handshake completes in a single round trip — roughly the latency of one ping. OpenVPN's TLS negotiation involves multiple round trips and can take several seconds over satellite or mobile links. WireGuard also roams silently between networks. Switch from Wi-Fi to mobile data and the tunnel re-establishes within a second, often without dropping a packet. This makes it particularly suited to mobile clients and laptops that move between networks throughout the day.
Tor, covered in a separate tutorial, provides anonymity rather than just encryption — a fundamentally different threat model. WireGuard encrypts your traffic and changes your apparent IP address. It does not anonymise you. The server operator sees your traffic in cleartext after it exits the tunnel.
Server Installation
Install WireGuard on Ubuntu.
apt update
apt install wireguard
WireGuard ships in the default Ubuntu repositories from 20.04 onwards. On older releases, add the PPA first — but if you are running Ubuntu older than 20.04 in production, you have larger problems than VPN selection.
Generating Server Keys
Every WireGuard peer — server and client alike — has a Curve25519 key pair. Generate the server's keys.
wg genkey | tee /etc/wireguard/server_private.key | wg pubkey > /etc/wireguard/server_public.key
Lock down the private key immediately.
chmod 600 /etc/wireguard/server_private.key
The private key must never leave this machine. If it is compromised, every tunnel using it is compromised. WireGuard has no certificate revocation mechanism — you generate new keys and reconfigure every peer.
Display the server's public key for later use in client configurations.
cat /etc/wireguard/server_public.key
Copy this value. Every client needs it in their [Peer] section. The private key stays on this machine and is never shared with anyone.
Server Configuration
Create the server configuration at /etc/wireguard/wg0.conf.
[Interface]
Address = 10.0.0.1/24
ListenPort = 51820
PrivateKey = <contents of server_private.key>
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
Replace eth0 with your server's actual external interface name. On many cloud instances this is ens3, enp0s3, or similar. Check with ip route show default.
Address — the server's IP address on the WireGuard network. Use a private range that does not collide with your existing networks. 10.0.0.0/24 is conventional.
ListenPort — the UDP port WireGuard listens on. 51820 is the default. UDP only — WireGuard does not use TCP.
PrivateKey — the server's private key. Paste the key contents, not the file path.
PostUp / PostDown — shell commands that run when the interface starts or stops. These enable NAT masquerading so tunnel clients can reach the internet through the server's public IP address.
Lock down the configuration file. It contains the private key in plaintext.
chmod 600 /etc/wireguard/wg0.conf
Enabling IP Forwarding
The server must forward packets between the WireGuard interface and the external interface. Without IP forwarding enabled, tunnel traffic arrives at the server and stops there.
Create a sysctl configuration file.
echo "net.ipv4.ip_forward = 1" > /etc/sysctl.d/99-wireguard.conf
sysctl -p /etc/sysctl.d/99-wireguard.conf
For IPv6 forwarding — necessary if you route IPv6 traffic through the tunnel — add net.ipv6.conf.all.forwarding = 1 to the same file.
Verify the setting is active.
sysctl net.ipv4.ip_forward
The output must read net.ipv4.ip_forward = 1. Anything else means forwarding is not enabled and tunnel traffic will be silently dropped.
If the setting was already present but set to 0 in another sysctl file, your new file may not take precedence. Check /etc/sysctl.conf and other files in /etc/sysctl.d/ for conflicting directives. The last value loaded wins, and files are processed in lexicographic order — which is why the 99 prefix ensures this file is loaded last.
Starting the Interface
Bring up the WireGuard interface and enable it across reboots.
systemctl enable --now wg-quick@wg0
Verify the interface is running.
wg show
This displays the interface name, public key, listening port, and any connected peers. At this stage the peer list is empty.
Generating Client Keys
Each client needs its own key pair. Generate them on the client machine — or on the server if you prefer centralised key management. Either way, transfer key material only through encrypted channels.
wg genkey | tee client_private.key | wg pubkey > client_public.key
Generate a preshared key for each client-server pair. This is optional but strongly recommended.
wg genpsk > client_preshared.key
The preshared key adds a symmetric encryption layer on top of the Curve25519 exchange. If a future quantum computer breaks Curve25519, the preshared key still protects recorded traffic. This is post-quantum defence at zero performance cost. There is no reason to omit it.
Adding the Peer to the Server
Add the client as a peer in the server configuration. Append the following block to /etc/wireguard/wg0.conf.
[Peer]
PublicKey = <contents of client_public.key>
PresharedKey = <contents of client_preshared.key>
AllowedIPs = 10.0.0.2/32
PublicKey — the client's public key. Never the private key.
PresharedKey — the symmetric secret for post-quantum defence. Must match the value in the client's configuration.
AllowedIPs — the IP addresses this peer is permitted to use inside the tunnel. Set this to the client's assigned address with a /32 mask. This directive serves two purposes — it is both a routing table entry and an access control. Packets arriving from this peer with a source address outside AllowedIPs are dropped silently.
If the server is already running, reload without dropping existing connections.
wg syncconf wg0 <(wg-quick strip wg0)
Or restart the interface entirely.
systemctl restart wg-quick@wg0
Repeat this process for each additional client. Assign each a unique IP address — 10.0.0.3/32, 10.0.0.4/32, and so on — and generate a separate key pair and preshared key for each.
Client Configuration
Create the client configuration file. This can live anywhere — /etc/wireguard/wg0.conf on a Linux client, or a file you import into the WireGuard application on other platforms.
[Interface]
PrivateKey = <contents of client_private.key>
Address = 10.0.0.2/24
DNS = 1.1.1.1
[Peer]
PublicKey = <contents of server_public.key>
PresharedKey = <contents of client_preshared.key>
Endpoint = <server_public_ip>:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25
PrivateKey — the client's private key.
Address — the client's IP on the WireGuard network. Must match what the server expects in the peer's AllowedIPs.
DNS — the DNS resolver to use while connected. Critical for preventing DNS leaks.
Endpoint — the server's public IP address and port. Only one side needs a known endpoint. The server learns the client's endpoint when the client initiates the handshake.
AllowedIPs — which destination addresses route through the tunnel. 0.0.0.0/0, ::/0 means all traffic — a full tunnel. For split tunnelling, replace this with only the subnets that should traverse the VPN. Everything else takes the default route.
PersistentKeepalive — sends a keepalive packet every 25 seconds. Required when the client sits behind NAT or a stateful firewall. Without it, the NAT mapping expires and the server can no longer reach the client.
Connecting the Client
On a Linux client, install WireGuard and bring up the interface.
apt install wireguard
cp client.conf /etc/wireguard/wg0.conf
chmod 600 /etc/wireguard/wg0.conf
systemctl enable --now wg-quick@wg0
On macOS, Windows, iOS, and Android, import the configuration file into the official WireGuard application. The app handles interface creation, routing table manipulation, and DNS configuration automatically.
On Windows, the official WireGuard application runs as a service and can be configured to start the tunnel at boot. On macOS, it integrates with the system's Network Extension framework. Both platforms support importing .conf files directly. The configuration format is identical across every platform — the same file works everywhere.
QR Codes for Mobile Clients
The WireGuard mobile apps can import configuration via QR code. Generate one from the client config file.
apt install qrencode
qrencode -t ansiutf8 < client.conf
This prints a QR code directly in the terminal. Point the phone's WireGuard app camera at it. The configuration transfers instantly without touching the phone's file system — useful when you generate configs on the server and want to avoid sending key material through messaging applications or email.
Delete the config file after generating the QR code. The file contains the client's private key in plaintext — leaving it on the server's filesystem means a server compromise exposes every client's key, not just the server's own. If you need to regenerate the QR code later, regenerate the client keys as well.
DNS Leak Prevention
A DNS leak occurs when your DNS queries bypass the VPN tunnel and reach your ISP's resolver in plaintext. The ISP then knows every domain you visit, regardless of the encrypted tunnel.
Set the DNS field in the client's [Interface] section to a resolver that is routed through the tunnel. Three approaches work.
Run a local resolver on the WireGuard server — unbound or dnsmasq listening on 10.0.0.1. Set DNS = 10.0.0.1 in the client config. All queries travel through the tunnel to the server and resolve there. This is the most private option — no third party sees your queries.
Use a privacy-respecting public resolver — 1.1.1.1 (Cloudflare) or 9.9.9.9 (Quad9) — and ensure AllowedIPs = 0.0.0.0/0 routes all traffic through the tunnel. DNS queries to any external IP then travel through the VPN.
On Linux clients, also check /etc/resolv.conf after connecting. Some network managers rewrite it and override the DNS setting in the WireGuard config. If your ISP's resolver appears there, the VPN's DNS setting is being ignored. Use resolvconf or systemd-resolved integration to enforce the tunnel's DNS.
Verify after connecting. Visit dnsleaktest.com and run the extended test. The results should show only resolvers associated with the server's network — not your local ISP.
Kill Switch
A kill switch prevents traffic from leaving the machine if the VPN tunnel drops. Without one, a momentary tunnel failure sends everything — DNS queries, application data, credentials — through the unencrypted default route. You would not notice.
The simplest approach is setting AllowedIPs = 0.0.0.0/0, ::/0. This routes all traffic through the tunnel. When the tunnel is down, there is no route and packets are dropped rather than sent in the clear. This provides a basic kill switch by default.
For stronger protection on Linux clients, add iptables rules that explicitly block non-tunnel traffic. Add these to the client configuration.
PostUp = iptables -I OUTPUT ! -o wg0 -m mark ! --mark $(wg show wg0 fwmark) -m addrtype ! --dst-type LOCAL -j REJECT
PreDown = iptables -D OUTPUT ! -o wg0 -m mark ! --mark $(wg show wg0 fwmark) -m addrtype ! --dst-type LOCAL -j REJECT
These rules reject all outbound traffic that does not pass through the wg0 interface, except traffic marked by WireGuard itself — the encrypted UDP packets to the server endpoint — and local traffic. If the tunnel fails, every outbound connection is rejected immediately rather than falling back to the default route.
On iOS and Android, the WireGuard app provides a built-in kill switch. Enable it in the tunnel's settings. The app configures the operating system's VPN integration to block all non-tunnel traffic automatically.
The family network guide elsewhere on this site mentions VPN tunnels as a bypass that children use to circumvent home DNS filtering. This is the tutorial that builds the tunnel — and the kill switch that ensures it cannot fail open.
Firewall Integration
The server must allow WireGuard traffic through its firewall. If you use UFW — covered in the UFW tutorial — allow the listening port.
ufw allow 51820/udp
Allow forwarding between the WireGuard interface and the external interface.
ufw route allow in on wg0 out on eth0
Replace eth0 with your actual external interface. Without this forwarding rule, tunnel clients can reach the server but not the internet beyond it.
If you use raw iptables instead of UFW, the PostUp rules in the server configuration handle forwarding. Ensure your INPUT chain allows UDP on port 51820 from any source.
If you run both Docker and WireGuard on the same server, be aware that Docker's iptables rules can interfere with WireGuard traffic forwarding. The FORWARD chain must allow traffic between wg0 and the external interface. Docker's default FORWARD policy is DROP, which blocks WireGuard forwarding unless explicit ACCEPT rules exist — the PostUp iptables commands in the server configuration handle this, but verify with iptables -L FORWARD -n that the rules are present and in the correct order.
Testing
Check the interface status on both server and client.
wg show
This displays each peer's public key, endpoint, latest handshake timestamp, and data transfer counters. A recent handshake — within the last two minutes — confirms the tunnel is active. A handshake older than several minutes means keepalives are not reaching the server, typically a firewall or NAT issue.
Verify your external IP address has changed.
curl ifconfig.me
The response should show the server's public IP, not the client's ISP-assigned address.
Run a DNS leak test at dnsleaktest.com. The extended test should show only resolvers associated with the server's network.
From the server, ping the client's tunnel address.
ping 10.0.0.2
From the client, ping the server's tunnel address.
ping 10.0.0.1
If pings fail but the handshake succeeds, the problem is almost always IP forwarding. Verify net.ipv4.ip_forward = 1 is active. If forwarding is correct, check for firewall rules blocking ICMP or forwarded traffic between interfaces.
For more detailed troubleshooting, watch the kernel log for WireGuard messages.
dmesg | grep wireguard
Common problems include: the server's ListenPort is blocked by the firewall, the client's Endpoint address is wrong, NAT timeout is dropping the connection because PersistentKeepalive is not set, or AllowedIPs on one side does not match the Address on the other. Each of these produces a different failure mode — no handshake, handshake but no traffic, or intermittent drops. Diagnose methodically from the handshake outward.
Hardening
Restrict AllowedIPs on the server. Each peer's AllowedIPs should be the narrowest possible range — a single /32 for most clients. An overly broad AllowedIPs on the server side can allow one client to route traffic as though it were another, or to inject routes for subnets it should not reach.
Monitor for unexpected peers. Run wg show regularly or script it into your monitoring. Any peer you do not recognise means a private key has been compromised. Generate new server keys immediately, reconfigure all legitimate peers, and investigate.
Rotate keys on a schedule. WireGuard has no built-in key rotation mechanism. Schedule manual rotation — quarterly is reasonable for most environments. Generate new key pairs for both sides, update the configurations, and restart.
Limit ListenPort exposure. If you change the default port from 51820, verify the new port is allowed through the firewall and is not used by another service. A port conflict fails silently — WireGuard starts but cannot receive packets.
Log connection events. WireGuard itself produces minimal logging. Use wg show output, firewall logs, and network flow data to track connection patterns. Unexpected endpoints or traffic volumes warrant investigation.
Keep configuration files out of version control. WireGuard config files contain private keys in plaintext. If you must version-control your server configuration, strip the PrivateKey values and load them from a separate file or environment variable at deployment time. A private key committed to a Git repository is a private key that every developer with read access can use to impersonate your server.
Use separate interfaces for separate purposes. A server can run multiple WireGuard interfaces — wg0 for remote access, wg1 for site-to-site tunnelling. Each has its own key pair, address range, and peer list. This provides clean separation between trust domains without running multiple VPN servers.
Operational Summary
Install WireGuard, generate key pairs for every peer with a preshared key for post-quantum defence, create the server configuration with NAT masquerading and IP forwarding, add each client as a peer with a narrowly scoped AllowedIPs, build the client configuration with DNS set to a resolver routed through the tunnel, enable the kill switch with AllowedIPs = 0.0.0.0/0, ::/0 and iptables rules that block non-tunnel traffic, open UDP 51820 through the firewall, and verify with wg show, curl ifconfig.me, and a DNS leak test. Rotate keys quarterly, monitor wg show for unknown peers, and never transfer private key material through unencrypted channels.