Script for automatic setup of an IPsec VPN server, with both IPsec/L2TP and Cisco IPsec on Ubuntu 16.04/14.04 and Debian 9/8. Works on any dedicated server or virtual private server (VPS) except OpenVZ.
It can also be used as Amazon EC2 "user data" with the official Ubuntu LTS or Debian AMIs.
» Related tutorial: IPsec VPN Server Auto Setup with Libreswan
Alternative VPN script for CentOS/RHEL
↓ ↓ ↓ Scroll down for the script ↓ ↓ ↓
Copyright (C) 2014-2017 Lin Song
Based on the work of Thomas Sarlandie (Copyright 2012)
This work is licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported License
Attribution required: please include my name in any derivative and let me know how you have improved it!
#!/bin/sh | |
# | |
# Script for automatic setup of an IPsec VPN server on Ubuntu LTS and Debian. | |
# Works on any dedicated server or virtual private server (VPS) except OpenVZ. | |
# | |
# DO NOT RUN THIS SCRIPT ON YOUR PC OR MAC! | |
# | |
# The latest version of this script is available at: | |
# https://github.com/hwdsl2/setup-ipsec-vpn | |
# | |
# Copyright (C) 2014-2017 Lin Song <linsongui@gmail.com> | |
# Based on the work of Thomas Sarlandie (Copyright 2012) | |
# | |
# This work is licensed under the Creative Commons Attribution-ShareAlike 3.0 | |
# Unported License: http://creativecommons.org/licenses/by-sa/3.0/ | |
# | |
# Attribution required: please include my name in any derivative and let me | |
# know how you have improved it! | |
# ===================================================== | |
# Define your own values for these variables | |
# - IPsec pre-shared key, VPN username and password | |
# - All values MUST be placed inside 'single quotes' | |
# - DO NOT use these characters within values: \ " ' | |
YOUR_IPSEC_PSK='' | |
YOUR_USERNAME='' | |
YOUR_PASSWORD='' | |
# Important notes: https://git.io/vpnnotes | |
# Setup VPN clients: https://git.io/vpnclients | |
# ===================================================== | |
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" | |
SYS_DT="$(date +%Y-%m-%d-%H:%M:%S)"; export SYS_DT | |
exiterr() { echo "Error: $1" >&2; exit 1; } | |
exiterr2() { echo "Error: 'apt-get install' failed." >&2; exit 1; } | |
conf_bk() { /bin/cp -f "$1" "$1.old-$SYS_DT" 2>/dev/null; } | |
bigecho() { echo; echo "## $1"; echo; } | |
check_ip() { | |
IP_REGEX="^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$" | |
printf %s "$1" | tr -d '\n' | grep -Eq "$IP_REGEX" | |
} | |
os_type="$(lsb_release -si 2>/dev/null)" | |
if [ -z "$os_type" ]; then | |
[ -f /etc/os-release ] && os_type="$(. /etc/os-release && echo "$ID")" | |
[ -f /etc/lsb-release ] && os_type="$(. /etc/lsb-release && echo "$DISTRIB_ID")" | |
fi | |
if ! printf %s "$os_type" | head -n 1 | grep -qiF -e ubuntu -e debian -e raspbian; then | |
exiterr "This script only supports Ubuntu/Debian." | |
fi | |
if [ "$(sed 's/\..*//' /etc/debian_version)" = "7" ]; then | |
exiterr "This script does not support Debian 7 (Wheezy)." | |
fi | |
if [ -f /proc/user_beancounters ]; then | |
echo "Error: This script does not support OpenVZ VPS." >&2 | |
echo "Try OpenVPN: https://github.com/Nyr/openvpn-install" >&2 | |
exit 1 | |
fi | |
if [ "$(id -u)" != 0 ]; then | |
exiterr "Script must be run as root. Try 'sudo sh $0'" | |
fi | |
NET_IFACE=${VPN_NET_IFACE:-'eth0'} | |
DEF_IFACE="$(route 2>/dev/null | grep '^default' | grep -o '[^ ]*$')" | |
[ -z "$DEF_IFACE" ] && DEF_IFACE="$(ip -4 route list 0/0 2>/dev/null | grep -Po '(?<=dev )(\S+)')" | |
if_state1=$(cat "/sys/class/net/$DEF_IFACE/operstate" 2>/dev/null) | |
if [ -z "$VPN_NET_IFACE" ] && [ -n "$if_state1" ] && [ "$if_state1" != "down" ]; then | |
if ! grep -qs raspbian /etc/os-release; then | |
case "$DEF_IFACE" in | |
wl*) | |
cat 1>&2 <<EOF | |
Error: Default network interface '$DEF_IFACE' detected. | |
>> DO NOT RUN THIS SCRIPT ON YOUR PC OR MAC! << | |
If you are certain that this script is running on a server, re-run it with: | |
sudo VPN_NET_IFACE="$DEF_IFACE" sh "$0" | |
EOF | |
exit 1 | |
;; | |
esac | |
fi | |
NET_IFACE="$DEF_IFACE" | |
fi | |
if_state2=$(cat "/sys/class/net/$NET_IFACE/operstate" 2>/dev/null) | |
if [ -z "$if_state2" ] || [ "$if_state2" = "down" ] || [ "$NET_IFACE" = "lo" ]; then | |
printf "Error: Network interface '%s' is not available.\n" "$NET_IFACE" >&2 | |
if [ -z "$VPN_NET_IFACE" ]; then | |
cat 1>&2 <<EOF | |
Unable to detect your server's default network interface. | |
You may manually re-run this script with: | |
sudo VPN_NET_IFACE="your_default_network_interface" sh "$0" | |
EOF | |
fi | |
exit 1 | |
fi | |
[ -n "$YOUR_IPSEC_PSK" ] && VPN_IPSEC_PSK="$YOUR_IPSEC_PSK" | |
[ -n "$YOUR_USERNAME" ] && VPN_USER="$YOUR_USERNAME" | |
[ -n "$YOUR_PASSWORD" ] && VPN_PASSWORD="$YOUR_PASSWORD" | |
if [ -z "$VPN_IPSEC_PSK" ] && [ -z "$VPN_USER" ] && [ -z "$VPN_PASSWORD" ]; then | |
bigecho "VPN credentials not set by user. Generating random PSK and password..." | |
VPN_IPSEC_PSK="$(LC_CTYPE=C tr -dc 'A-HJ-NPR-Za-km-z2-9' < /dev/urandom | head -c 16)" | |
VPN_USER=vpnuser | |
VPN_PASSWORD="$(LC_CTYPE=C tr -dc 'A-HJ-NPR-Za-km-z2-9' < /dev/urandom | head -c 16)" | |
fi | |
if [ -z "$VPN_IPSEC_PSK" ] || [ -z "$VPN_USER" ] || [ -z "$VPN_PASSWORD" ]; then | |
exiterr "All VPN credentials must be specified. Edit the script and re-enter them." | |
fi | |
if printf %s "$VPN_IPSEC_PSK $VPN_USER $VPN_PASSWORD" | LC_ALL=C grep -q '[^ -~]\+'; then | |
exiterr "VPN credentials must not contain non-ASCII characters." | |
fi | |
case "$VPN_IPSEC_PSK $VPN_USER $VPN_PASSWORD" in | |
*[\\\"\']*) | |
exiterr "VPN credentials must not contain the following characters: \\ \" '" | |
;; | |
esac | |
bigecho "VPN setup in progress... Please be patient." | |
# Create and change to working dir | |
mkdir -p /opt/src | |
cd /opt/src || exiterr "Cannot enter /opt/src." | |
bigecho "Populating apt-get cache..." | |
export DEBIAN_FRONTEND=noninteractive | |
apt-get -yq update || exiterr "'apt-get update' failed." | |
bigecho "Installing packages required for setup..." | |
apt-get -yq install wget dnsutils openssl || exiterr2 | |
apt-get -yq install iproute gawk grep sed net-tools || exiterr2 | |
bigecho "Trying to auto discover IP of this server..." | |
cat <<'EOF' | |
In case the script hangs here for more than a few minutes, | |
use Ctrl-C to interrupt. Then edit it and manually enter IP. | |
EOF | |
# In case auto IP discovery fails, enter server's public IP here. | |
PUBLIC_IP=${VPN_PUBLIC_IP:-''} | |
# Try to auto discover IP of this server | |
[ -z "$PUBLIC_IP" ] && PUBLIC_IP=$(dig @resolver1.opendns.com -t A -4 myip.opendns.com +short) | |
# Check IP for correct format | |
check_ip "$PUBLIC_IP" || PUBLIC_IP=$(wget -t 3 -T 15 -qO- http://ipv4.icanhazip.com) | |
check_ip "$PUBLIC_IP" || exiterr "Cannot find valid public IP. Edit the script and manually enter it." | |
bigecho "Installing packages required for the VPN..." | |
apt-get -yq install libnss3-dev libnspr4-dev pkg-config libpam0g-dev \ | |
libcap-ng-dev libcap-ng-utils libselinux1-dev \ | |
libcurl4-nss-dev flex bison gcc make \ | |
libnss3-tools libevent-dev || exiterr2 | |
apt-get -yq install ppp xl2tpd || exiterr2 | |
bigecho "Installing Fail2Ban to protect SSH..." | |
apt-get -yq install fail2ban || exiterr2 | |
bigecho "Compiling and installing Libreswan..." | |
swan_ver=3.21 | |
swan_file="libreswan-$swan_ver.tar.gz" | |
swan_url1="https://github.com/libreswan/libreswan/archive/v$swan_ver.tar.gz" | |
swan_url2="https://download.libreswan.org/$swan_file" | |
if ! { wget -t 3 -T 30 -nv -O "$swan_file" "$swan_url1" || wget -t 3 -T 30 -nv -O "$swan_file" "$swan_url2"; }; then | |
exiterr "Cannot download Libreswan source." | |
fi | |
/bin/rm -rf "/opt/src/libreswan-$swan_ver" | |
tar xzf "$swan_file" && /bin/rm -f "$swan_file" | |
cd "libreswan-$swan_ver" || exiterr "Cannot enter Libreswan source dir." | |
cat > Makefile.inc.local <<'EOF' | |
WERROR_CFLAGS = | |
USE_DNSSEC = false | |
EOF | |
if [ "$(packaging/utils/lswan_detect.sh init)" = "systemd" ]; then | |
apt-get -yq install libsystemd-dev || exiterr2 | |
fi | |
NPROCS="$(grep -c ^processor /proc/cpuinfo)" | |
[ -z "$NPROCS" ] && NPROCS=1 | |
make "-j$((NPROCS+1))" -s base && make -s install-base | |
# Verify the install and clean up | |
cd /opt/src || exiterr "Cannot enter /opt/src." | |
/bin/rm -rf "/opt/src/libreswan-$swan_ver" | |
if ! /usr/local/sbin/ipsec --version 2>/dev/null | grep -qF "$swan_ver"; then | |
exiterr "Libreswan $swan_ver failed to build." | |
fi | |
bigecho "Creating VPN configuration..." | |
L2TP_NET=${VPN_L2TP_NET:-'192.168.42.0/24'} | |
L2TP_LOCAL=${VPN_L2TP_LOCAL:-'192.168.42.1'} | |
L2TP_POOL=${VPN_L2TP_POOL:-'192.168.42.10-192.168.42.250'} | |
XAUTH_NET=${VPN_XAUTH_NET:-'192.168.43.0/24'} | |
XAUTH_POOL=${VPN_XAUTH_POOL:-'192.168.43.10-192.168.43.250'} | |
DNS_SRV1=${VPN_DNS_SRV1:-'8.8.8.8'} | |
DNS_SRV2=${VPN_DNS_SRV2:-'8.8.4.4'} | |
# Create IPsec (Libreswan) config | |
conf_bk "/etc/ipsec.conf" | |
cat > /etc/ipsec.conf <<EOF | |
version 2.0 | |
config setup | |
virtual-private=%v4:10.0.0.0/8,%v4:192.168.0.0/16,%v4:172.16.0.0/12,%v4:!$L2TP_NET,%v4:!$XAUTH_NET | |
protostack=netkey | |
nhelpers=0 | |
interfaces=%defaultroute | |
uniqueids=no | |
conn shared | |
left=%defaultroute | |
leftid=$PUBLIC_IP | |
right=%any | |
encapsulation=yes | |
authby=secret | |
pfs=no | |
rekey=no | |
keyingtries=5 | |
dpddelay=30 | |
dpdtimeout=120 | |
dpdaction=clear | |
ike=3des-sha1,3des-sha2,aes-sha1,aes-sha1;modp1024,aes-sha2,aes-sha2;modp1024,aes256-sha2_512 | |
phase2alg=3des-sha1,3des-sha2,aes-sha1,aes-sha2,aes256-sha2_512 | |
sha2-truncbug=yes | |
conn l2tp-psk | |
auto=add | |
leftprotoport=17/1701 | |
rightprotoport=17/%any | |
type=transport | |
phase2=esp | |
also=shared | |
conn xauth-psk | |
auto=add | |
leftsubnet=0.0.0.0/0 | |
rightaddresspool=$XAUTH_POOL | |
modecfgdns1=$DNS_SRV1 | |
modecfgdns2=$DNS_SRV2 | |
leftxauthserver=yes | |
rightxauthclient=yes | |
leftmodecfgserver=yes | |
rightmodecfgclient=yes | |
modecfgpull=yes | |
xauthby=file | |
ike-frag=yes | |
ikev2=never | |
cisco-unity=yes | |
also=shared | |
EOF | |
# Workaround for Raspbian 9 | |
if grep -qs 'Raspbian GNU/Linux 9' /etc/os-release; then | |
PRIVATE_IP=$(ip -4 route get 1 | awk '{print $NF;exit}') | |
check_ip "$PRIVATE_IP" && sed -i "s/left=%defaultroute/left=$PRIVATE_IP/" /etc/ipsec.conf | |
fi | |
# Specify IPsec PSK | |
conf_bk "/etc/ipsec.secrets" | |
cat > /etc/ipsec.secrets <<EOF | |
%any %any : PSK "$VPN_IPSEC_PSK" | |
EOF | |
# Create xl2tpd config | |
conf_bk "/etc/xl2tpd/xl2tpd.conf" | |
cat > /etc/xl2tpd/xl2tpd.conf <<EOF | |
[global] | |
port = 1701 | |
[lns default] | |
ip range = $L2TP_POOL | |
local ip = $L2TP_LOCAL | |
require chap = yes | |
refuse pap = yes | |
require authentication = yes | |
name = l2tpd | |
pppoptfile = /etc/ppp/options.xl2tpd | |
length bit = yes | |
EOF | |
# Set xl2tpd options | |
conf_bk "/etc/ppp/options.xl2tpd" | |
cat > /etc/ppp/options.xl2tpd <<EOF | |
+mschap-v2 | |
ipcp-accept-local | |
ipcp-accept-remote | |
ms-dns $DNS_SRV1 | |
ms-dns $DNS_SRV2 | |
noccp | |
auth | |
mtu 1280 | |
mru 1280 | |
proxyarp | |
lcp-echo-failure 4 | |
lcp-echo-interval 30 | |
connect-delay 5000 | |
EOF | |
# Create VPN credentials | |
conf_bk "/etc/ppp/chap-secrets" | |
cat > /etc/ppp/chap-secrets <<EOF | |
# Secrets for authentication using CHAP | |
# client server secret IP addresses | |
"$VPN_USER" l2tpd "$VPN_PASSWORD" * | |
EOF | |
conf_bk "/etc/ipsec.d/passwd" | |
VPN_PASSWORD_ENC=$(openssl passwd -1 "$VPN_PASSWORD") | |
cat > /etc/ipsec.d/passwd <<EOF | |
$VPN_USER:$VPN_PASSWORD_ENC:xauth-psk | |
EOF | |
bigecho "Updating sysctl settings..." | |
if ! grep -qs "hwdsl2 VPN script" /etc/sysctl.conf; then | |
conf_bk "/etc/sysctl.conf" | |
cat >> /etc/sysctl.conf <<EOF | |
# Added by hwdsl2 VPN script | |
kernel.msgmnb = 65536 | |
kernel.msgmax = 65536 | |
kernel.shmmax = 68719476736 | |
kernel.shmall = 4294967296 | |
net.ipv4.ip_forward = 1 | |
net.ipv4.tcp_syncookies = 1 | |
net.ipv4.conf.all.accept_source_route = 0 | |
net.ipv4.conf.default.accept_source_route = 0 | |
net.ipv4.conf.all.accept_redirects = 0 | |
net.ipv4.conf.default.accept_redirects = 0 | |
net.ipv4.conf.all.send_redirects = 0 | |
net.ipv4.conf.default.send_redirects = 0 | |
net.ipv4.conf.lo.send_redirects = 0 | |
net.ipv4.conf.$NET_IFACE.send_redirects = 0 | |
net.ipv4.conf.all.rp_filter = 0 | |
net.ipv4.conf.default.rp_filter = 0 | |
net.ipv4.conf.lo.rp_filter = 0 | |
net.ipv4.conf.$NET_IFACE.rp_filter = 0 | |
net.ipv4.icmp_echo_ignore_broadcasts = 1 | |
net.ipv4.icmp_ignore_bogus_error_responses = 1 | |
net.core.wmem_max = 12582912 | |
net.core.rmem_max = 12582912 | |
net.ipv4.tcp_rmem = 10240 87380 12582912 | |
net.ipv4.tcp_wmem = 10240 87380 12582912 | |
EOF | |
fi | |
bigecho "Updating IPTables rules..." | |
# Check if IPTables rules need updating | |
ipt_flag=0 | |
IPT_FILE="/etc/iptables.rules" | |
if ! grep -qs "hwdsl2 VPN script" "$IPT_FILE" \ | |
|| ! iptables -t nat -C POSTROUTING -s "$L2TP_NET" -o "$NET_IFACE" -j MASQUERADE 2>/dev/null \ | |
|| ! iptables -t nat -C POSTROUTING -s "$XAUTH_NET" -o "$NET_IFACE" -m policy --dir out --pol none -j MASQUERADE 2>/dev/null; then | |
ipt_flag=1 | |
fi | |
# Add IPTables rules for VPN | |
if [ "$ipt_flag" = "1" ]; then | |
service fail2ban stop >/dev/null 2>&1 | |
iptables-save > "$IPT_FILE.old-$SYS_DT" | |
iptables -I INPUT 1 -p udp --dport 1701 -m policy --dir in --pol none -j DROP | |
iptables -I INPUT 2 -m conntrack --ctstate INVALID -j DROP | |
iptables -I INPUT 3 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT | |
iptables -I INPUT 4 -p udp -m multiport --dports 500,4500 -j ACCEPT | |
iptables -I INPUT 5 -p udp --dport 1701 -m policy --dir in --pol ipsec -j ACCEPT | |
iptables -I INPUT 6 -p udp --dport 1701 -j DROP | |
iptables -I FORWARD 1 -m conntrack --ctstate INVALID -j DROP | |
iptables -I FORWARD 2 -i "$NET_IFACE" -o ppp+ -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT | |
iptables -I FORWARD 3 -i ppp+ -o "$NET_IFACE" -j ACCEPT | |
iptables -I FORWARD 4 -i ppp+ -o ppp+ -s "$L2TP_NET" -d "$L2TP_NET" -j ACCEPT | |
iptables -I FORWARD 5 -i "$NET_IFACE" -d "$XAUTH_NET" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT | |
iptables -I FORWARD 6 -s "$XAUTH_NET" -o "$NET_IFACE" -j ACCEPT | |
# Uncomment if you wish to disallow traffic between VPN clients themselves | |
# iptables -I FORWARD 2 -i ppp+ -o ppp+ -s "$L2TP_NET" -d "$L2TP_NET" -j DROP | |
# iptables -I FORWARD 3 -s "$XAUTH_NET" -d "$XAUTH_NET" -j DROP | |
iptables -A FORWARD -j DROP | |
iptables -t nat -I POSTROUTING -s "$XAUTH_NET" -o "$NET_IFACE" -m policy --dir out --pol none -j MASQUERADE | |
iptables -t nat -I POSTROUTING -s "$L2TP_NET" -o "$NET_IFACE" -j MASQUERADE | |
echo "# Modified by hwdsl2 VPN script" > "$IPT_FILE" | |
iptables-save >> "$IPT_FILE" | |
# Update rules for iptables-persistent | |
IPT_FILE2="/etc/iptables/rules.v4" | |
if [ -f "$IPT_FILE2" ]; then | |
conf_bk "$IPT_FILE2" | |
/bin/cp -f "$IPT_FILE" "$IPT_FILE2" | |
fi | |
fi | |
bigecho "Enabling services on boot..." | |
mkdir -p /etc/network/if-pre-up.d | |
cat > /etc/network/if-pre-up.d/iptablesload <<'EOF' | |
#!/bin/sh | |
iptables-restore < /etc/iptables.rules | |
exit 0 | |
EOF | |
for svc in fail2ban ipsec xl2tpd; do | |
update-rc.d "$svc" enable >/dev/null 2>&1 | |
systemctl enable "$svc" 2>/dev/null | |
done | |
if ! grep -qs "hwdsl2 VPN script" /etc/rc.local; then | |
if [ -f /etc/rc.local ]; then | |
conf_bk "/etc/rc.local" | |
sed --follow-symlinks -i '/^exit 0/d' /etc/rc.local | |
else | |
echo '#!/bin/sh' > /etc/rc.local | |
fi | |
cat >> /etc/rc.local <<'EOF' | |
# Added by hwdsl2 VPN script | |
(sleep 15 | |
service ipsec restart | |
service xl2tpd restart | |
echo 1 > /proc/sys/net/ipv4/ip_forward)& | |
exit 0 | |
EOF | |
fi | |
bigecho "Starting services..." | |
# Reload sysctl.conf | |
sysctl -e -q -p | |
# Update file attributes | |
chmod +x /etc/rc.local /etc/network/if-pre-up.d/iptablesload | |
chmod 600 /etc/ipsec.secrets* /etc/ppp/chap-secrets* /etc/ipsec.d/passwd* | |
# Apply new IPTables rules | |
iptables-restore < "$IPT_FILE" | |
# Restart services | |
service fail2ban restart 2>/dev/null | |
service ipsec restart 2>/dev/null | |
service xl2tpd restart 2>/dev/null | |
cat <<EOF | |
================================================ | |
IPsec VPN server is now ready for use! | |
Connect to your new VPN with these details: | |
Server IP: $PUBLIC_IP | |
IPsec PSK: $VPN_IPSEC_PSK | |
Username: $VPN_USER | |
Password: $VPN_PASSWORD | |
Write these down. You'll need them to connect! | |
Important notes: https://git.io/vpnnotes | |
Setup VPN clients: https://git.io/vpnclients | |
================================================ | |
EOF | |
exit 0 |