Amazon EC2
aws ec2 run-instances
--image-id ami-12345678
--instance-type t3.micro
--user-data file://script.yamlInstalls WireGuard + OpenVPN + stunnel. Generates configs under /var/lib/vpn/clients. To use stunnel, install locally, copy stunnel-client.conf and modify client-direct.ovpn to point to 127.0.0.1 instead of your server. Make sure you replace the SSH authorized keys with your own.
#cloud-config
# Secure VPN Server Cloud-Init Configuration
#
# BEFORE DEPLOYMENT:
# 1. Replace the example SSH keys below with your actual public keys
# 2. Generate your SSH key: ssh-keygen -t ed25519 -C "your_email@example.com"
# 3. Copy your public key: cat ~/.ssh/id_ed25519.pub
# 4. Paste it in the ssh_authorized_keys sections below
#
# AFTER DEPLOYMENT:
# - Connect via SSH: ssh -p 2222 vpnuser@YOUR_SERVER_IP
# - Download VPN configs: scp -P 2222 -r vpnuser@YOUR_SERVER_IP:/var/lib/vpn/clients/ ./
# SSH Keys Configuration - Add your SSH public keys here
# Keys are defined in the users section below for the vpnuser account
package_update: true
package_upgrade: true
# User configuration
users:
- name: vpnuser
shell: /bin/bash
sudo: ['ALL=(ALL) NOPASSWD:ALL']
ssh_authorized_keys:
# Replace these example keys with your actual SSH public keys:
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEXAMPLE1234567890abcdefghijklmnopqrstuvwxyz user@example.com"
- "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQEXAMPLE1234567890abcdefghijklmnopqrstuvwxyz user2@example.com"
packages:
- wireguard
- openvpn
- easy-rsa
- stunnel4
- iptables-persistent
- curl
- fail2ban
- unattended-upgrades
write_files:
# Enable IP forwarding
- path: /etc/sysctl.d/99-ip-forward.conf
content: |
net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1
# Security hardening
net.ipv4.conf.all.send_redirects=0
net.ipv4.conf.default.send_redirects=0
net.ipv4.conf.all.accept_redirects=0
net.ipv4.conf.default.accept_redirects=0
net.ipv4.conf.all.secure_redirects=0
net.ipv4.conf.default.secure_redirects=0
permissions: '0644'
# Secure firewall rules with DROP default policy
- path: /etc/iptables/rules.v4
content: |
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
# Allow loopback
-A INPUT -i lo -j ACCEPT
-A OUTPUT -o lo -j ACCEPT
# Allow established and related connections
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
# Rate limiting for SSH (hardened port)
-A INPUT -p tcp --dport 2222 -m conntrack --ctstate NEW -m recent --set --name ssh
-A INPUT -p tcp --dport 2222 -m conntrack --ctstate NEW -m recent --update --seconds 60 --hitcount 4 --name ssh -j DROP
-A INPUT -p tcp --dport 2222 -j ACCEPT
# Rate limiting for VPN ports
-A INPUT -p udp --dport 51820 -m conntrack --ctstate NEW -m limit --limit 10/min --limit-burst 20 -j ACCEPT
-A INPUT -p tcp --dport 1194 -m conntrack --ctstate NEW -m limit --limit 10/min --limit-burst 20 -j ACCEPT
-A INPUT -p tcp --dport 443 -m conntrack --ctstate NEW -m limit --limit 20/min --limit-burst 50 -j ACCEPT
# VPN traffic forwarding with security
-A FORWARD -i wg0 -o eth0 -j ACCEPT
-A FORWARD -i tun0 -o eth0 -j ACCEPT
-A FORWARD -i eth0 -o wg0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -i eth0 -o tun0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
# Drop invalid packets
-A INPUT -m conntrack --ctstate INVALID -j DROP
-A FORWARD -m conntrack --ctstate INVALID -j DROP
COMMIT
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
# NAT rules for VPN traffic
-A POSTROUTING -s 10.66.66.0/24 ! -d 10.66.66.0/24 -o eth0 -j MASQUERADE
-A POSTROUTING -s 10.8.0.0/24 ! -d 10.8.0.0/24 -o eth0 -j MASQUERADE
COMMIT
permissions: '0644'
# Fail2ban configuration for VPN protection
- path: /etc/fail2ban/jail.d/vpn.conf
content: |
[sshd]
enabled = true
port = 2222
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
[openvpn]
enabled = true
port = 1194,443
filter = openvpn
logpath = /var/log/openvpn/server.log
maxretry = 5
bantime = 1800
permissions: '0644'
# Secure stunnel config template
- path: /etc/stunnel/stunnel.conf.template
content: |
pid = /var/run/stunnel4/stunnel.pid
setuid = stunnel4
setgid = stunnel4
foreground = no
debug = 3
output = /var/log/stunnel4/stunnel.log
# Security options
options = NO_SSLv2
options = NO_SSLv3
options = CIPHER_SERVER_PREFERENCE
ciphersuites = TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256
[openvpn-ssl]
accept = 443
connect = 127.0.0.1:1194
cert = /etc/stunnel/stunnel.pem
# Require client certificates for mutual TLS
verify = 2
CAfile = /etc/stunnel/ca.pem
permissions: '0644'
# Automatic security updates
- path: /etc/apt/apt.conf.d/20auto-upgrades
content: |
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
permissions: '0644'
# SSH hardening configuration
- path: /etc/ssh/sshd_config.d/99-hardening.conf
content: |
# Change default port (update firewall rules accordingly)
Port 2222
# Protocol and encryption
Protocol 2
# Authentication
PermitRootLogin no
PasswordAuthentication no
ChallengeResponseAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey
# Login restrictions
MaxAuthTries 3
MaxSessions 2
LoginGraceTime 30
# Disable unused authentication methods
KerberosAuthentication no
GSSAPIAuthentication no
UsePAM yes
# Network settings
ClientAliveInterval 300
ClientAliveCountMax 2
TCPKeepAlive no
# Disable dangerous features
AllowAgentForwarding no
AllowTcpForwarding no
X11Forwarding no
PermitTunnel no
GatewayPorts no
PermitUserEnvironment no
# Logging
LogLevel VERBOSE
SyslogFacility AUTH
# Restrict users (adjust as needed)
AllowUsers vpnuser
DenyUsers root
# Crypto settings
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha2-256,hmac-sha2-512
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512
# Host key algorithms
HostKeyAlgorithms ssh-ed25519,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,rsa-sha2-256,rsa-sha2-512
permissions: '0644'
runcmd:
# Apply sysctl settings
- sysctl -p /etc/sysctl.d/99-ip-forward.conf
# Auto-detect primary network interface
- |
PRIMARY_IFACE=$(ip route | awk '/default/ { print $5 ; exit }')
sed -i "s/eth0/$PRIMARY_IFACE/g" /etc/iptables/rules.v4
# Load iptables rules
- iptables-restore < /etc/iptables/rules.v4
- systemctl enable netfilter-persistent
# Create VPN directories (user is created automatically by cloud-init)
- mkdir -p /var/lib/vpn/clients /var/lib/vpn/server
- chmod 700 /var/lib/vpn /var/lib/vpn/clients /var/lib/vpn/server
- chown -R vpnuser:vpnuser /var/lib/vpn
# === WireGuard setup ===
- mkdir -p /etc/wireguard
- chmod 700 /etc/wireguard
# Generate WireGuard keys with better entropy
- |
umask 077
wg genkey > /etc/wireguard/server.key
wg pubkey < /etc/wireguard/server.key > /etc/wireguard/server.pub
wg genkey > /var/lib/vpn/clients/wg-client.key
wg pubkey < /var/lib/vpn/clients/wg-client.key > /var/lib/vpn/clients/wg-client.pub
chown vpnuser:vpnuser /var/lib/vpn/clients/wg-client.*
# Create WireGuard server config
- |
SERVER_KEY=$(cat /etc/wireguard/server.key)
CLIENT_PUB=$(cat /var/lib/vpn/clients/wg-client.pub)
cat > /etc/wireguard/wg0.conf <> vars < /var/lib/vpn/client_name.txt
- openvpn --genkey secret /etc/openvpn/ta.key
- chmod 600 /etc/openvpn/ta.key /etc/openvpn/easy-rsa/pki/private/*
# Secure OpenVPN server config
- |
mkdir -p /var/log/openvpn
cat > /etc/openvpn/server.conf <<'EOF'
port 1194
proto tcp
dev tun
ca /etc/openvpn/easy-rsa/pki/ca.crt
cert /etc/openvpn/easy-rsa/pki/issued/server.crt
key /etc/openvpn/easy-rsa/pki/private/server.key
dh /etc/openvpn/easy-rsa/pki/dh.pem
# Strong crypto
auth SHA512
cipher AES-256-GCM
ncp-ciphers AES-256-GCM:AES-128-GCM
tls-version-min 1.2
tls-auth /etc/openvpn/ta.key 0
topology subnet
server 10.8.0.0 255.255.255.0
# Security options
remote-cert-tls client
tls-server
# Networking
push "redirect-gateway def1 bypass-dhcp"
push "dhcp-option DNS 9.9.9.9"
push "dhcp-option DNS 149.112.112.112"
keepalive 10 120
persist-key
persist-tun
# Privileges
user nobody
group nogroup
# Logging
status /var/log/openvpn/status.log
log-append /var/log/openvpn/server.log
verb 3
mute 20
explicit-exit-notify 1
EOF
- systemctl enable openvpn@server
- systemctl start openvpn@server
# === Secure stunnel setup ===
- |
# Get public IP with fallback
PUBLIC_IP=$(curl -s --max-time 10 ifconfig.me || curl -s --max-time 10 icanhazip.com || hostname -I | awk '{print $1}')
# Generate stunnel server certificate using OpenVPN CA (not self-signed)
cd /etc/openvpn/easy-rsa
STUNNEL_SERVER="stunnel-server-$(openssl rand -hex 4)"
./easyrsa gen-req $STUNNEL_SERVER nopass
./easyrsa sign-req server $STUNNEL_SERVER
# Create stunnel certificate bundle (cert + key)
cat /etc/openvpn/easy-rsa/pki/issued/$STUNNEL_SERVER.crt \
/etc/openvpn/easy-rsa/pki/private/$STUNNEL_SERVER.key > /etc/stunnel/stunnel.pem
chmod 600 /etc/stunnel/stunnel.pem
# Copy CA for client verification (same CA as OpenVPN)
cp /etc/openvpn/easy-rsa/pki/ca.crt /etc/stunnel/ca.pem
# Generate client certificate for stunnel mutual TLS
STUNNEL_CLIENT="stunnel-client-$(openssl rand -hex 4)"
./easyrsa gen-req $STUNNEL_CLIENT nopass
./easyrsa sign-req client $STUNNEL_CLIENT
# Create client certificate bundle for stunnel
cat /etc/openvpn/easy-rsa/pki/issued/$STUNNEL_CLIENT.crt \
/etc/openvpn/easy-rsa/pki/private/$STUNNEL_CLIENT.key > /var/lib/vpn/clients/client.pem
chmod 600 /var/lib/vpn/clients/client.pem
# Use secure stunnel config
cp /etc/stunnel/stunnel.conf.template /etc/stunnel/stunnel.conf
# Start services with proper ordering
- systemctl enable stunnel4 fail2ban
- sleep 5 # Allow OpenVPN to fully start
- systemctl start stunnel4
- systemctl start fail2ban
# Generate client configurations securely
- |
PUBLIC_IP=$(curl -s --max-time 10 ifconfig.me || curl -s --max-time 10 icanhazip.com || hostname -I | awk '{print $1}')
CLIENT_NAME=$(cat /var/lib/vpn/client_name.txt)
# WireGuard client config
CLIENT_KEY=$(cat /var/lib/vpn/clients/wg-client.key)
SERVER_PUB=$(cat /etc/wireguard/server.pub)
cat > /var/lib/vpn/clients/wg-client.conf < /var/lib/vpn/clients/client-direct.ovpn <
$(cat /etc/openvpn/easy-rsa/pki/ca.crt)
$(sed -n '/BEGIN CERTIFICATE/,/END CERTIFICATE/p' /etc/openvpn/easy-rsa/pki/issued/$CLIENT_NAME.crt)
$(cat /etc/openvpn/easy-rsa/pki/private/$CLIENT_NAME.key)
$(cat /etc/openvpn/ta.key)
EOF
# Secure stunnel client config
cat > /var/lib/vpn/clients/stunnel-client.conf < /var/lib/vpn/clients/SECURITY-README.md </dev/null || true
chmod 640 /var/log/stunnel4/* 2>/dev/null || true
# Remove unnecessary packages
apt autoremove -y
# Update package database
apt update
# Status check
- sleep 15
- systemctl status wg-quick@wg0 openvpn@server stunnel4 fail2ban --no-pager aws ec2 run-instances
--image-id ami-12345678
--instance-type t3.micro
--user-data file://script.yamldoctl compute droplet create
--image ubuntu-22-04-x64
--size s-1vcpu-1gb
--user-data-file script.yaml
my-dropletgcloud compute instances create
my-instance
--metadata-from-file
user-data=script.yaml