Skip to content

Instantly share code, notes, and snippets.

@Suzhou65
Last active April 1, 2026 04:23
Show Gist options
  • Select an option

  • Save Suzhou65/b63a90d846c12efceee4bae3c31fde8e to your computer and use it in GitHub Desktop.

Select an option

Save Suzhou65/b63a90d846c12efceee4bae3c31fde8e to your computer and use it in GitHub Desktop.
OpenVPN server building memo

Debian setup OpenVPN server

Contents

Env

  • Debian 12.13 (Bookworm)
  • VPS with Public IPv4 Address available
  • IPv6 Address is selectively
  • Running Apache2 service for obfuscating

Step

0. Check Public IPv4/IPv6 Address

Check IP address

ip a

Output be like:

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host noprefixroute 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:0c:29:8d:49:9f brd ff:ff:ff:ff:ff:ff
    inet XXX.XXX.XXX.XXX/24 brd 139.162.71.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 2400:8902:0:1::/64 scope global dynamic mngtmpaddr 
       valid_lft 5349sec preferred_lft 1749sec
    inet6 fe80::XXXX:XXXX:XXXX:XXXX/64 scope link 
       valid_lft forever preferred_lft forever

Copy inet and inet6 WAN IPv4/IPv6 address.

1. Install Package

apt install ufw sslh openvpn easy-rsa -y

2. Initializing PKI

# Switch to OpenVPN folder
cd /etc/openvpn/
# Create PKI folder
mkdir -p easy-rsa
cd easy-rsa/
# Create symbolic link to easyrsa
ln -s /usr/share/easy-rsa/easyrsa ./easyrsa
# Init PKI
./easyrsa init-pki
# Create certificates
./easyrsa build-ca nopass
# Create server certificates
./easyrsa build-server-full server nopass
# Diffie-Hellman key exchange
./easyrsa gen-dh
# Create TLS-Crypt authentication key
openvpn --genkey secret /etc/openvpn/server/ta.key

3. Copy Certificate and Key

# Switch folder
cd /etc/openvpn/easy-rsa/
# copy
cp pki/ca.crt                   /etc/openvpn/server/
cp pki/issued/server.crt        /etc/openvpn/server/
cp pki/private/server.key       /etc/openvpn/server/
cp pki/dh.pem                   /etc/openvpn/server/

4. OpenVPN Server Configuration

# Switch folder
cd /etc/openvpn/server/
# create config file
touch server.conf
# Edit
vim server.conf

Use following configuration:

# Server listening
local 127.0.0.1
# Port config
port 1194
# Using TCP
proto tcp

# OSI Layer 3, Virtual network interface
# IPv6 enable by default after version 2.5
dev tun

# Maximum Transmission Unit, default 1500
# In case of packet too big issue, suggestion is 1400
tun-mtu 1400
# Maximum Segment Size
mssfix 1360

# Certificate
ca        /etc/openvpn/server/ca.crt
cert      /etc/openvpn/server/server.crt
key       /etc/openvpn/server/server.key
dh        /etc/openvpn/server/dh.pem
tls-crypt /etc/openvpn/server/ta.key 0
# Auth
cipher AES-256-GCM
auth SHA256
# Permission protect
user nobody
group nogroup
persist-key
persist-tun

# VPN Subnetwork IPv4
server 10.8.0.0 255.255.255.0
# Keep alive
keepalive 10 120
# Set server side as gateway
push "redirect-gateway def1 bypass-dhcp"
# DNS config, Cloudflare DNS IPv4
push "dhcp-option DNS 1.1.1.1"
push "dhcp-option DNS 1.0.0.1"

# Client-Specific Rules
client-config-dir /etc/openvpn/ccd

# Disable compress function
compress migrate
comp-noadapt
# Log
status /var/log/openvpn/status.log
log    /var/log/openvpn/openvpn.log
# Debugging, rollback to 1 after test
verb 4

5. Create OpenVPN log folder

mkdir -p /var/log/openvpn

6. IP Forward Configuration

vim /etc/sysctl.conf

Enable IP Forward

# IPv4
net.ipv4.ip_forward=1
# IPv6
net.ipv6.conf.all.forwarding=1
# Active configuration
sysctl -p

7. UFW Configuration

Disable UFW when change configuration

# Disable when change configuration
ufw disable

Check Network interface card.
Network interface card usually named eth0.

ip route | grep default | awk '{print $5}'

Editing UFW default rules.

vim /etc/ufw/before.rules

Allow NAT for OpenVPN at eth0.
Following should place before *filter.

*nat
:PREROUTING ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-A PREROUTING -p tcp --dport 8080 -j REDIRECT --to-port 1194
-A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE
COMMIT

For IPv6. Following should place before *filter.

vim /etc/ufw/before6.rules
# If your WAN IPv6 is 2400:8902:0:1::/64, plus one at first group.
*nat
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -s 2401:8902:0:1::/64 -o eth0 -j MASQUERADE
COMMIT

Allow UFW forward

vim /etc/default/ufw

Find DEFAULT_FORWARD_POLICY, modify DROP to ACCEPT

DEFAULT_FORWARD_POLICY="ACCEPT"

Rule Configuration

# Default deny all connect
ufw default deny
# Allow SSH connect
ufw allow 22/tcp comment 'SSH'
# Allow 443/tcp for SSLH
ufw allow 443/tcp comment 'SSLH'
# Allow 8080 for OpenVPN
ufw allow 8080/tcp comment 'OpenVPN'

Active UFW.

ufw disable && ufw enable
# Enable when booting
systemctl enable ufw

8. Change Apache2 Configuration

# Stop Apache server
systemctl stop apache2
# Editing port configuration
vim /etc/apache2/ports.conf
# Disable HTTP
# Listen 80

# HTTPS using 8090 
<IfModule ssl_module>
	Listen 8090
</IfModule>
<IfModule mod_gnutls.c>
	Listen 8090
</IfModule>
# Editing site configuration
vim /etc/apache2/sites-available/000-default.conf

Change HTTPS from port 443 to 8090:

# IP address request
<VirtualHost _default_:8090>
  ...(Ommit)...
</VirtualHost>

# ServerName based request
<VirtualHost *:8090>
  ...(Ommit)...
</VirtualHost>
# Restart apache2
systemctl restart apache2

9. SSLH Configuration

vim /etc/default/sslh
# Enable SSLH service
RUN=yes
# Package sorting mechanism
DAEMON_OPTS="--user sslh --listen 0.0.0.0:443 --listen :::443 --tls 127.0.0.1:8090 --openvpn 127.0.0.1:1194 --http 127.0.0.1:1080  --pidfile /var/run/sslh/sslh.pid"

# --listen 0.0.0.0:443   > IPv4 listening
# --listen :::443 > IPv6 listening
# --tls 127.0.0.1:8090   > HTTPS
# --openvpn 127.0.0.1:1194 > OpenVPN
# --http 127.0.0.1:1080 > Shadowsocks
# Please follow the correct combination, HTTPS(TLS) > OpenVPN > HTTP.
# Otherwise, the sorting mechanism will not function properly.

Restart and Check SSLH Status

systemctl restart sslh
systemctl status sslh

10. Running OpenVPN Server

# Restart OpenVPN
systemctl enable --now openvpn-server@server
systemctl status openvpn-server@server
# Check port status
ss -tlnp | grep -E '443|1080|1194|8080|8090'

11. Create Adding-User Script

# Create user file folder
mkdir -p /home/openvpn_user
# Switch folder
cd /home/openvpn_user
# Create script
touch opv_adduser.sh
# Editing script
vim opv_adduser.sh

Pasting following script:

#!/bin/bash

# Server IPv4
# Your server address
SERVER_IP="XXX.XXX.XXX.XXX"
# Server Port
SERVER_PORT="443"

# Asking client
CLIENT=$1
# Need client name
if [ -z "$CLIENT" ]; then
    echo "Please setting $0 <client-name>"
    exit 1
fi
# Check user name if exists
cd /etc/openvpn/easy-rsa
if [ -f "pki/issued/${CLIENT}.crt" ]; then
    echo "Error: Client '$CLIENT' already exists"
    exit 1
fi
# Create User certificates
./easyrsa build-client-full "$CLIENT" nopass
# Certificates payload
CA=$(cat pki/ca.crt)
CERT=$(openssl x509 -in "pki/issued/${CLIENT}.crt")
KEY=$(cat "pki/private/${CLIENT}.key")
TLS=$(cat /etc/openvpn/server/ta.key)
# Output opvn file
mkdir -p /home/OpenVPN_User
cat > "/home/OpenVPN_User/${CLIENT}.ovpn" <<EOF
client
dev tun
proto tcp

remote $SERVER_IP $SERVER_PORT

resolv-retry infinite
nobind
persist-key
persist-tun

tun-mtu 1400
mssfix 1360

cipher AES-256-GCM
auth SHA256
remote-cert-tls server

verb 2

<ca>
$CA
</ca>

<cert>
$CERT
</cert>

<key>
$KEY
</key>

<tls-crypt>
$TLS
</tls-crypt>
EOF

echo "openvpn configuration file at: /home/OpenVPN_User/${CLIENT}.ovpn"

Executable the script

chmod +x opv_adduser.sh

Using the script

bash opv_adduser.sh <user name>

12. Configuring Client-Specific Rules

Selectively IPv6.

# Create script
touch opv_addccd.sh
# Editing script
vim opv_addccd.sh

Pasting following script:

#!/bin/bash
# Asking client
CLIENT=$1
# Need client name
if [ -z "$CLIENT" ]; then
    echo "Please setting $0 <client-name>"
    exit 1
fi
# Check user name if exists
cd /etc/openvpn/easy-rsa
if [ ! -f "pki/issued/${CLIENT}.crt" ]; then
    echo "Error: Client '${CLIENT}' does not exist, create account first"
    exit 1
fi
# Check CCD
if [ -f "/etc/openvpn/ccd/${CLIENT}" ]; then
    echo "CCD for '${CLIENT}' already exists"
    exit 1
fi
# Create User CCD folder
mkdir -p /etc/openvpn/ccd/
# 1. VPN Server IPv6 
#    If your WAN IPv6 is 2400:8902:0:1::/64, plus one at first group
# 2. Psuh Subnetwork IPv6 to client.
#    If your WAN IPv6 is 2400:8902:0:1::/64, plus one at first group.
# 3. Push IPv6 route. This part don't need plus one at first group.
# 4. Push DNS config, Cloudflare DNS IPv6

cat > "/etc/openvpn/ccd/${CLIENT}" <<EOF
ifconfig-ipv6-push 2401:8902:0:1::2/64 2401:8902:0:1::1
push "route-ipv6 2401:8902:0:1::/64"
push "route-ipv6 2400::/3"
push "dhcp-option DNS6 2606:4700:4700::1111"
push "dhcp-option DNS6 2606:4700:4700::1001"
EOF
# Print result
echo "Client-specific configuration create at: /etc/openvpn/ccd/${CLIENT}"

Executable the script

chmod +x opv_addccd.sh

Using the script

bash opv_addccd.sh <user name>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment