What's Actually Happening
WireGuard VPN tunnel fails to establish connection. Peers cannot complete handshake and traffic doesn't flow.
The Error You'll See
```bash $ wg show
interface: wg0 public key: abc123... private key: (hidden) listening port: 51820
peer: xyz789... endpoint: 203.0.113.50:51820 allowed ips: 10.0.0.2/32 latest handshake: 0 # No handshake! transfer: 0 B received, 0 B sent ```
No handshake message:
Peer has no recent handshakeHandshake timeout:
# wg show shows:
latest handshake: 3 minutes ago # Should be within 2-3 minutesConnection refused:
# In kernel logs:
dmesg | grep wireguard
# Shows: "Sending handshake initiation failed"Why This Happens
- 1.Wrong public key - Peer public key mismatch
- 2.Endpoint unreachable - Peer endpoint not accessible
- 3.Firewall blocking - UDP port 51820 blocked
- 4.Wrong listening port - Port mismatch between peers
- 5.Allowed IPs mismatch - Routing configuration wrong
- 6.Interface not up - WireGuard interface not started
Step 1: Check WireGuard Status
```bash # Check WireGuard interface: wg show
# Check all interfaces: wg show all
# Show specific interface: wg show wg0
# Check interface status: ip link show wg0
# Check if interface is up: ip addr show wg0
# Check routing: ip route show table all | grep wg0
# Check WireGuard configuration: cat /etc/wireguard/wg0.conf
# Check WireGuard service: systemctl status wg-quick@wg0
# Start interface: wg-quick up wg0
# Check kernel module: lsmod | grep wireguard
# Load module: modprobe wireguard
# Check WireGuard tools: which wg wg-quick ```
Step 2: Verify Public Keys
```bash # Check peer public key matches: wg show wg0
# On peer1, public key should match peer2's config # On peer2, public key should match peer1's config
# Generate new keys if needed: wg genkey | tee privatekey | wg pubkey > publickey
# Check private key in config: cat /etc/wireguard/wg0.conf | grep PrivateKey
# Verify public key derivation: cat privatekey | wg pubkey # Should match PublicKey in peer's config
# Common error: Copy public key to own config instead of peer's # Correct: Put peer's public key in [Peer] section
# Example correct config on peer1: [Interface] PrivateKey = peer1_private_key Address = 10.0.0.1/24 ListenPort = 51820
[Peer] PublicKey = peer2_public_key # Peer's public key! Endpoint = peer2_ip:51820 AllowedIPs = 10.0.0.2/32
# Update keys: wg set wg0 peer peer_public_key
# Remove wrong peer: wg set wg0 peer wrong_public_key remove ```
Step 3: Check Endpoint Connectivity
```bash # Test endpoint reachable: ping peer-endpoint-ip
# Test UDP port: nc -zuv peer-endpoint-ip 51820
# Or use nmap: nmap -sU -p 51820 peer-endpoint-ip
# Check endpoint in config: grep Endpoint /etc/wireguard/wg0.conf
# Correct endpoint format: Endpoint = 203.0.113.50:51820
# For dynamic endpoint (peer behind NAT): Endpoint = (empty or remove) # Peer will discover endpoint from incoming handshake
# Check peer's listening port: wg show wg0 listen-port
# Update endpoint: wg set wg0 peer peer_key endpoint new-ip:51820
# Check NAT traversal: # WireGuard handles NAT well but needs correct setup
# For peer behind NAT: # 1. Set PersistentKeepalive PersistentKeepalive = 25
# This sends keepalive every 25 seconds # Keeps NAT mapping open
# Add keepalive: wg set wg0 peer peer_key persistent-keepalive 25
# Check if peer can reach you: # Ensure your endpoint is correct on peer's side ```
Step 4: Check Firewall Configuration
```bash # Check firewall rules: iptables -L -n -v | grep 51820 iptables -L -n -v -t filter | grep wireguard
# Check UDP port allowed: iptables -I INPUT -p udp --dport 51820 -j ACCEPT
# Check forwarding allowed: iptables -I FORWARD -i wg0 -j ACCEPT iptables -I FORWARD -o wg0 -j ACCEPT
# Using ufw: ufw status | grep 51820 ufw allow 51820/udp
# Using firewalld: firewall-cmd --list-all | grep 51820 firewall-cmd --add-port=51820/udp --permanent firewall-cmd --reload
# Check NAT/masquerading if needed: iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
# Check nftables: nft list ruleset | grep 51820
# Allow WireGuard with nftables: nft add rule inet filter input udp dport 51820 accept
# Test firewall: # From another machine: nc -zuv your-ip 51820
# Check if port is listening: ss -ulnp | grep 51820 netstat -ulnp | grep 51820 ```
Step 5: Check Allowed IPs
```bash # Check allowed IPs configuration: wg show wg0 allowed-ips
# In config: grep AllowedIPs /etc/wireguard/wg0.conf
# Allowed IPs define what traffic goes through tunnel # Common configurations:
# Full tunnel (all traffic): AllowedIPs = 0.0.0.0/0
# Site-to-site (specific network): AllowedIPs = 10.0.1.0/24, 192.168.1.0/24
# Single peer: AllowedIPs = 10.0.0.2/32
# Check routing matches allowed IPs: ip route show | grep wg0
# Add route manually if missing: ip route add 10.0.0.2/32 dev wg0
# For full tunnel, check default route: ip route show default # Should route through wg0 if AllowedIPs = 0.0.0.0/0
# Update allowed IPs: wg set wg0 peer peer_key allowed-ips 10.0.0.0/24
# Multiple allowed IPs: AllowedIPs = 10.0.0.0/24, 192.168.0.0/24
# Check both IPv4 and IPv6 if used: AllowedIPs = 10.0.0.0/24, fd00::/64 ```
Step 6: Fix Interface Configuration
```bash # Check interface configuration: cat /etc/wireguard/wg0.conf
# Basic configuration template: [Interface] PrivateKey = your_private_key Address = 10.0.0.1/24 # VPN IP address ListenPort = 51820 # UDP port
[Peer] PublicKey = peer_public_key Endpoint = peer-ip:51820 AllowedIPs = 10.0.0.2/32 PersistentKeepalive = 25 # For NAT traversal
# Check address assigned: ip addr show wg0
# If no address: wg-quick down wg0 wg-quick up wg0
# Manual address assignment: ip addr add 10.0.0.1/24 dev wg0
# Check interface created: ip link show wg0
# Create interface manually: ip link add dev wg0 type wireguard
# Configure interface: wg set wg0 private-key privatekey_file listen-port 51820
# Check DNS configuration: grep DNS /etc/wireguard/wg0.conf # Or: PostUp = resolvconf dns %i
# Check MTU: grep MTU /etc/wireguard/wg0.conf # Default is auto-detected # Can set manually: MTU = 1420
# For PPPoE or encapsulation overhead: MTU = 1280 ```
Step 7: Check Kernel Module and Logs
```bash # Check WireGuard module: lsmod | grep wireguard
# Load module: modprobe wireguard
# Check module info: modinfo wireguard
# Check kernel support: cat /proc/modules | grep wireguard
# Check kernel logs: dmesg | grep -i wireguard
# Journalctl logs: journalctl -u wg-quick@wg0 -f
# Check system logs: tail -f /var/log/syslog | grep wireguard
# Enable verbose logging: # WireGuard is quiet by default # Use wg show for status
# Check for errors: dmesg | grep -E "wireguard|wg"
# Common errors: # "Failed to create interface" - module not loaded # "Permission denied" - need root/capabilities # "Network is unreachable" - endpoint unreachable
# Check capabilities: getcap /usr/bin/wg
# Set capabilities if needed: setcap cap_net_admin+ep /usr/bin/wg ```
Step 8: Test WireGuard Connection
```bash # Start interface: wg-quick up wg0
# Show status: wg show
# Expected output with handshake: peer: xyz789... endpoint: 203.0.113.50:51820 allowed ips: 10.0.0.2/32 latest handshake: 30 seconds ago # Should show timestamp transfer: 1.50 KiB received, 1.50 KiB sent
# Test ping through tunnel: ping 10.0.0.2 # Peer's VPN IP
# Test bandwidth: iperf3 -c 10.0.0.2
# Check routing: ip route get 10.0.0.2 # Should show: 10.0.0.2 dev wg0
# Monitor handshake: watch -n 1 wg show
# Test from peer: wg show wg0 latest-handshake
# Force new handshake: wg set wg0 peer peer_key endpoint peer-ip:51820
# Check packet flow: tcpdump -i wg0 -n ```
Step 9: Handle NAT Traversal
```bash # For peer behind NAT:
# 1. Use PersistentKeepalive: PersistentKeepalive = 25
# This keeps NAT mapping open
# 2. Remove endpoint if dynamic: # Let peer discover endpoint from incoming packets
# 3. Check NAT type: # WireGuard works with most NAT types # Issues with symmetric NAT may require fixes
# For symmetric NAT: # May need to use same port for outgoing
# Configure source port: # Set ListenPort to fixed value
ListenPort = 51820
# Check NAT mapping: # From peer with known endpoint: wg show wg0 endpoints
# For roaming support: # WireGuard handles IP changes automatically # If endpoint changes, handshake updates
# Multiple peers with NAT: # Each peer needs own PersistentKeepalive
# Check NAT traversal working: watch wg show
# Handshake should update after NAT changes ```
Step 10: WireGuard Verification Script
```bash # Create verification script: cat << 'EOF' > /usr/local/bin/check-wireguard.sh #!/bin/bash
echo "=== WireGuard Module ===" lsmod | grep wireguard || echo "Module not loaded"
echo "" echo "=== WireGuard Interfaces ===" ip link show type wireguard 2>/dev/null || echo "No WireGuard interfaces"
echo "" echo "=== Interface Status ===" wg show 2>/dev/null || echo "WireGuard not configured"
echo "" echo "=== Configuration Files ===" ls -la /etc/wireguard/*.conf 2>/dev/null || echo "No config files"
echo "" echo "=== Firewall Status ===" iptables -L -n 2>/dev/null | grep 51820 || echo "No iptables rule for 51820" ufw status 2>/dev/null | grep 51820 || true
echo "" echo "=== Listening Ports ===" ss -ulnp 2>/dev/null | grep 51820 || echo "Port 51820 not listening"
echo "" echo "=== Routing Table ===" ip route show | grep wg || echo "No wg routes"
echo "" echo "=== Peer Connectivity ===" # For each peer in config, test endpoint for peer in $(wg show peers 2>/dev/null); do endpoint=$(wg show peer $peer endpoint 2>/dev/null) if [ -n "$endpoint" ]; then echo "Peer $peer endpoint: $endpoint" ping -c 1 -W 2 $(echo $endpoint | cut -d: -f1) 2>/dev/null && echo " Reachable" || echo " Unreachable" fi done
echo "" echo "=== Recent Handshakes ===" wg show 2>/dev/null | grep "latest handshake" || echo "No handshakes"
echo "" echo "=== Kernel Logs ===" dmesg 2>/dev/null | grep -i wireguard | tail -5 || echo "No WireGuard kernel logs"
echo "" echo "=== Recommendations ===" echo "1. Verify public keys match between peers" echo "2. Check endpoint is reachable" echo "3. Allow UDP port 51820 in firewall" echo "4. Add PersistentKeepalive for NAT traversal" echo "5. Verify AllowedIPs routing configuration" echo "6. Check ListenPort matches on both peers" echo "7. Enable IP forwarding if needed" EOF
chmod +x /usr/local/bin/check-wireguard.sh
# Usage: /usr/local/bin/check-wireguard.sh ```
WireGuard Handshake Checklist
| Check | Expected |
|---|---|
| Public keys | Peer's key in config |
| Endpoint | Reachable and correct |
| Firewall | UDP 51820 allowed |
| Allowed IPs | Routing configured |
| Interface | Up with address |
| Handshake | Within 3 minutes |
| Keepalive | Set for NAT peers |
Verify the Fix
```bash # After fixing WireGuard handshake
# 1. Check interface up wg show wg0 // Interface visible
# 2. Check handshake wg show wg0 latest-handshake // Shows timestamp < 3 minutes
# 3. Test connectivity ping 10.0.0.2 // Packets through tunnel
# 4. Check transfer stats wg show transfer // Shows bytes transferred
# 5. Monitor connection watch wg show // Handshake updates regularly
# 6. Check routing ip route get 10.0.0.2 // Routes through wg0 ```
Related Issues
- [Fix OpenVPN Connection Failed](/articles/fix-openvpn-connection-failed)
- [Fix IPsec Tunnel Not Establishing](/articles/fix-ipsec-tunnel-not-establishing)
- [Fix SSH Connection Refused](/articles/fix-ssh-connection-refused)