fedora-csb-system-manager

WireGuard iOS Troubleshooting Guide

Overview

This guide addresses handshake failures when connecting iOS devices to a WireGuard VPN server. iOS WireGuard clients have specific requirements and quirks that differ from other platforms.

Background

WireGuard uses a simple handshake protocol:

  1. Client sends handshake initiation to server
  2. Server responds with handshake response
  3. Both sides derive session keys
  4. Data packets can flow

When handshakes fail, you’ll see:

  • “Handshake did not complete” in iOS WireGuard app
  • No “latest handshake” timestamp on server (wg show)
  • Connection timeout without error details

Common iOS-Specific Causes

1. DNS Configuration Issues (MOST COMMON)

Problem: iOS handles DNS differently than other platforms. The WireGuard iOS app may refuse to connect or fail silently if DNS is misconfigured.

Symptoms:

  • Connection appears to activate but no traffic flows
  • Works on some networks but not others
  • Interface goes “up” but handshake never completes

Root Causes:

  • No DNS server specified in configuration
  • Multiple DNS servers listed (iOS sometimes rejects this)
  • DNS server is within VPN tunnel range (10.100.0.x) but unreachable during handshake
  • DNS64/NAT64 networks (some cellular carriers) conflict with WireGuard DNS

Solution:

[Interface]
DNS = 8.8.8.8

Best Practices:

  • Use a single, external DNS server (8.8.8.8, 1.1.1.1, or 9.9.9.9)
  • Avoid using multiple DNS servers
  • Don’t use VPN-internal DNS during initial connection
  • For privacy, Cloudflare (1.1.1.1) or Quad9 (9.9.9.9) are good alternatives to Google

2. MTU Size Mismatches

Problem: iOS devices, especially on cellular networks, are sensitive to MTU (Maximum Transmission Unit) size.

Symptoms:

  • Handshake completes but no data transfers
  • Works on WiFi but fails on cellular
  • Small packets work but large ones don’t (can ping but can’t browse)

Root Causes:

  • Default WireGuard MTU is 1420 bytes
  • Cellular networks often have lower MTU (1280-1400)
  • WireGuard adds 80 bytes of overhead to IP packets
  • Path MTU discovery doesn’t work well with iOS + cellular

Solution:

[Interface]
MTU = 1280

Testing Different MTU Values:

# On server after connection established:
ping -M do -s 1232 10.100.0.5  # Test with 1280 MTU (1232 + 28 ICMP + 20 IP = 1280)
ping -M do -s 1372 10.100.0.5  # Test with 1420 MTU (1372 + 28 + 20 = 1420)

If lower values work but higher ones fail, reduce MTU in iOS config.

Recommended Values:

  • Start with: 1280 (works on most cellular networks)
  • If problems persist: try 1280, 1360, 1400, 1412
  • WiFi-only: can usually use 1420 (default)

3. Cellular Network Routing Issues

Problem: iOS on cellular data routes traffic differently than WiFi, and some carriers block or NAT UDP traffic aggressively.

Symptoms:

  • Works perfectly on home WiFi
  • Fails immediately on cellular data
  • No traffic appears at server firewall
  • Sometimes works, then stops working

Root Causes:

  • Carrier-grade NAT (CGNAT) blocks or mangles UDP
  • Carrier firewall blocks UDP port 51820
  • iOS routing table prioritizes cellular routes incorrectly
  • Mobile data restrictions or VPN policies

Diagnosis:

# On iOS device, check if UDP packets reach server
# Have someone run on server while you connect:
sudo tcpdump -i any udp port 51820

# If you see no packets from your IP, it's a carrier issue

Solutions:

  • Test on different cellular network if possible
  • Try alternative WireGuard port (some carriers block 51820)
  • Use TCP-based tunnel (Shadowsocks/V2Ray) if UDP is completely blocked
  • Contact carrier if VPN is required for work

Workarounds:

  • Enable VPN only on WiFi (on-demand rules)
  • Use backup VPN provider for cellular
  • Switch to different carrier with better UDP support

4. Server NAT/Masquerading Misconfiguration

Problem: Server firewall rules don’t properly NAT traffic from VPN clients.

Symptoms:

  • Handshake completes (shows “latest handshake” on server)
  • Can ping server VPN IP (10.100.0.1) but nothing else
  • No internet access through VPN

Root Causes:

  • Missing or incorrect MASQUERADE rule
  • Wrong source subnet in iptables rule
  • Missing IP forwarding (net.ipv4.ip_forward = 0)

Check Server Config:

# Verify IP forwarding is enabled
sysctl net.ipv4.ip_forward
# Should return: net.ipv4.ip_forward = 1

# Check NAT rule exists
sudo iptables -t nat -L POSTROUTING -v -n | grep 10.100.0.0

# Should see something like:
# MASQUERADE  all  --  *  *  10.100.0.0/24  0.0.0.0/0

Fix in NixOS (modules/wireguard-server.nix):

networking.firewall.extraCommands = ''
  iptables -t nat -A POSTROUTING -s 10.100.0.0/24 -j MASQUERADE
  iptables -A FORWARD -i wg+ -j ACCEPT
'';

Common Mistakes:

  • -s10.100.0.0/24 (missing space after -s)
  • -s 10.100.0.0/32 (wrong subnet mask - /32 is single IP, should be /24)
  • Using SNAT instead of MASQUERADE on dynamic IP servers

5. Key Mismatches

Problem: Public/private keys don’t match between client and server config.

Symptoms:

  • “Handshake did not complete” immediately
  • No handshake attempts visible on server
  • iOS app shows “Invalid key” or just fails silently

Root Causes:

  • Typo when copying public key
  • QR code scanning error
  • Key regeneration on one side but not updated on other
  • PreSharedKey mismatch (if using PSK)

Verification:

On iOS device:

  1. Open WireGuard app
  2. Tap on tunnel configuration
  3. Note the Public key shown under Interface

On server:

# Check what public key is configured for this peer
sudo wg show wg0 peers | grep -A5 "1wzFG60hlrAoSYcRKApsH+WK3Zyz8ljdLglb/8JbuW0="

In your NixOS config (globals.nix):

hokkaido = {
  net = {
    vpn = {
      pubkey = "1wzFG60hlrAoSYcRKApsH+WK3Zyz8ljdLglb/8JbuW0=";
      ips = [ "10.100.0.5" ];
    };
  };
};

Solutions:

  • Delete and recreate tunnel on iOS (generates new key pair)
  • Update globals.nix with new public key
  • Rebuild and deploy server config
  • Double-check keys match exactly (case-sensitive, watch for l vs 1, O vs 0)

6. Server Firewall Blocking UDP

Problem: Server firewall doesn’t allow UDP port 51820.

Symptoms:

  • Connection times out
  • No errors, just hangs
  • Works from some networks but not others (if server IP is geo-blocked)

Check Firewall:

# On NixOS server
sudo iptables -L INPUT -v -n | grep 51820

# Should see:
# ACCEPT  udp  --  *  *  0.0.0.0/0  0.0.0.0/0  udp dpt:51820

Cloud Provider Firewalls:

If running on DigitalOcean/AWS/GCP, check:

  • DigitalOcean: Cloud Firewalls in dashboard
  • AWS: Security Groups must allow UDP 51820 inbound
  • GCP: VPC Firewall Rules must allow UDP 51820

Fix in NixOS:

networking.firewall.allowedUDPPorts = [ 51820 ];

7. iOS App-Specific Bugs

Problem: Bugs in specific versions of WireGuard iOS app.

Symptoms:

  • Works on other devices but not iOS
  • Works on older iOS version but not after update
  • Inconsistent behavior

Solutions:

  • Update WireGuard iOS app to latest version
  • Check WireGuard iOS GitHub issues for known bugs
  • Try toggling “On-Demand” activation off and on
  • Force-quit and restart WireGuard app
  • Reboot iOS device (clears network stack caches)
  • Delete and recreate tunnel configuration

For device hokkaido (10.100.0.5) connecting to server kerkouane:

[Interface]
PrivateKey = <GENERATE_ON_IOS_DEVICE>
Address = 10.100.0.5/24
DNS = 8.8.8.8
MTU = 1280

[Peer]
PublicKey = +H3fxErP9HoFUrPgU19ra9+GDLQw+VwvLWx3lMct7QI=
Endpoint = 167.99.17.238:51820
AllowedIPs = 10.100.0.0/24
PersistentKeepalive = 25

Field Explanations:

  • PrivateKey: Generated automatically by iOS app (never share this)
  • Address: Your VPN IP from globals.nix
  • DNS: Single external DNS server (Google, Cloudflare, or Quad9)
  • MTU: Reduced for cellular compatibility
  • PublicKey: Server’s public key from globals.nix (kerkouane)
  • Endpoint: Server’s public IP and WireGuard port
  • AllowedIPs: VPN subnet to route through tunnel
  • PersistentKeepalive: Keep NAT mappings alive (important for mobile)

For Full Tunnel (Route all traffic through VPN):

AllowedIPs = 0.0.0.0/0, ::/0

For Split Tunnel (Only VPN subnet):

AllowedIPs = 10.100.0.0/24

Step-by-Step Troubleshooting

Step 1: Verify Server Configuration

# SSH to kerkouane
ssh kerkouane

# Check WireGuard interface is up
sudo wg show

# Should see:
# interface: wg0
#   public key: +H3fxErP9HoFUrPgU19ra9+GDLQw+VwvLWx3lMct7QI=
#   private key: (hidden)
#   listening port: 51820

# Check peer is configured
sudo wg show wg0 peers

# Should include:
# peer: 1wzFG60hlrAoSYcRKApsH+WK3Zyz8ljdLglb/8JbuW0=
#   allowed ips: 10.100.0.5/32

# Check firewall allows UDP 51820
sudo iptables -L INPUT -v -n | grep 51820

# Check NAT rule
sudo iptables -t nat -L POSTROUTING -v -n | grep 10.100.0

Step 2: Test Server Connectivity

# From any other machine (not iOS yet)
nc -u -v 167.99.17.238 51820

# Or test with another WireGuard client if available

Step 3: Configure iOS Client

  1. Open WireGuard app on iOS
  2. Tap “Add a tunnel” → “Create from scratch”
  3. Set name: kerkouane-vpn
  4. Copy Public key shown (you’ll need this for server config)
  5. Tap “Add peer”
  6. Enter server configuration
  7. Save tunnel

Step 4: Update Server with iOS Public Key

# In globals.nix, update hokkaido's pubkey if changed
hokkaido = {
  net = {
    vpn = {
      pubkey = "PASTE_PUBLIC_KEY_FROM_IOS_APP_HERE";
      ips = [ "10.100.0.5" ];
    };
  };
};
# Rebuild server config
make host/kerkouane/switch

Step 5: Attempt Connection from iOS

  1. Activate tunnel in WireGuard app
  2. Watch for status change
  3. If it shows “Connected”, check connection
# On iOS, open Safari and try:
http://10.100.0.1  # Server VPN IP
http://whoami.sbr.pm  # Public service through VPN

Step 6: Monitor Server Logs

# On server, watch for handshake attempts
sudo journalctl -u systemd-networkd -f

# Or watch WireGuard interface
watch -n 1 sudo wg show

# Look for:
# peer: 1wzFG60hlrAoSYcRKApsH+WK3Zyz8ljdLglb/8JbuW0=
#   endpoint: <YOUR_IP>:<PORT>
#   latest handshake: X seconds ago  # This should appear!
#   transfer: X.XX KiB received, Y.YY KiB sent

Step 7: Test Different Networks

# Try connection on:
1. Home WiFi
2. Mobile data (cellular)
3. Different WiFi network (coffee shop, etc.)

# Note which networks work/fail
# This helps identify carrier or firewall issues

Diagnostic Commands

On Server (kerkouane)

# Show full WireGuard status
sudo wg show all

# Show only latest handshakes
sudo wg show wg0 latest-handshakes

# Capture WireGuard traffic (run during connection attempt)
sudo tcpdump -i any -n udp port 51820

# Show connected peers
sudo wg show wg0 endpoints

# Check if packets are forwarded
sudo iptables -L FORWARD -v -n | grep wg

# Test routing from server to client (after handshake)
ping 10.100.0.5

On iOS Device

# No direct CLI on iOS, but you can:
1. Check WireGuard app logs (tap on tunnel, scroll down)
2. Use Network utility apps (iNetTools, Ping)
3. Check "Latest handshake" timestamp in app
4. Test with Safari: http://10.100.0.1

On Another Machine (for comparison)

# Create test config on Linux/Mac
sudo wg-quick up wg0

# Or with temporary interface
sudo ip link add dev wg0 type wireguard
sudo wg set wg0 private-key <(echo "TEST_PRIVATE_KEY")
sudo wg set wg0 peer +H3fxErP9HoFUrPgU19ra9+GDLQw+VwvLWx3lMct7QI= \
  endpoint 167.99.17.238:51820 \
  allowed-ips 10.100.0.0/24 \
  persistent-keepalive 25
sudo ip addr add 10.100.0.99/24 dev wg0
sudo ip link set wg0 up

# Test handshake
sudo wg show

Verification Checklist

After configuration changes, verify:

  • Server shows peer configured: sudo wg show wg0 peers
  • Firewall allows UDP 51820: sudo iptables -L INPUT -v -n | grep 51820
  • NAT rule is correct: sudo iptables -t nat -L POSTROUTING -v -n
  • IP forwarding enabled: sysctl net.ipv4.ip_forward returns 1
  • iOS config has DNS set: Check WireGuard app interface config
  • iOS config has MTU=1280: Check WireGuard app interface config
  • iOS public key matches server config: Compare app and globals.nix
  • Server public key matches iOS peer config: Compare globals.nix and app
  • Endpoint IP is correct: 167.99.17.238:51820
  • AllowedIPs is correct: 10.100.0.0/24 (or 0.0.0.0/0 for full tunnel)

Working Configuration Example

Server (kerkouane) - modules/wireguard-server.nix

{
  services.wireguard.server = {
    enable = true;
    ips = libx.wg-ips globals.machines.kerkouane.net.vpn.ips;
    peers = libx.generateWireguardPeers globals.machines;
  };

  boot.kernel.sysctl."net.ipv4.ip_forward" = 1;

  networking.firewall.extraCommands = ''
    iptables -t nat -A POSTROUTING -s 10.100.0.0/24 -j MASQUERADE
    iptables -A FORWARD -i wg+ -j ACCEPT
  '';

  networking.firewall.allowedUDPPorts = [ 51820 ];
  networking.firewall.trustedInterfaces = [ "wg0" ];
}

Client (iOS) - WireGuard App Config

[Interface]
PrivateKey = oK8qaDg9FXYSVwGp9NM8xz8VQm7JwLN7CwTQMb7Hv1E=
Address = 10.100.0.5/24
DNS = 8.8.8.8
MTU = 1280

[Peer]
PublicKey = +H3fxErP9HoFUrPgU19ra9+GDLQw+VwvLWx3lMct7QI=
Endpoint = 167.99.17.238:51820
AllowedIPs = 10.100.0.0/24
PersistentKeepalive = 25

Server Config (globals.nix)

hokkaido = {
  net = {
    vpn = {
      pubkey = "1wzFG60hlrAoSYcRKApsH+WK3Zyz8ljdLglb/8JbuW0=";
      ips = [ "10.100.0.5" ];
    };
  };
};

kerkouane = {
  net = {
    vpn = {
      pubkey = "+H3fxErP9HoFUrPgU19ra9+GDLQw+VwvLWx3lMct7QI=";
      ips = [ "10.100.0.1" ];
    };
  };
};

Advanced Troubleshooting

Packet Capture Analysis

# On server, capture handshake initiation
sudo tcpdump -i any -vvv -X udp port 51820

# Look for:
# - Packets FROM client IP TO server:51820 (initiation)
# - Packets FROM server:51820 TO client IP (response)
# - If you see initiation but no response → server issue
# - If you see no packets at all → network/firewall issue

Testing with Different Endpoints

# Try different port (if carrier blocks 51820)
# On server:
networking.wireguard.interfaces.wg0.listenPort = 443;
networking.firewall.allowedUDPPorts = [ 443 ];

# On iOS:
Endpoint = 167.99.17.238:443

Enable Debug Logging (iOS)

  1. Export tunnel config from WireGuard app
  2. Edit .conf file, add to [Interface]:
    # No debug flag in iOS, but you can:
    # - Check system Settings → Privacy → Analytics → Analytics Data
    # - Look for WireGuard crash logs
    

Test with Minimal Config

# Minimal iOS config to isolate issues
[Interface]
PrivateKey = <GENERATED>
Address = 10.100.0.5/32

[Peer]
PublicKey = +H3fxErP9HoFUrPgU19ra9+GDLQw+VwvLWx3lMct7QI=
Endpoint = 167.99.17.238:51820
AllowedIPs = 10.100.0.1/32
PersistentKeepalive = 25

If this works, gradually add: DNS, full subnet AllowedIPs, MTU changes.

Performance Tuning

Once working, optimize for performance:

Increase MTU (if stable)

# Test progressively higher MTU values
MTU = 1280  # Start here
MTU = 1360  # Try this
MTU = 1400  # Or this
MTU = 1420  # Default (best performance if it works)

Adjust Keepalive

# Default
PersistentKeepalive = 25

# For unstable cellular (more aggressive)
PersistentKeepalive = 15

# For stable WiFi only (reduce bandwidth)
PersistentKeepalive = 60

On-Demand Rules (iOS-specific)

Configure automatic VPN activation:

  1. In WireGuard app, tap tunnel
  2. Tap “Edit”
  3. Tap “On-Demand Activation”
  4. Configure rules:
    • WiFi: Connect to VPN when on untrusted WiFi
    • Cellular: Always connect on cellular
    • Ethernet: Connect when on any wired network

Example rules:

Connect On Demand:
  ☑ Cellular
  ☑ WiFi (except: Home_WiFi_SSID)
  ☐ Ethernet

Common Error Messages

“Handshake did not complete after 5 seconds”

Causes:

  • Server not reachable (firewall, wrong endpoint)
  • Key mismatch
  • Server not running WireGuard

Solutions: Check server status, verify endpoint IP/port, verify keys

“Unable to resolve endpoint”

Causes:

  • DNS resolution failure
  • No internet connection on iOS device
  • Invalid endpoint hostname

Solutions: Use IP address instead of hostname, check iOS internet connectivity

“Invalid key”

Causes:

  • Malformed base64 key
  • Wrong key length (WireGuard keys are 32 bytes, 44 chars in base64)

Solutions: Regenerate keys, check for copy-paste errors

Connection works but no internet

Causes:

  • NAT/masquerade misconfigured
  • DNS not set or incorrect
  • AllowedIPs too restrictive

Solutions: Check server MASQUERADE rule, add DNS to iOS config, verify AllowedIPs

References

Documentation

Troubleshooting Resources

Tools

Summary

Most common iOS WireGuard issues:

  1. Missing or incorrect DNS configuration
  2. MTU too high for cellular networks
  3. Server NAT/masquerade misconfigured
  4. Carrier blocking UDP or using aggressive NAT

Quick fixes that solve 90% of issues:

  • Add DNS = 8.8.8.8 to iOS config
  • Add MTU = 1280 to iOS config
  • Fix server NAT rule: -s 10.100.0.0/24 not /32
  • Verify keys match between client and server

Systematic troubleshooting:

  1. Verify server configuration and logs
  2. Test from known working client first
  3. Configure iOS with minimal settings
  4. Add features incrementally (DNS, MTU, full routing)
  5. Test on different networks (WiFi vs cellular)
  6. Monitor server during connection attempts

When in doubt, start fresh with minimal config and build up from there.