How to set up WireGuard as an iOS VPN configuration in order to tunnel all traffic from an iOS or iPadOS device through a Linux server.
While there are many configurations possible, my intentation was to secure the traffic from my iOS device by routing it securely through a Linux server. As such, this HowTo will show you how to set-up an initial working configuration, which you can customize afterward.
Most likely things will be missing, such as how to configure the firewall or the init
scripts on your Linux distribution. As it won't be possible to cover all Linux
flavors this HowTo
will attempt to give you a working WireGuard
configuration, leaving it up to you to fill-in the blanks. Feel free to write a comment to touch upon your specific situation to help others that might stumble upon this HowTo :)
Note: In these configuration examples I use
10.8.0.1/24
and10.8.0.2/24
. However, as WireGuard IPs may not overlap your other network interfaces. If your existing interfaces use these ranges, you need to use different ranges for yourWireGuard
configurations._
WireGuard® describes it self as an extremely simple yet fast and modern VPN that utilizes state-of-the-art cryptography. It aims to be faster, simpler, leaner, and more useful than IPsec, while avoiding the massive headache. It intends to be considerably more performant than OpenVPN. WireGuard is designed as a general purpose VPN for running on embedded interfaces and super computers alike, fit for many different circumstances. Initially released for the Linux kernel, it is now cross-platform (Windows, macOS, BSD, iOS, Android) and widely deployable. It is currently under heavy development, but already it might be regarded as the most secure, easiest to use, and simplest VPN solution in the industry.
Traditionally, you would set up a VPN servers and connect clients to the server to route (some of) their traffic via the VPN.
WireGuard is different as there is no real separation between clients and servers. Instead, in WireGuard all devices are peer
s, using pre-defined public / private keys to connect to eachother and to encrypt traffic between them. Configuring IPs (e.g. AllowdIPs
) defines what traffic will be routed through the tunnel (from the sending end) and what traffic will be accepted on the receiving end. The [Interface]
section of a peer's configuration configures the WireGuard
interface for that peer
, and the one or more [Peer]
configuration sections in a peer's configuration configures all the peer
s it will connect to / accept connections from.
For your understanding, WireGuard is more like setting up an SSH tunnel and routing traffic over the tunnel, than setting up a VPN (like OpenVPN).
By design WireGuard does not log anything, so it can be a bit harder to debug. In order to debug WireGuard, firstly, your kernel need to be configured with dynamic debugging support. Secondly you need to use WireGuard as a kernel module. On Ubuntu, this is already the case. On other Linux flavors where you compile the kernel yourself, such as Gentoo Linux, you may need to customize your kernel configuration.
Starting with kernel 5.6, Wireguard is included in the upstream kernel sources. It is enabled via the following menuconfig option:
Enable CONFIG_WIREGUARD
:
Device Drivers --->
[*] Network device support --->
[*] Network core driver support
<M> WireGuard secure network tunnel
[*] Debugging checks and verbose messages
Enable CONFIG_DYNAMIC_DEBUG
dynamic debugging:
Kernel Hacking --->
printk and dmesg options --->
[*] Enable dynamic printk() support
As mentioned above, your kernel needs to support dynamic debugging and the WireGuard kernel extension needs to be used as a module.
Enable debugging:
$ echo 'module wireguard +p' | sudo tee /sys/kernel/debug/dynamic_debug/control
You can inspect the debug output using dmesg
(or your system log):
$ dmesg
Disable debugging:
$ echo 'module wireguard -p' | sudo tee /sys/kernel/debug/dynamic_debug/control
Use your package manager to install WireGuard (e.g. apt install wireguard
on Debian
derived installations or emerge wireguard
on Gentoo Linux
).
Configuring your server, or better, your Linux peer:
$ cd /etc/wireguard
And set the directory mask:
$ umask 077
First generate the public and private key pair for your Linux peer:
$ wg genkey | tee privatekey | wg pubkey > publickey
Copy your privatekey and create a new wg0.conf
file in /etc/wireguard
:
/etc/wireguard/wg0.conf:
[Interface]
Address = 10.8.0.1/24
SaveConfig = false
ListenPort = 51820
PrivateKey = <your private key>
And set up your init script and enable the WireGuard interface wg0
to be enabled at boot:
Gentoo Linux
:
$ ln -s /etc/init.d/wg-quick /etc/init.d/wg-quick.wg0
$ rc-update add wg-quick.wg0 default
At this point you can bring up your wg0
interface:
Gentoo Linux
:
$ /etc/init.d/wg-quick.wg0 start
Your Linux server now has a new wg0
interface with IP address 10.8.0.1
, which you can ping.
$ wg
interface: wg0
public key: <the public key of your linux peer>
private key: (hidden)
listening port: 51820
$ ifconfig
...
wg0: flags=209<UP,POINTOPOINT,RUNNING,NOARP> mtu 1420
inet 10.8.0.1 netmask 255.255.255.0 destination 10.8.0.1
unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 txqueuelen 1000 (UNSPEC)
RX packets 37689 bytes 5130384 (4.8 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 141857 bytes 175519208 (167.3 MiB)
TX errors 0 dropped 21 overruns 0 carrier 0 collisions 0
$ ping -c 1 10.8.0.1
PING 10.8.0.1 (10.8.0.1) 56(84) bytes of data.
64 bytes from 10.8.0.1: icmp_seq=1 ttl=64 time=0.124 ms
--- 10.8.0.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.124/0.124/0.124/0.000 ms
The iOS App is easiest set up using a QR code. Personally I prefer to keep the peer configuration stored on the server for easy safe keeping and QR-code generation, so here I will do the same.
On your Linux server, create a new directory to hold your peer
configurations. This directory or the files stored within are not used by the server configuration (your Linux peer), but just by you.
Start by creating a new peers
configuration directory:
$ cd /etc/wireguard
$ mkdir -p peers/FooPhone
$ cd peers/FooPhone
Then in the new peers/FooPhone
directory, create a public / private key pair for your iOS client (or iOS peer) FooPhone
:
$ wg genkey | tee privatekey | wg pubkey > publickey
Create a new configuration, using the newly created FooPhone
private key, and the public key of your Linux server (Linux peer):
/etc/wireguard/peers/FooPhone/FooPhone.conf:
[Interface]
PrivateKey = <FooPhone private key>
Address = 10.8.0.2/24
DNS = 1.1.1.1, 1.0.0.1
[Peer]
PublicKey = <Linux peer public key>
Endpoint = your-linux-peer.com:51820
AllowedIPs = 0.0.0.0/0
#AllowedIPs = 10.8.0.0/24
PersistentKeepalive = 30
Now head back to your Linux peer's wg0.conf
and add the iOS Peer
to the configuration as a new peer:
/etc/wireguard/wg0.conf:
[Interface]
Address = 10.8.0.1/24
SaveConfig = false
ListenPort = 51820
PrivateKey = <linux peer's private key>
[Peer]
PublicKey = <FooPhone public key>
AllowedIPs = 10.8.0.2/32
Restart wireguard to enable the peer and confirm that you see the peer configuration:
$ /etc/init.d/wg-quick.wg0 restart
$ wg
interface: wg0
public key: <Linux peer public key>
private key: (hidden)
listening port: 51820
peer: <FooPhone / iOS peer public key>
allowed ips: 10.8.0.2/32
Lastly, ensure the firewall on your Linux server will allow UDP connections to port 51820. For example, using NFT you could use something like this:
table inet filter {
chain input {
...
udp dport 51820 meta nftrace set 1
iifname "eno0" udp dport 51820 accept comment "accept all connections from WAN to WireGuard"
iifname "wg0" tcp dport 53 ip saddr 10.0.0.1 accept comment "Allow DNS for VPN"
iifname "wg0" udp dport 53 ip saddr 10.0.0.1 accept comment "Allow DNS for VPN"
}
chain output {
...
}
chain forward {
...
iifname "wg0" oifname "eno0" counter packets 0 bytes 0 goto wg0_to_internet
oifname "wg0" iifname "eno0" counter packets 0 bytes 0 goto internet_to_wg0
iifname "wg0" oifname "wg0" counter packets 0 bytes 0 goto wg0_to_wg0
}
chain wg0_to_internet {
counter packets 0 bytes 0 accept comment "policy"
}
chain internet_to_wg0 {
counter packets 0 bytes 0 jump from_any_to_tunnel_peers
counter packets 0 bytes 0 drop comment "policy"
}
chain wg0_to_wg0 {
counter packets 0 bytes 0 jump from_any_to_tunnel_peers
counter packets 0 bytes 0 drop comment "policy"
}
chain from_any_to_tunnel_peers {
}
}
Now your configuration is done.
Configuring the WireGuard iOS app:
Install the WireGuard iOS app if you have not yet done so.
Then, on your Linux server (the Linux peer) execute the following command to generate a QR code in your terminal emulator for your FooPhone
iOS Peer:
$ qrencode -t ansiutf8 -r /etc/wireguard/peers/FooPhone/FooPhone.conf
In the WireGuard iOS app, tap the +
item in the navigation bar, select Create from QR code
and scan the generated QR code
. Give the new configuration some name that makes sense to you (for example home
) and perform the next steps to have the new configuration saved as a VPN profile on your FooPhone
iPhone.
If you want to debug the connection, this is probably where you would want to enable dynamic debugging:
$ echo 'module wireguard +p' | sudo tee /sys/kernel/debug/dynamic_debug/control
If you toggle the newly created configuration on your FooPhone
iPhone to On
, WireGuard
on your iPhone should perform a handshake with your Linux peer.
Ensure you see the new entries endpoint
, latest handshake
and transfer
on your Linux server.
$ wg
interface: wg0
public key: <Linux peer public key>
private key: (hidden)
listening port: 51820
peer: <FooPhone / iOS peer public key>
endpoint: <public ip of your FooPhone>:20109
allowed ips: 10.8.0.2/32
latest handshake: 0 hour, 1 minutes, 48 seconds ago
transfer: 0.89 MiB received, 1.39 MiB sent
$ dmesg
...
[140710.234202] wireguard: wg0: Receiving handshake initiation from peer 1 <FooPhone public IP>:20108)
[140710.234220] wireguard: wg0: Sending handshake response to peer 1 (<FooPhone public IP>:20108)
[140710.236466] wireguard: wg0: Keypair 29 destroyed for peer 1
[140710.236477] wireguard: wg0: Keypair 31 created for peer 1
[140710.276811] wireguard: wg0: Receiving keepalive packet from peer 1 (<FooPhone public IP>:20108)
[140728.961078] wireguard: wg0: Receiving keepalive packet from peer 1 (<FooPhone public IP>:20108)
Now you should be able to ping
the IP address of FooPhone
from your Linux server (peer):
$ ping -c 1 10.8.0.2
PING 10.8.0.2 (10.8.0.2) 56(84) bytes of data.
64 bytes from 10.8.0.2: icmp_seq=1 ttl=64 time=268 ms
--- 10.8.0.2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 268.309/268.309/268.309/0.000 ms
This is where I will stop as the connection has been established. As FooPhone
has been configured with AllowedIPs = 0.0.0.0/0
, all traffic from your iPhone will be routed via the WireGuard tunnel. The server does not limit this, but you could defined a smaller range or even a single IP on the server (e.g. linux peer)'s configuration.
For the rest ensure your firewall, routing and NAT configuration on your Linux server are configured to allow connections to UDP 51820, and to forward / masquerade traffic from wg0
.
WireGuard
is very fast, but it's limited by your Linux Server's upstream speed. In case you use an asynchronous connection, like Cable or DSL, the bandwidth of your iPhone peer is limited by the maximum upstream speed of your Linux peer's Cable / DSL connection.