Skip to main content
COSMICBYTEZLABS
NewsSecurityHOWTOsToolsTraining
StudyProjectsNewsletterHire MeAbout
Subscribe

Press Enter to search or Esc to close

News
Security
HOWTOs
Tools
Training
Study
Projects
Newsletter
Hire Me
About
RSS Feed
Reading List
Subscribe

Stay in the Loop

Get the latest security alerts, tutorials, and tech insights delivered to your inbox.

Subscribe NowFree forever. No spam.
COSMICBYTEZLABS

Your trusted source for IT intelligence, cybersecurity insights, and hands-on technical guides.

1371+ Articles
150+ Guides

CONTENT

  • Latest News
  • Security Alerts
  • HOWTOs
  • Checklists
  • Projects
  • Exam Prep

RESOURCES

  • Search
  • Browse Tags
  • Newsletter Archive
  • Reading List
  • RSS Feed

COMPANY

  • About Us
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CosmicBytez Labs. All rights reserved.

System Status: Operational
  1. Home
  2. HOWTOs
  3. WireGuard VPN Setup and Security Hardening on Linux
WireGuard VPN Setup and Security Hardening on Linux
HOWTOIntermediate

WireGuard VPN Setup and Security Hardening on Linux

Deploy a hardened WireGuard VPN server on Linux — key generation, server and client config, firewall rules, and security best practices for production use.

Dylan H.

Tutorials

June 8, 2026
8 min read

Prerequisites

  • Linux server running Ubuntu 22.04 LTS or Debian 12 (kernel 5.6+ recommended)
  • Root or sudo access on the server
  • A public IP address or hostname for the server
  • Basic familiarity with the Linux command line and networking concepts
  • At least one client device (Linux, macOS, Windows, or mobile)

Introduction

WireGuard is a modern VPN protocol built directly into the Linux kernel. Compared to OpenVPN or IPSec, it offers a significantly smaller codebase (~4,000 lines vs. hundreds of thousands), faster handshakes, and a simpler configuration model — all while using state-of-the-art cryptography (ChaCha20, Poly1305, Curve25519, BLAKE2s, and SipHash).

This guide walks through deploying a WireGuard server on Ubuntu/Debian, configuring one or more clients, setting up proper firewall rules with nftables, and applying hardening measures suited for production or homelab environments. By the end you will have an encrypted tunnel that routes all client traffic through the server, with key rotation procedures and monitoring in place.


Prerequisites

Before starting, confirm the following:

  • Server OS: Ubuntu 22.04 LTS or Debian 12 (WireGuard ships in the mainline kernel from 5.6+; Ubuntu 20.04 requires a manual kernel module install)
  • Server resources: 1 vCPU / 512 MB RAM is sufficient for a personal or small-team VPN
  • Open port: UDP 51820 (or your chosen port) reachable from the internet
  • IP forwarding: must be enabled on the server (covered in Step 3)
  • Clients: WireGuard apps are available at wireguard.com/install/ for all major platforms

Step 1 — Install WireGuard

On Ubuntu 22.04 or Debian 12, WireGuard is available from the default repositories:

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

Verify the kernel module loaded:

sudo modprobe wireguard
lsmod | grep wireguard
# Expected output:
# wireguard             102400  0

If the module is missing on older kernels, add the WireGuard PPA first:

sudo add-apt-repository ppa:wireguard/wireguard
sudo apt update && sudo apt install -y wireguard

Step 2 — Generate Server and Client Key Pairs

WireGuard uses Curve25519 asymmetric keys. Each peer (server or client) needs a private/public key pair. Keep private keys strictly confidential — they never leave their respective host.

# Create a secure working directory
sudo mkdir -p /etc/wireguard
sudo chmod 700 /etc/wireguard
cd /etc/wireguard
 
# Generate server keys
wg genkey | sudo tee server_private.key | wg pubkey | sudo tee server_public.key
sudo chmod 600 server_private.key
 
# Generate keys for client 1 (repeat for each additional client)
wg genkey | sudo tee client1_private.key | wg pubkey | sudo tee client1_public.key
sudo chmod 600 client1_private.key
 
# Generate a pre-shared key for extra forward secrecy (optional but recommended)
wg genpsk | sudo tee client1_preshared.key
sudo chmod 600 client1_preshared.key

Print the keys you will need for configuration:

echo "Server private: $(sudo cat /etc/wireguard/server_private.key)"
echo "Server public:  $(sudo cat /etc/wireguard/server_public.key)"
echo "Client1 private: $(sudo cat /etc/wireguard/client1_private.key)"
echo "Client1 public:  $(sudo cat /etc/wireguard/client1_public.key)"
echo "Client1 PSK:     $(sudo cat /etc/wireguard/client1_preshared.key)"

Step 3 — Enable IP Forwarding

The server must forward packets between the tunnel interface and the internet-facing interface.

# Enable immediately (non-persistent)
sudo sysctl -w net.ipv4.ip_forward=1
sudo sysctl -w net.ipv6.conf.all.forwarding=1
 
# Make persistent across reboots
sudo tee -a /etc/sysctl.d/99-wireguard.conf <<EOF
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
# Harden: disable source routing and ICMP redirects
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.all.rp_filter = 1
EOF
 
sudo sysctl --system

Step 4 — Configure the WireGuard Server

Determine your server's outbound interface name (typically eth0 or ens3):

ip route | grep default
# Example: default via 203.0.113.1 dev eth0 proto static

Create the server configuration file. Replace SERVER_PRIVATE_KEY and CLIENT1_PUBLIC_KEY with the actual key values from Step 2, and adjust the interface name in the PostUp/PostDown rules to match your server.

sudo tee /etc/wireguard/wg0.conf <<EOF
[Interface]
# Server private key
PrivateKey = <SERVER_PRIVATE_KEY>
 
# VPN subnet — server takes .1, clients get .2, .3, etc.
Address = 10.8.0.1/24
 
# UDP port WireGuard listens on
ListenPort = 51820
 
# Save peer changes made with 'wg set' back to this file
SaveConfig = false
 
# NAT: masquerade VPN traffic as the server's public IP
PostUp   = nft add table ip wireguard; \
           nft add chain ip wireguard postrouting { type nat hook postrouting priority 100 \; }; \
           nft add rule ip wireguard postrouting oifname "eth0" masquerade
PostDown = nft delete table ip wireguard
 
# Drop martian packets on the tunnel interface
PostUp   = nft add table ip wg_filter; \
           nft add chain ip wg_filter input { type filter hook input priority 0 \; policy drop \; }; \
           nft add rule ip wg_filter input iifname "wg0" accept; \
           nft add rule ip wg_filter input ct state established,related accept; \
           nft add rule ip wg_filter input iifname "lo" accept
PostDown = nft delete table ip wg_filter
 
# --- Peers ---
 
[Peer]
# Client 1 — laptop
PublicKey = <CLIENT1_PUBLIC_KEY>
PresharedKey = <CLIENT1_PRESHARED_KEY>
# IP address assigned to this client inside the VPN
AllowedIPs = 10.8.0.2/32
EOF
 
sudo chmod 600 /etc/wireguard/wg0.conf

Note on SaveConfig: Setting SaveConfig = false prevents WireGuard from overwriting your hand-crafted config when the interface goes down. If you add peers dynamically with wg set, set it to true instead.


Step 5 — Configure the Firewall

Open the WireGuard UDP port using ufw or directly with nftables. The PostUp rules in the server config already handle NAT, but you still need to allow the inbound port:

Option A — ufw (simpler)

sudo ufw allow 51820/udp
sudo ufw allow OpenSSH      # ensure SSH remains open
sudo ufw enable
sudo ufw status

Option B — nftables (production-grade)

sudo tee /etc/nftables.conf <<'NFTEOF'
#!/usr/sbin/nft -f
flush ruleset
 
table inet filter {
    chain input {
        type filter hook input priority 0; policy drop;
        ct state established,related accept
        iifname "lo" accept
        ip protocol icmp accept
        tcp dport 22 accept      # SSH
        udp dport 51820 accept   # WireGuard
        iifname "wg0" accept     # allow traffic from VPN peers
    }
    chain forward {
        type filter hook forward priority 0; policy drop;
        iifname "wg0" oifname "eth0" accept   # VPN → internet
        iifname "eth0" oifname "wg0" ct state established,related accept
    }
    chain output {
        type filter hook output priority 0; policy accept;
    }
}
NFTEOF
 
sudo systemctl enable nftables
sudo systemctl restart nftables

Step 6 — Start WireGuard and Enable on Boot

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

Verify the interface is up:

sudo wg show
# Expected output:
# interface: wg0
#   public key: <server public key>
#   private key: (hidden)
#   listening port: 51820
#
# peer: <client1 public key>
#   preshared key: (hidden)
#   allowed ips: 10.8.0.2/32

Step 7 — Configure the Client

Linux Client

Install WireGuard on the client machine, then create its configuration:

sudo tee /etc/wireguard/wg0.conf <<EOF
[Interface]
PrivateKey = <CLIENT1_PRIVATE_KEY>
Address = 10.8.0.2/32
DNS = 1.1.1.1, 8.8.8.8
 
[Peer]
PublicKey = <SERVER_PUBLIC_KEY>
PresharedKey = <CLIENT1_PRESHARED_KEY>
Endpoint = <SERVER_PUBLIC_IP_OR_HOSTNAME>:51820
# Route ALL traffic through VPN; change to 10.8.0.0/24 for split-tunnel
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25
EOF
 
sudo chmod 600 /etc/wireguard/wg0.conf
sudo wg-quick up wg0

Mobile / GUI Clients (iOS, Android, Windows, macOS)

The easiest method is to generate a QR code from the client config on the server:

# Install qrencode
sudo apt install -y qrencode
 
# Generate QR code (display in terminal)
qrencode -t ansiutf8 < /etc/wireguard/client1.conf

Create /etc/wireguard/client1.conf using the same [Interface] + [Peer] structure above, then encode it. Scan the QR from the official WireGuard app.


Step 8 — Security Hardening

8.1 Restrict Config File Permissions

sudo chmod 600 /etc/wireguard/*.conf
sudo chmod 600 /etc/wireguard/*.key
sudo chown root:root /etc/wireguard/*.conf /etc/wireguard/*.key

8.2 Use a Non-Standard Port

Avoid 51820 if your deployment is high-visibility. Edit ListenPort in wg0.conf to any unused UDP port (e.g., 4096, 59000), then update your firewall rules and client Endpoint accordingly.

8.3 Rate-Limit Handshake Attempts

Prevent brute-force amplification by rate-limiting inbound WireGuard UDP packets at the firewall:

# Add to the nftables input chain (before the allow rule)
sudo nft add rule inet filter input udp dport 51820 limit rate over 100/second drop

8.4 Rotate Pre-Shared Keys Periodically

Pre-shared keys add a symmetric layer on top of the asymmetric handshake, providing post-quantum forward secrecy. Rotate them quarterly:

# Generate new PSK
NEW_PSK=$(wg genpsk)
echo "$NEW_PSK" | sudo tee /etc/wireguard/client1_preshared.key
 
# Update running config without a full restart
sudo wg set wg0 peer <CLIENT1_PUBLIC_KEY> preshared-key /etc/wireguard/client1_preshared.key
 
# Update wg0.conf for persistence
sudo sed -i "s|PresharedKey = .*|PresharedKey = $NEW_PSK|" /etc/wireguard/wg0.conf

8.5 Monitor with Fail2ban (Optional)

WireGuard itself doesn't expose traditional auth logs, but you can monitor for unexpected peer connection attempts in kernel logs and react with custom Fail2ban filters targeting your firewall logs.

sudo apt install -y fail2ban

Create /etc/fail2ban/filter.d/wireguard.conf:

[Definition]
failregex = nft.*wireguard.*SRC=<HOST>
ignoreregex =

8.6 Disable Unused IPv6 (If Not Needed)

If you are not routing IPv6 over the tunnel, prevent IPv6 leaks on clients by removing ::/0 from AllowedIPs in the client config.


Step 9 — Verification and Testing

Verify the Tunnel from the Client

# Check the interface is up
sudo wg show
 
# Ping the server's VPN address
ping 10.8.0.1
 
# Confirm traffic routes through the VPN
curl https://ifconfig.me
# Should return the SERVER's public IP, not the client's

Check Handshake Timestamps

A recent handshake confirms bidirectional connectivity:

sudo wg show wg0 latest-handshakes
# Output: <client_pubkey>   <unix_timestamp>
# Convert: date -d @<timestamp>

Verify DNS is Leaking Correctly

# On the client with 0.0.0.0/0 route
nslookup whoami.akamai.net
# Should resolve from the server-side DNS, not your local ISP

Use https://dnsleaktest.com for an end-to-end DNS leak test.


Troubleshooting

Handshake Never Completes

# Check the server is listening
sudo ss -ulnp | grep 51820
 
# Confirm the firewall allows UDP 51820
sudo nft list ruleset | grep 51820
 
# Test UDP reachability from the client (requires netcat)
nc -zu <SERVER_IP> 51820 && echo "Port open" || echo "Port closed"

No Internet Access After Connecting

# Confirm IP forwarding is active
sysctl net.ipv4.ip_forward   # should be 1
 
# Confirm NAT rule is loaded
sudo nft list table ip wireguard
 
# Check default route on the client
ip route
# 0.0.0.0/0 should point to the wg0 interface

Peer Disconnects After Idle Period

Add PersistentKeepalive = 25 to the [Peer] block in the client config. This sends a keepalive packet every 25 seconds, preventing NAT session tables from expiring — essential when clients sit behind NAT routers.

Config Changes Not Taking Effect

WireGuard reads the config only on interface bring-up. After editing wg0.conf, restart the interface:

sudo wg-quick down wg0 && sudo wg-quick up wg0
# or
sudo systemctl restart wg-quick@wg0

Key Mismatch Errors in Logs

journalctl -u wg-quick@wg0 --since "10 minutes ago"

A "Invalid public key" error means the server's [Peer] PublicKey doesn't match the client's actual private key. Regenerate and copy keys carefully — base64 keys are easy to accidentally truncate.


Summary

You now have a production-ready WireGuard VPN deployment:

ComponentConfiguration
EncryptionChaCha20-Poly1305 + Curve25519 key exchange
Forward secrecyPer-session ephemeral keys + optional PSK layer
NATnftables masquerade on the server's public interface
Firewallnftables with explicit allow rules and rate limiting
Systemdwg-quick@wg0 service, enabled on boot
Hardening600 permissions on keys, non-default port, rate limiting, PSK rotation

WireGuard's minimal attack surface and in-kernel implementation make it one of the most defensible VPN options available today. For multi-site mesh topologies, explore tools like Netbird or Tailscale which build on WireGuard and add centralized key management and ACLs.

#wireguard#vpn#networking#linux#encryption#security-hardening#tunneling

Related Articles

Employee Offboarding: The Security Checklist Most Northern Alberta Businesses Skip

Offboarding is where most SMB security postures actually fail. The technical checklist is well-known. The process discipline is what&apos;s missing in…

8 min read

OT Security for Sawmills, Shops, and Ag Operations: The Part of Cyber That Breaks Production

OT — operational technology — is the side of cyber that takes a sawmill offline for a week. PLCs, telemetry, SCADA, building-management systems. Different…

8 min read

What a vCISO Actually Does for a 30-Person Business (and When You Don&apos;t Need One Yet)

vCISO services get marketed to every SMB with a security budget. Most businesses under 20 seats don&apos;t need one yet. Most businesses 20 to 100 seats with…

7 min read
Back to all HOWTOs