Here’s how to set up a VPN that has all these features:
- Works natively on iOS and macOS
- Doesn't require you to manage certificates or keys
- Automatically connects when your device is online
It should also work on
Windows*, and Android via the StrongSwan app, but I haven’t tested it. Please post about your experiences using this guide with other devices in the comments!
*EDIT: unfortunately the settings as provided below don’t seem to work with Windows. If you manage to get it working, please post a comment with details below, to help other people who come to this page.
As with most of my posts, there are plenty of other places online that describe parts of this process, but nowhere that collects it all together. I’ve wanted to set up a VPN for some time because since last year, British ISPs have been legally required to collect information about all the web addresses that their customers visit and send it to a large number of government organisations, with little to no transparency or oversight. Paid VPN services exist, but they essentially mean that you’re trusting all of your internet traffic to a private company. Free VPN services also exist, but it’s pretty safe to say that they are scanning all of your internet traffic and selling information about it.
These instructions were tested with Debian Stretch but should work on recent versions of Ubuntu too. I’m going to assume that you’ve already done the base install of your server, sorted out IP addresses and DNS, and secured it properly. After all, there’s not much point in setting up a VPN if the server it runs on is wide open. The VPN settings I’m using here aren’t super-secure (they probably won’t protect from the NSA, for example), but they will protect you from being monitored by default. Someone with know-how and resources will have to make an active effort to spy on your traffic.
First of all, let’s sort out Let’s Encrypt.
apt-get install certbot certbot certonly --standalone -d vpn.example.com --register-unsafely-without-email
Replace the domain with yours, obviously. Standalone mode requires that (a) you’re not running a web server on this server/IP address and (b) port 80 is not firewalled off. If you have a web server running then you probably want the
--webroot mode instead. If port 80 is firewalled, you might want to check out the
--post-hook options for certbot renewals. Alternatively, you could try something like Mythic Beasts’ DNS plugin. If you want to get emails about failed renewals of your VPN certificate, skip the
--register-unsafely-without-email. You obviously lose some anonymity in that case. I just monitor the certificate from elsewhere, so I get a warning if it gets close to expiring.
Next, let’s install StrongSwan and configure it.
apt-get install strongswan libcharon-extra-plugins moreutils iptables-persistent nano /etc/ipsec.conf
Put these contents into the config file:
config setup charondebug="ike 1, knl 1, cfg 0" uniqueids=no conn ikev2-vpn auto=add compress=no type=tunnel keyexchange=ikev2 fragmentation=yes forceencaps=yes ike=aes256-sha1-modp1024,3des-sha1-modp1024! esp=aes256-sha1,3des-sha1! dpdaction=clear dpddelay=300s rekey=no left=%any email@example.com leftcert=/etc/letsencrypt/live/vpn.example.com/fullchain.pem leftsendcert=always leftsubnet=0.0.0.0/0 right=%any rightid=%any rightauth=eap-mschapv2 rightdns=2001:1608:10:25::1c04:b12f,2001:1608:10:25::9249:d69b,18.104.22.168,22.214.171.124 rightsourceip=10.11.12.0/24 rightsendcert=never eap_identity=%identity
Make sure you replace the parts in
bold. You might also want to change the
rightdns. I’m using the DNS.WATCH servers because I (maybe foolishly) trust them more with my privacy than Google. They also correctly return
NXDOMAIN when you try to look up a record that doesn’t exist, which is one of my bugbears with many free DNS servers.
Now you want to set your login details.
Put these contents in:
# This file holds shared secrets or RSA private keys for authentication. # # RSA private key for this host, authenticating it to any other host # which knows the public part. vpn.example.com : RSA /etc/letsencrypt/live/vpn.example.com/privkey.pem username %any% : EAP "password"
Again, replace the parts in
Now it’s time to configure the firewall. This is the main part where all of the guides I found I fell down. I still haven’t found an ideal solution (the firewall rules below set the default policy to
REJECT instead of
DROP), but it’s good enough. The rules below assume that you have no firewall up to start with. If you’ve already got a firewall, you’ll need to figure out how to integrate these rules with your existing ones. I don’t recommend trying to do this with
ufw as its support for advanced options is a lot more limited than raw
iptables -A POSTROUTING -s 10.11.12.0/24 -o eth0 -m policy --dir out --pol ipsec -j ACCEPT iptables -A POSTROUTING -s 10.11.12.0/24 -o eth0 -j MASQUERADE iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT iptables -A INPUT -p tcp -m tcp --dport 22 -j ACCEPT iptables -A INPUT -i lo -j ACCEPT iptables -A INPUT -p udp -m udp --dport 500 -j ACCEPT iptables -A INPUT -p udp -m udp --dport 4500 -j ACCEPT iptables -A INPUT -p tcp -m tcp --dport 80 -j ACCEPT iptables -A INPUT -p tcp -m tcp --dport 111 -j REJECT --reject-with icmp-port-unreachable iptables -A FORWARD -s 10.11.12.0/24 -m policy --dir in --pol ipsec --proto esp -j ACCEPT iptables -A FORWARD -s 10.11.12.0/24 -m policy --dir out --pol ipsec --proto esp -j ACCEPT iptables -A FORWARD -s 10.11.12.0/24 -o eth0 -p tcp -m policy --dir in --pol ipsec -m tcp --tcp-flags SYN,RST SYN -m tcpmss --mss 1361:1536 -j TCPMSS --set-mss 1360 netfilter-persistent save
Things to note:
- If you changed the
/etc/ipsec.conf, replace it accordingly in the firewall rules.
- Don’t forget to open any other ports you need. I’ve included
sshon its default port as I’m assuming that’s how you manage your server, if you don’t need it or you use a different port then update as appropriate.
- Port 80 is required for Let’s Encrypt renewals.
- Since there is no default
DROPrule, you definitely want to explicitly block access to port 111 for security (and anything else you might be running that shouldn’t be accessible to the whole internet).
- The final forward rule does some magic to help prevent packet fragmentation. Without it, your VPN might not work on some networks. I spent a long, long, loooong time trying to figure out why my VPN kept flaking out on one particular connection without this rule.
- Don’t forget to save your rules afterwards! Otherwise you’ll lose them every time you reboot the server.
Finally, a few changes to the kernel network settings.
nano /etc/sysctl.conf and make sure that it includes the following lines (create the line if the option isn’t there, or update it if it’s already present):
# Uncomment the next line to enable packet forwarding for IPv4 net.ipv4.ip_forward=1 # Do not accept ICMP redirects (prevent MITM attacks) net.ipv4.conf.all.accept_redirects = 0 # Do not send ICMP redirects (we are not a router) net.ipv4.conf.all.send_redirects = 0 net.ipv4.ip_no_pmtu_disc = 1
Reboot once you’ve finished these, and check that (a) the firewall rules are correctly set (
iptables -L), and (b) the kernel options are correct (check output of
If that all looks good, then your VPN should work, but you’ll need to manually configure it and it won’t automatically connect. To do that you need to create a configuration profile for your mobile device. Make a plain text file with the following contents:
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <!-- Set the name to whatever you like, it is used in the profile list on the device --> <key>PayloadDisplayName</key> <string>Name</string> <key>PayloadIdentifier</key> <!-- This is a reverse-DNS style unique identifier used to detect duplicate profiles --> <string>com.example.vpn</string> <!-- A globally unique identifier, use uuidgen on Linux/Mac OS X to generate it --> <key>PayloadUUID</key> <string>UUID</string> <key>PayloadType</key> <string>Configuration</string> <key>PayloadVersion</key> <integer>1</integer> <key>PayloadContent</key> <array> <!-- It is possible to add multiple VPN payloads with different identifiers/UUIDs and names --> <dict> <!-- This is an extension of the identifier given above --> <key>PayloadIdentifier</key> <string>com.example.vpn.conf</string> <!-- A globally unique identifier for this payload --> <key>PayloadUUID</key> <string>UUID</string> <key>PayloadType</key> <string>com.apple.vpn.managed</string> <key>PayloadVersion</key> <integer>1</integer> <key>UserDefinedName</key> <!-- This is the name of the VPN connection as seen in the VPN application later --> <string>Name</string> <key>VPNType</key> <string>IKEv2</string> <key>IKEv2</key> <dict> <!-- This is the hostname or IP address of VPN server. Chosing IP address can avoid issues with client DNS resolvers and speed up connection process. --> <key>RemoteAddress</key> <string>vpn.example.com</string> <!-- leftid in ipsec.conf --> <key>RemoteIdentifier</key> <string>vpn.example.com</string> <!-- OnDemand references: http://www.v2ex.com/t/137653 https://developer.apple.com/library/mac/featuredarticles/iPhoneConfigurationProfileRef/Introduction/Introduction.html Continue reading: https://github.com/iphoting/ovpnmcgen.rb --> <!-- AlwaysOn OnDemand Rule --> <key>OnDemandEnabled</key> <integer>1</integer> <key>OnDemandRules</key> <array> <dict> <key>Action</key> <string>Connect</string> </dict> </array> <key>DeadPeerDetectionRate</key> <string>High</string> <key>AuthenticationMethod</key> <string>Certificate</string> <key>NATKeepAliveInterval</key> <integer>30</integer> <key>NATKeepAliveOffloadEnable</key> <true/> <key>ExtendedAuthEnabled</key> <integer>1</integer> <!-- Username and password from ipsec.secrets --> <key>AuthName</key> <string>username</string> <key>AuthPassword</key> <string>password</string> </dict> </dict> </array> </dict> </plist>
Replace the bits in
bold as required. The
UUIDs can be generated using
uuidgen. Everything else should be self explanatory.
Finally, save the plain text file with the extension
vpn.mobileconfig. Then you can either email it to yourself, or put it on a web server and access it from the browser on your mobile device. It will prompt you to install the profile. Once that’s done, reboot the device and voilà! It should automatically connect to the VPN on startup. Make sure that you make the config file inaccessible once you’re done, as it contains your plaintext username and password.
It seems like the strongswan-plugin-eap-mschapv2 package was moved.
So just apt-get
libcharon-extra-plugins instead of
Thanks for the feedback! I have updated the article.
Hi, very good your post. I want to use the VPN in Windows 7 client. How can I export the Let's Encrypt certificate to use it on Windows 7?
To export the certificate, you just need to copy the file
/etc/letsencrypt/life/example.com/fullchain.pem from your server (where example.com is the domain name you're using). Sorry to say, I don't know what the required encryption algorithm settings are to work with Windows. The settings above work with Apple devices, but when I tried them with Windows 7 a few years ago it didn't work. It's definitely possible to get it working with Windows, but I don't use Windows and don't have any Windows devices for testing so I haven't had the chance to update the instructions to work with it. If you are able to work out suitable settings, please post them here and I will update the article.
What If I’ve already set up letsencrypt for a web server?
Sorry for the excessively long delay in responding! In this case, you just need to enter the location of your existing Let's Encrypt certificate in the
/etc/ipsec.secrets configuration files. The VPN and the webserver will need to be on the same server for this to work.
Works perfect on MacOS and iOS. Great guide!