Error Medic

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.

Last updated:
Last verified:
2,173 words
Key Takeaways
  • 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.
Fix Approaches Compared
MethodWhen to UseTimeRisk
Verify firewall rules (iptables/nftables/ufw)Port appears blocked, nc -zvu times out5 minLow — read-only diagnosis, additive rule
Regenerate keypair and update peersHandshake never completes, keys were rotated10 minMedium — all peers must update simultaneously
Change WireGuard listen port to 443/UDPISP or captive portal blocks non-standard UDP5 minLow — requires firewall rule update on server
Enable IP forwarding + MASQUERADE ruleClients connect but cannot reach internet/LAN5 minLow — standard routing config
Rebuild interface (wg-quick down/up)Stale routes, kernel module inconsistency2 minLow — brief tunnel outage
Use wg-quick with DNS= overrideDNS leaks or resolution fails inside tunnel5 minLow — may affect host resolver
Switch to TCP encapsulation (udptunnel/udp2raw)UDP fully blocked by restrictive network30 minMedium — 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 show reports latest handshake: never or a handshake timestamp older than 3 minutes
  • ping to 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@wg0 shows RTNETLINK 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

bash
#!/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.'
E

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.

Sources

Related Articles in WireGuard

Explore More Networking Guides