WireGuard clients
Chapter 4 — WireGuard Clients
The server is running. Now connect to it. WireGuard has official client apps for Windows, macOS, iOS, and Android — all free, all using the same config file format you wrote in Chapter 3. This chapter walks through installing each one, importing a config, enabling the kill switch, and verifying the tunnel is working.
The Client Config — Quick Recap
Every client needs its own .conf file generated on the
server in Chapter 3. It looks like this — make sure you have it ready
before starting any of the platform sections below:
[Interface]
Address = 10.0.0.2/32 # this client's VPN IP
PrivateKey = Y2nL7rH8T4wX3vK9m... # client's private key
DNS = 10.0.0.1 # optional — use server as DNS
[Peer]
PublicKey = P3mK9vX2nL7rH8T4... # server public key
Endpoint = your.server.ip:51820
AllowedIPs = 0.0.0.0/0 # full tunnel
PersistentKeepalive = 25
Transfer the config file securely. It contains the
client's private key. Use a QR code displayed on-screen (Chapter 3),
copy it over SSH/SCP, or use an encrypted password manager. Never send
it over plain email, WhatsApp, or Slack.
Windows
🪟
Windows 10 / 11
Official WireGuard app — wireguard.com/install · also available via winget
- Install the app. Download from
wireguard.com/installor runwinget install WireGuard.WireGuardin PowerShell. The installer adds a WireGuard system service and a tray icon. - Import the config. Open WireGuard → click the arrow next to Add Tunnel → Import tunnel(s) from file → select your
client1.conffile. The tunnel appears in the list with its name taken from the filename. - Activate. Click the tunnel name → click Activate. The status changes to Active and a green indicator appears in the system tray.
- Enable the kill switch (recommended). Click Edit on the tunnel → tick Block all traffic when tunnel is not active (kill switch). This prevents any traffic leaving your machine if the VPN drops.
Command-line alternative (no GUI)
# copy config to the WireGuard directory
PS> Copy-Item client1.conf "C:\Program Files\WireGuard\Data\Configurations\"
# install and start the tunnel as a Windows service
PS> & "C:\Program Files\WireGuard\wireguard.exe" /installtunnelservice "C:\Program Files\WireGuard\Data\Configurations\client1.conf"
# start / stop via sc (service control)
PS> sc.exe start WireGuardTunnel$client1
PS> sc.exe stop WireGuardTunnel$client1
macOS
🍎
macOS 12 Monterey and later
Mac App Store · also available via Homebrew
- Install. Search WireGuard in the Mac App Store, or run
brew install wireguard-toolsfor the command-line version (no GUI, same as Linux). - Import. Open WireGuard → click the + button → Import tunnel(s) from file → select your
.conffile. macOS will ask for permission to add VPN configurations — click Allow. - Activate. Toggle the tunnel on. Status shows in the menu bar icon.
- On-Demand (optional). Click Edit → enable On-Demand → set rules, e.g. activate automatically when Wi-Fi is connected but not when on a trusted SSID. Useful for always-on protection on untrusted networks.
Homebrew / command-line (wg-quick)
# install wireguard-tools (includes wg-quick)
philip@mac:~$ brew install wireguard-tools
# copy config and bring tunnel up
philip@mac:~$ sudo cp client1.conf /etc/wireguard/wg0.conf
philip@mac:~$ sudo wg-quick up wg0
philip@mac:~$ sudo wg-quick down wg0
iOS
📱
iPhone / iPad — iOS 15+
App Store — free · deepest integration via QR code import
- Install WireGuard from the App Store.
- Import via QR code (easiest). On the server, run
qrencode -t ansiutf8 < /etc/wireguard/client1.conf→ hold your phone up to the screen → tap Allow to add the VPN profile. - Or import a file. Transfer the
.conffile to your phone via AirDrop, iCloud Drive, or the Files app → open it → iOS offers to import it into WireGuard. - Activate. Tap the toggle next to the tunnel name. iOS adds a VPN indicator to the status bar.
- On-Demand (optional). Tap the tunnel info icon → On-Demand Activation → choose Wi-Fi, cellular, or both. Useful for automatic activation on all networks except your home Wi-Fi.
Kill switch on iOS: iOS has no explicit kill switch
toggle in the WireGuard app, but when On-Demand is enabled
iOS re-establishes the tunnel automatically if it drops — effectively
achieving the same result. For a true kill switch, enable On-Demand
on all interfaces with no exclusions.
Android
🤖
Android 7+ (API 24+)
Google Play Store · also F-Droid for open-source build
- Install WireGuard from the Play Store (or F-Droid).
- Import via QR code. Tap the + button → Scan from QR code → scan the code generated by
qrencodeon the server. Name the tunnel and tap Create Tunnel. - Or import a file. Tap + → Import from file or archive → navigate to the
.conffile on the device. - Activate. Toggle the tunnel on. Android will prompt you to approve the VPN connection on first use.
- Enable the kill switch. Go to Android Settings → Network → VPN → tap the gear icon next to the WireGuard VPN → enable Always-on VPN and Block connections without VPN. This is Android's system-level kill switch — more reliable than any in-app toggle.
Android kill switch is system-level. Unlike the
in-app toggles on Windows and iOS, Android's "Block connections
without VPN" is enforced by the OS network stack — no traffic leaves
the device on any interface if the VPN is down, regardless of which
app is trying to send it.
Kill Switch Summary — Platform Comparison
Windows
Built-in
Tick "Block all traffic when tunnel is not active" in the tunnel Edit dialog
macOS
Limited
No kill switch in the GUI app. Use On-Demand rules or a firewall rule via
pfiOS
Via On-Demand
Enable On-Demand on all interfaces with no exclusions — iOS reconnects automatically
Android
System-level
Settings → Network → VPN → Always-on VPN + Block connections without VPN
Verifying the Connection on Any Platform
| Test | How to run it | Expected result |
|---|---|---|
| Public IP changed | Visit ifconfig.me or ipinfo.io |
Shows your VPN server's IP, not your real IP |
| Ping VPN server | ping 10.0.0.1 | Replies with low latency |
| DNS not leaking | Visit dnsleaktest.com → Extended test |
Only your VPN server or its upstream DNS visible |
| WebRTC leak | Visit browserleaks.com/webrtc |
No local IP exposed (or only VPN IP visible) |
| Server sees handshake | wg show | Peer shows "latest handshake: N seconds ago" |
Common Client Problems
Tunnel activates but no internet
IP forwarding not enabled on the server, or the iptables MASQUERADE rule is missing/wrong interface name.
On the server:
cat /proc/sys/net/ipv4/ip_forward should return 1. Check iptables -t nat -L for the POSTROUTING rule. Verify the interface name in PostUp matches your actual external interface.Handshake never completes
UDP port 51820 is blocked by a firewall between client and server, or the Endpoint IP/port in the client config is wrong.
Confirm the server's public IP in
Endpoint. Test UDP reachability: nc -u your.server.ip 51820. Check ufw status on the server. Try a different network (mobile data vs Wi-Fi) to isolate the blocking firewall.DNS leaking despite DNS setting
The
DNS = 10.0.0.1 line sets the resolver but the OS may still use its default DNS for non-tunnelled queries, or AllowedIPs doesn't include the DNS server's IP.Ensure
AllowedIPs = 0.0.0.0/0 (full tunnel). On Windows, check that the WireGuard interface's DNS is listed first in network adapter settings. Run dnsleaktest.com to confirm.Tunnel drops on mobile when screen locks
Mobile OS aggressively kills background network connections to save battery.
Ensure
PersistentKeepalive = 25 is set in the client config — this sends a UDP keepalive every 25 seconds to prevent NAT tables from timing out. Enable On-Demand on iOS for automatic reconnection.Wrong key — handshake rejected
The public key in the server's
[Peer] block doesn't match the client's actual private key, or keys were copy-pasted with extra whitespace.On the client, run
wg show and note the public key shown. Compare it to what's in the server's wg0.conf [Peer] block. Regenerate and re-import the config if they don't match.Can reach VPN server but not LAN devices
You're in split-tunnel mode (
AllowedIPs = 10.0.0.0/24) and LAN devices have a different subnet, or the server isn't routing to the LAN.Add the LAN subnet to
AllowedIPs on the client, e.g. 10.0.0.0/24, 192.168.0.0/24. Ensure the server has a route to the LAN (it does if it's on the LAN; it won't if it's a remote VPS).