How to Install and Configure WireGuard VPN Server on Linux: Secure, Lightweight, and High-Speed Remote Connections

Linux tutorial - IT technology blog
Linux tutorial - IT technology blog

The Problem with Traditional VPNs and Why WireGuard Was Created

Most sysadmins reach for OpenVPN when they need a VPN — it works, but the configuration is incredibly cumbersome. CA certificates, .ovpn files hundreds of lines long, a bloated daemon. And every time a client switches networks — from WiFi to 4G, for example — the connection drops immediately and has to be reconnected from scratch.

I ran into exactly this problem when our company needed remote team members to access internal systems. Running on an aging CentOS 7 server, I tried every trick in the book to get acceptable performance — and after many rounds of testing, WireGuard was the only solution that actually solved the problem without dragging in a whole new set of headaches.

Since Linux 5.6, WireGuard has been built directly into the kernel — no third-party modules needed. The entire codebase is roughly 4,000 lines, compared to hundreds of thousands of lines in the OpenVPN + OpenSSL stack. Less code means fewer attack surfaces, easier security auditing, and noticeably lower packet processing overhead.

Core Concepts Before Installation

Unlike OpenVPN’s strict server/client distinction, WireGuard uses a peer-to-peer model — every machine in the network is an equal peer with its own key pair (private key + public key).

  • Interface: WireGuard creates a virtual network interface (typically named wg0), similar to interfaces like eth0 or tun0.
  • Endpoint: The actual IP:port address peers use to connect to each other (typically only the server needs to declare this).
  • AllowedIPs: The IP ranges allowed to traverse the tunnel. This is a key difference from OpenVPN — WireGuard uses this field directly for routing, eliminating the need for complex routing tables.
  • PersistentKeepalive: Sends a keepalive packet every N seconds — important when the client is behind NAT.

The key point: WireGuard maintains the connection across network changes through its roaming mechanism — a client changing IP addresses won’t break the tunnel, as long as both peers can still exchange packets.

Hands-On: Installing WireGuard Server

Step 1: Install WireGuard

On Ubuntu 20.04/22.04:

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

On CentOS 7/8 or RHEL (requires the EPEL repository):

# CentOS 7
yum install -y epel-release
yum install -y kmod-wireguard wireguard-tools

# CentOS 8 / Stream
dnf install -y epel-release elrepo-release
dnf install -y kmod-wireguard wireguard-tools

Verify the kernel module has loaded:

modprobe wireguard
lsmod | grep wireguard

Step 2: Generate the Server Key Pair

WireGuard uses Curve25519 for key exchange — the same algorithm used by the Signal Protocol. Generating keys takes a single command:

cd /etc/wireguard
umask 077  # Important: ensure the private key cannot be read by other users
wg genkey | tee server_private.key | wg pubkey > server_public.key
cat server_private.key  # Save this value
cat server_public.key   # Use this in client config

The umask 077 command ensures the generated key files have 600 permissions. Skip this step and the private key can be read by any user on the server — compromising the entire tunnel.

Step 3: Create the Server Configuration (/etc/wireguard/wg0.conf)

Create the configuration file with the following content:

cat > /etc/wireguard/wg0.conf << 'EOF'
[Interface]
Address = 10.0.0.1/24
ListenPort = 51820
PrivateKey = <SERVER_PRIVATE_KEY>

# Enable IP forwarding and NAT so clients can reach the internet through the VPN
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

# Peer: Client 1
[Peer]
PublicKey = <CLIENT1_PUBLIC_KEY>
AllowedIPs = 10.0.0.2/32
EOF

Note: Replace eth0 with your server’s actual interface (check with ip route | grep default). Replace the <...> placeholders with actual key values.

Enable IP forwarding permanently:

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

Step 4: Start WireGuard and Enable Autostart

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

# View tunnel status
wg show

The output of wg show displays the wg0 interface, the listening port, and the list of connected peers.

Client Configuration

Generate the Client Key Pair

Run this on the client machine (the process is the same on Linux, macOS, and Windows):

wg genkey | tee client_private.key | wg pubkey > client_public.key

Client Configuration File (wg0.conf or tunnel.conf)

[Interface]
Address = 10.0.0.2/24
PrivateKey = <CLIENT_PRIVATE_KEY>
DNS = 1.1.1.1  # Optional: use DNS through the tunnel

[Peer]
PublicKey = <SERVER_PUBLIC_KEY>
Endpoint = <SERVER_PUBLIC_IP>:51820
AllowedIPs = 0.0.0.0/0  # Route all traffic through the VPN
# Or route only internal subnets:
# AllowedIPs = 10.0.0.0/24, 192.168.1.0/24
PersistentKeepalive = 25

Start the tunnel on the client:

sudo wg-quick up ./tunnel.conf

# Check connectivity
ping 10.0.0.1  # Ping the server
curl ifconfig.me  # Check public IP (should show the VPN server's IP)

Adding a New Peer to the Server Without Restarting

This is one of my favorite things about WireGuard — adding a new client doesn’t require reloading the service:

# Add a peer dynamically (without interrupting current connections)
wg set wg0 peer <CLIENT2_PUBLIC_KEY> allowed-ips 10.0.0.3/32

# Save current config to file
wg-quick save wg0

Opening the Firewall for WireGuard’s Port

Don’t forget to open UDP port 51820 on the server’s firewall:

# UFW (Ubuntu)
ufw allow 51820/udp

# firewalld (CentOS/RHEL)
firewall-cmd --permanent --add-port=51820/udp
firewall-cmd --reload

# Direct iptables
iptables -A INPUT -p udp --dport 51820 -j ACCEPT

If your server is behind NAT (a cloud VPS), remember to open the port in your cloud provider’s security group or network ACL.

Troubleshooting Common Issues

Some issues I’ve encountered and how to fix them:

  • Client connects but can’t reach the internet: Check that ip_forward is enabled (sysctl net.ipv4.ip_forward), and verify the NAT MASQUERADE rule is pointing to the correct interface.
  • Tunnel is up but pings fail: Run wg show — if you see a latest handshake line, the handshake was successful. No such line means the public key or endpoint is incorrect.
  • CentOS 7 fails to load the module: The default CentOS 7 kernel (3.10.x) doesn’t support WireGuard natively — you’ll need to use DKMS or upgrade to a mainline kernel via ELRepo (yum install -y kernel-ml from the ELRepo repo).
  • Connection drops after a few minutes: Add PersistentKeepalive = 25 to the peer config on the client side.

Conclusion

After a few weeks of real-world use, the entire team stopped complaining about VPN drops. Switching networks from the office to home, from WiFi to 4G — the tunnel holds steady. Adding new members takes just a few minutes without touching the running service.

WireGuard isn’t an improved version of OpenVPN — it’s an entirely different approach. 4,000 lines running in the kernel instead of hundreds of thousands of lines running in userspace. When you need to scale: change AllowedIPs to specific subnets for split tunneling, or add a DNS entry in the client’s [Interface] section to access internal services by domain name instead of typing IP addresses.

Share: