WireGuard Connection Refused: Fix 'connection refused', High Latency, and Packet Loss
WireGuard connection refused? Fix port blocking, handshake failures, and packet loss with step-by-step commands for Linux, pfSense, and Docker.
- Root cause 1: UDP port 51820 is blocked by a firewall rule, cloud security group, or ISP — WireGuard is UDP-only and silently drops when the port is inaccessible.
- Root cause 2: Mismatched public keys, AllowedIPs, or Endpoint addresses in wg0.conf prevent the cryptographic handshake from completing, producing 'latest handshake: never' in wg show.
- Root cause 3: Kernel module not loaded (wg-quick requires wireguard.ko) or systemd-resolved conflicts cause DNS leaks and apparent connection failures after tunnel comes up.
- Quick fix: Run 'wg show' to confirm handshake state, verify UDP 51820 is open with 'nc -zvu <server> 51820', re-check keys with 'wg pubkey < privatekey', and ensure PostUp iptables MASQUERADE rules are active.
| Method | When to Use | Time | Risk |
|---|---|---|---|
| Verify firewall rules (iptables/nftables/ufw) | Port appears blocked, nc -zvu times out | 5 min | Low — read-only diagnosis, additive rule |
| Regenerate keypair and update peers | Handshake never completes, keys were rotated | 10 min | Medium — all peers must update simultaneously |
| Change WireGuard listen port to 443/UDP | ISP or captive portal blocks non-standard UDP | 5 min | Low — requires firewall rule update on server |
| Enable IP forwarding + MASQUERADE rule | Clients connect but cannot reach internet/LAN | 5 min | Low — standard routing config |
| Rebuild interface (wg-quick down/up) | Stale routes, kernel module inconsistency | 2 min | Low — brief tunnel outage |
| Use wg-quick with DNS= override | DNS leaks or resolution fails inside tunnel | 5 min | Low — may affect host resolver |
| Switch to TCP encapsulation (udptunnel/udp2raw) | UDP fully blocked by restrictive network | 30 min | Medium — additional software dependency |
Understanding WireGuard Connection Refused
WireGuard operates exclusively over UDP. Unlike TCP-based VPNs that return a TCP RST (Connection refused) or TLS alert, a blocked WireGuard port produces silence — the kernel sends an ICMP Port Unreachable only in some configurations, and many firewalls drop UDP silently. This makes diagnosis less obvious than a classic TCP Connection refused error.
The symptoms manifest in several ways:
wg showreportslatest handshake: neveror a handshake timestamp older than 3 minutespingto tunnel peer IP succeeds but latency spikes above 200 ms intermittently- Packet loss of 10–50% visible in
ping -c 100 10.0.0.1 - DNS resolution fails inside the tunnel despite the interface being up
journalctl -u wg-quick@wg0showsRTNETLINK answers: Operation not permitted
Step 1: Confirm the Interface Is Up
The first question is whether WireGuard even loaded the interface:
ip link show wg0
wg show
If ip link show wg0 returns Device "wg0" does not exist, the kernel module is missing or wg-quick failed silently. Load the module:
sudo modprobe wireguard
lsmod | grep wireguard
On distributions using DKMS-built modules (Debian/Ubuntu before 5.6 kernel):
sudo apt install wireguard-dkms linux-headers-$(uname -r)
On RHEL/CentOS 8+:
sudo dnf install elrepo-release epel-release
sudo dnf install kmod-wireguard
Step 2: Check UDP Port Reachability
From the client machine, test whether the server's WireGuard port is reachable:
nc -zvu <server-public-ip> 51820
Expected output when port is open:
Connection to <server-public-ip> 51820 port [udp/*] succeeded!
If it hangs or shows ICMP port unreachable — the port is filtered. Common causes:
On the server — iptables/nftables:
sudo iptables -L INPUT -n -v | grep 51820
sudo nft list ruleset | grep 51820
Add the rule if missing:
sudo iptables -A INPUT -p udp --dport 51820 -j ACCEPT
sudo iptables -A FORWARD -i wg0 -j ACCEPT
sudo iptables -A FORWARD -o wg0 -j ACCEPT
On the server — ufw:
sudo ufw allow 51820/udp
sudo ufw status verbose
Cloud providers (AWS, GCP, Azure, DigitalOcean): Check the security group / firewall rules in the cloud console. UDP 51820 must be explicitly allowed inbound. This is the most commonly missed step on cloud VMs.
Step 3: Validate Keys and Configuration
A handshake that never completes despite an open port almost always means a key mismatch. WireGuard is strict: if the peer's public key does not match, the packet is silently discarded.
On the server, display the running config:
sudo wg show
Verify the client's public key shown under peer: matches the key you generated on the client:
# On the client — regenerate public key from private key
wg pubkey < /etc/wireguard/privatekey
Compare it character-for-character with the [Peer] PublicKey in the server's /etc/wireguard/wg0.conf.
Common configuration errors in /etc/wireguard/wg0.conf:
# WRONG — missing /32 on AllowedIPs causes routing table conflict
AllowedIPs = 10.0.0.2
# CORRECT
AllowedIPs = 10.0.0.2/32
# WRONG — Endpoint uses hostname that doesn't resolve from inside tunnel
Endpoint = myvpn.local:51820
# CORRECT — use public IP or FQDN resolvable before tunnel up
Endpoint = 203.0.113.1:51820
Step 4: Fix IP Forwarding and NAT (Clients Can't Reach Internet)
If the handshake succeeds (wg show shows a recent timestamp) but traffic beyond the server is dropped, IP forwarding or masquerade is missing:
# Check current forwarding state
cat /proc/sys/net/ipv4/ip_forward
# Enable immediately
echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward
# Persist across reboots
echo 'net.ipv4.ip_forward = 1' | sudo tee /etc/sysctl.d/99-wireguard.conf
sudo sysctl -p /etc/sysctl.d/99-wireguard.conf
Add MASQUERADE to the PostUp hook in wg0.conf (replace eth0 with your outbound interface — find it with ip route | grep default):
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PreDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
Step 5: Diagnose High Latency and Packet Loss
If the tunnel is up but latency is unpredictable or packet loss occurs:
Check MTU. WireGuard adds ~60 bytes of overhead. If the underlying network MTU is 1500, the WireGuard interface should be 1420 or lower to avoid fragmentation:
ip link show wg0 | grep mtu
# Adjust if needed
sudo ip link set wg0 mtu 1420
# Or in wg0.conf under [Interface]:
MTU = 1420
Test with ping to detect fragmentation:
ping -M do -s 1400 10.0.0.1
If this fails but ping -s 1200 10.0.0.1 works, fragmentation is the cause.
Check CPU crypto offload. On low-power devices (Raspberry Pi, ARM routers), WireGuard's ChaCha20-Poly1305 may saturate the CPU at high throughput. Monitor with:
top -d 1
# Look for softirq or kworker spikes
watch -n1 'cat /proc/net/dev | grep wg0'
Persistent keepalive for NAT traversal. Behind NAT, stateful mapping expires after ~30 seconds of inactivity, causing sudden packet loss bursts. Add to the [Peer] section on the client:
PersistentKeepalive = 25
Step 6: Port Blocked by ISP or Captive Portal
Some ISPs block non-standard UDP ports. If nc -zvu fails from multiple networks and the server firewall is confirmed open, change the listen port on the server to something less likely to be filtered:
[Interface]
ListenPort = 53
# or 443 — note: 443/UDP is also used by QUIC/HTTP3, but rarely blocked
Update the Endpoint on all clients to match. If UDP is entirely blocked (corporate networks, hotel Wi-Fi), use udp2raw to wrap WireGuard traffic in a fake TCP stream or ICMP:
# Server
udp2raw -s -l 0.0.0.0:443 -r 127.0.0.1:51820 -k "yourpassword" --raw-mode faketcp
# Client
udp2raw -c -l 0.0.0.0:51821 -r <server-ip>:443 -k "yourpassword" --raw-mode faketcp
# Then set Endpoint = 127.0.0.1:51821 in wg0.conf
Step 7: Docker and Container-Specific Issues
When running WireGuard in Docker, NET_ADMIN and SYS_MODULE capabilities are required:
cap_add:
- NET_ADMIN
- SYS_MODULE
sysctl:
- net.ipv4.ip_forward=1
- net.ipv4.conf.all.src_valid_mark=1
volumes:
- /lib/modules:/lib/modules:ro
Without SYS_MODULE, wg-quick up wg0 fails with:
ip: RTNETLINK answers: Operation not permitted
Also ensure the host kernel has the wireguard module loaded before starting the container — the container cannot load kernel modules if the host lacks them.
Frequently Asked Questions
#!/usr/bin/env bash
# WireGuard Diagnostic Script
# Run as root or with sudo on the server or client
echo '=== WireGuard Interface Status ==='
ip link show wg0 2>/dev/null || echo 'ERROR: wg0 interface not found'
echo ''
echo '=== WireGuard Runtime Config ==='
wg show 2>/dev/null || echo 'ERROR: wg command failed or interface down'
echo ''
echo '=== Kernel Module ==='
lsmod | grep wireguard && echo 'OK: wireguard module loaded' || echo 'WARN: wireguard module NOT loaded -- run: sudo modprobe wireguard'
echo ''
echo '=== IP Forwarding ==='
FWD=$(cat /proc/sys/net/ipv4/ip_forward)
if [ "$FWD" = '1' ]; then echo 'OK: ip_forward = 1'; else echo 'ERROR: ip_forward = 0 -- run: echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward'; fi
echo ''
echo '=== NAT / MASQUERADE Rules ==='
sudo iptables -t nat -L POSTROUTING -n -v 2>/dev/null | grep -E 'MASQUERADE|target' || echo 'WARN: no MASQUERADE rules found'
echo ''
echo '=== INPUT Rules for UDP 51820 ==='
sudo iptables -L INPUT -n -v 2>/dev/null | grep '51820' || echo 'WARN: no iptables rule found for 51820 -- may be handled by ufw or nftables'
sudo ufw status 2>/dev/null | grep '51820' || true
sudo nft list ruleset 2>/dev/null | grep '51820' || true
echo ''
echo '=== MTU Check ==='
MTU=$(ip link show wg0 2>/dev/null | grep -oP 'mtu \K[0-9]+')
if [ -n "$MTU" ]; then
echo "wg0 MTU: $MTU"
[ "$MTU" -gt 1420 ] && echo 'WARN: MTU may be too high, consider setting MTU=1420 in wg0.conf'
fi
echo ''
echo '=== Routing Table (wg0 entries) ==='
ip route show table main | grep wg0 || echo 'No routes via wg0 found'
echo ''
echo '=== Recent Journal Errors ==='
journalctl -u wg-quick@wg0 --since '10 minutes ago' --no-pager -q 2>/dev/null | tail -20
echo ''
echo '=== Quick Port Test (from this machine) ==='
# Edit SERVER_IP and PORT to match your setup
SERVER_IP="${1:-<server-public-ip>}"
PORT="${2:-51820}"
if command -v nc &>/dev/null; then
timeout 3 nc -zvu "$SERVER_IP" "$PORT" 2>&1 && echo "OK: UDP $PORT reachable" || echo "WARN: UDP $PORT not reachable from this host"
else
echo 'INFO: nc not installed, skipping port test'
fi
echo ''
echo '=== Public Key Derivation (verify matches peer config) ==='
if [ -f /etc/wireguard/privatekey ]; then
echo -n 'Derived public key: '
wg pubkey < /etc/wireguard/privatekey
else
echo 'INFO: /etc/wireguard/privatekey not found, check your key path'
fi
echo ''
echo 'Diagnostic complete.'Error Medic Editorial
The Error Medic Editorial team comprises senior DevOps engineers, SREs, and network specialists with hands-on experience across cloud infrastructure, Linux systems, and VPN technologies. Our guides are written from production troubleshooting experience and validated against official documentation and community-verified solutions.