Setting up WireGuard between Linux and iOS

WireGuard is a modern VPN that’s designed to be easy to configure, performant, and secure. The ease-of-configuration is really important. If you’ve ever set up IPsec, you know what I mean. OpenVPN isn’t awful, but it isn’t good, either. WireGuard has both a Linux kernel implementation as well as a Go-based portable implementation that works on Mac and iOS. Official Windows support doesn’t exist yet, but is on the way.

I couldn’t find any information on configuring WireGuard to work with iOS. Here’s what I did to get it working.

On Linux:

Designated a /24 subnet in the RFC1918 space and set up wg0 according to the Wireguard quick start documentation. I configured it in /etc/network/interfaces like so:

auto wg0
     iface wg0 inet static
     address 10.108.45.1
     netmask 255.255.255.0
     pre-up ip link add $IFACE type wireguard
     pre-up wg setconf $IFACE /etc/wireguard/$IFACE.conf
     post-down ip link del $IFACE</pre>

Created /etc/wireguard/wg0.conf:

[Interface]
PrivateKey = my-private-key-goes-here
ListenPort = port-goes-here

# iPhone
[Peer]
PublicKey = public-key-goes-here
AllowedIPs = client-ip-address-goes-here/32</pre>

AllowedIPs is a bit of a misnomer. It’s not just an ACL for incoming packets; It’s also used to determine what traffic to route to each peer. The important thing here is to use /32 addresses in the AllowedIPs of the peers. If you use the entire /24 subnet, only the first peer using that subnet will work.

The iOS client side is where I had the most trouble. Part of the trouble is that the iOS app doesn’t show the state of the client. The other part is that since WireGuard is connection-less, even a bogus config will show up as active when you enable it.

Here’s what worked for me on iOS:

I created a key pair using the iOS app and put the public key in my Linux wg0.conf and restarted that interface.

Assign an address to the client from the /24 subnet. This needs to match the AllowedIPs line on the server. I used a /24 netmask on the iOS device, but /32 should work, too. I set the Listen port to 5555 to make it easy to verify incoming traffic using Wireshark, but that’s optional. A DNS server should be specified, because the existing one probably won’t be reachable with the VPN up. (Enabling Exclude Private IPs might fix that depending on the network you’re on.)

On the iOS peer config, enter the public key of the server and set the endpoint IP:port. For Allowed IPs, use 0.0.0.0/0. This will cause all traffic to be routed through the VPN endpoint while it’s active.

That’s it. Now activate the VPN and send some traffic through it. The ‘wg’ command on the Linux peer should show a handshake and data transferred in and out. To make Internet access work from the iOS device, you’ll probably want to set up NAT on the Linux peer.

Note that WireGuard is silent on the wire by default, so you won’t see a handshake unless you force traffic through it. The easiest way to do that is to use Safari to try to connect to the Linux peer’s IP address. (It doesn’t matter if it doesn’t have a web server running.) Using 0.0.0.0/0 for Allowed IPs on the client essentially forces a connection handshake because the iOS device will start sending traffic to the world through it on its own.

WireGuard roams peers between IPs effortlessly. Obviously one endpoint must have a fixed IP:port, but a peer roaming between Wi-Fi networks and LTE works beautifully.