Most enterprises end up with infrastructure in at least three places: on-premise, one cloud, then another cloud. The networks are isolated by default. VPN appliances from cloud vendors cost money and lock you in. SoftEther is open-source, supports every major VPN protocol (L2TP/IPsec, OpenVPN, SSTP, SoftEther native), and can bridge Layer 2 across sites – meaning your on-prem machines, AWS EC2 instances, and Azure VMs can talk to each other as if they were on the same LAN.
This guide sets up a SoftEther VPN hub on a public-facing server that connects three sites into a unified network.
Architecture #
On-Premise Network Cloud Networks
(192.168.1.0/24)
|
[VPN Client]
|
| L2TP/IPsec, OpenVPN,
| SSTP, or SoftEther native
|
+-----------+
| VPN Hub | (Ubuntu 22.04, public IP)
| SoftEther |
+-----------+
| |
[tap_soft] [eth0]
192.168.30.1 <public-ip>
VPN subnet |
(192.168.30.0/24) |
| |
+-----+-----+ |
| | |
[AWS Site] [Azure Site]
VPN Client VPN Client
(10.1.0.0/16) (10.2.0.0/16)The Three Sites #
| Site | Network | Connects Via | Role |
|---|---|---|---|
| On-Premise | 192.168.1.0/24 | VPN client on router/gateway | Branch office, home lab, data centre |
| AWS | 10.1.0.0/16 (VPC) | SoftEther client on EC2 instance | Cloud workloads, databases |
| Azure | 10.2.0.0/16 (VNet) | SoftEther client on Azure VM | Cloud workloads, services |
| VPN Hub | 192.168.30.0/24 (VPN subnet) | Central server (public IP) | Routes traffic between all sites |
Once all three sites connect to the hub, any machine on any network can reach any other – on-prem to AWS, AWS to Azure, Azure to on-prem. The VPN hub handles the routing.
Ports #
| Port | Protocol | Service |
|---|---|---|
| 443 | TCP | SoftEther / SSTP |
| 992 | TCP | SoftEther |
| 1194 | UDP | OpenVPN compatible |
| 5555 | TCP | SoftEther management |
| 500+4500 | UDP | L2TP/IPsec (kernel) |
Prerequisites #
A public-facing Ubuntu 22.04 server with at least one public IP. This can be on any provider (Hetzner, DigitalOcean, a spare machine in your DMZ – anywhere clients can reach port 443).
apt-get update -y
apt-get install -y build-essential gnupg2 gcc make dnsmasq iptablesDisable systemd-resolved DNS stub listener #
SoftEther and dnsmasq both need port 53. Disable the stub listener so dnsmasq can bind it:
sed -i 's/^#DNSStubListener=.*/DNSStubListener=no/' /etc/systemd/resolved.conf
sed -i 's/^#DNS=.*/DNS=8.8.8.8/' /etc/systemd/resolved.conf
systemctl restart systemd-resolved
# Fix /etc/resolv.conf symlink so DNS still works on the host
ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.confStep 1: Install SoftEther VPN Server #
mkdir -p ~/Downloads && cd ~/Downloads
# Download SoftEther v4.41 (x64)
wget https://www.softether-download.com/files/softether/v4.41-9787-rtm-2023.03.14-tree/Linux/SoftEther_VPN_Server/64bit_-_Intel_x64_or_AMD64/softether-vpnserver-v4.41-9787-rtm-2023.03.14-linux-x64-64bit.tar.gz
# Extract and compile
tar -xvzf softether-vpnserver-v4.41-9787-rtm-2023.03.14-linux-x64-64bit.tar.gz
cd vpnserver
make
# Install to /usr/local
cd ..
mv vpnserver /usr/local/
# Set permissions
cd /usr/local/vpnserver
chmod 600 *
chmod 700 vpnserver vpncmdStep 2: Create the Service Script #
This init.d script starts SoftEther, waits for the TAP device, configures its IP, applies iptables rules for routing between all sites, and starts dnsmasq:
cat > /etc/init.d/vpnserver << 'INITEOF'
#!/bin/sh
### BEGIN INIT INFO
# Provides: vpnserver
# Required-Start: $network $remote_fs
# Required-Stop: $network $remote_fs
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: SoftEther VPN Server
# Description: Starts SoftEther VPN, configures TAP interface,
# applies iptables rules, and starts dnsmasq.
### END INIT INFO
DAEMON=/usr/local/vpnserver/vpnserver
LOCK=/var/lock/subsys/vpnserver
# --- Configuration ---
TAP_DEVICE=tap_soft
VPN_SUBNET=192.168.30.0/24
VPN_GATEWAY=192.168.30.1
WAN_INTERFACE=eth0
# Site subnets (for routing)
ONPREM_SUBNET=192.168.1.0/24
AWS_SUBNET=10.1.0.0/16
AZURE_SUBNET=10.2.0.0/16
# ----------------------
test -x $DAEMON || exit 0
wait_for_tap() {
for i in $(seq 1 30); do
if ip link show "$TAP_DEVICE" > /dev/null 2>&1; then
return 0
fi
sleep 1
done
echo "ERROR: $TAP_DEVICE did not appear after 30 seconds"
return 1
}
do_start() {
echo "Starting SoftEther VPN Server..."
$DAEMON start
touch $LOCK
echo "Waiting for TAP device $TAP_DEVICE..."
if ! wait_for_tap; then
exit 1
fi
echo "Configuring TAP interface..."
ip addr flush dev $TAP_DEVICE
ip addr add ${VPN_GATEWAY}/24 dev $TAP_DEVICE
ip link set $TAP_DEVICE up
echo "Applying iptables rules..."
# NAT: masquerade VPN traffic going out to internet
iptables -t nat -C POSTROUTING -o $WAN_INTERFACE -s $VPN_SUBNET -j MASQUERADE 2>/dev/null \
|| iptables -t nat -A POSTROUTING -o $WAN_INTERFACE -s $VPN_SUBNET -j MASQUERADE
# Allow OpenVPN UDP port inbound
iptables -C INPUT -i $WAN_INTERFACE -p udp --dport 1194 -j ACCEPT 2>/dev/null \
|| iptables -A INPUT -i $WAN_INTERFACE -p udp --dport 1194 -j ACCEPT
# Allow all traffic on TAP interface
iptables -C INPUT -i $TAP_DEVICE -j ACCEPT 2>/dev/null \
|| iptables -A INPUT -i $TAP_DEVICE -j ACCEPT
# Forward VPN traffic to WAN and between sites
iptables -C FORWARD -i $TAP_DEVICE -o $WAN_INTERFACE -s $VPN_SUBNET \
-m conntrack --ctstate NEW,RELATED,ESTABLISHED -j ACCEPT 2>/dev/null \
|| iptables -A FORWARD -i $TAP_DEVICE -o $WAN_INTERFACE -s $VPN_SUBNET \
-m conntrack --ctstate NEW,RELATED,ESTABLISHED -j ACCEPT
# Forward between VPN clients (site-to-site: TAP in -> TAP out)
iptables -C FORWARD -i $TAP_DEVICE -o $TAP_DEVICE -j ACCEPT 2>/dev/null \
|| iptables -A FORWARD -i $TAP_DEVICE -o $TAP_DEVICE -j ACCEPT
# Allow return traffic
iptables -C FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT 2>/dev/null \
|| iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
echo "Restarting dnsmasq..."
/etc/init.d/dnsmasq restart
echo "VPN Server started successfully."
}
do_stop() {
echo "Stopping SoftEther VPN Server..."
$DAEMON stop
echo "Removing iptables rules..."
iptables -t nat -D POSTROUTING -o $WAN_INTERFACE -s $VPN_SUBNET -j MASQUERADE 2>/dev/null
iptables -D INPUT -i $WAN_INTERFACE -p udp --dport 1194 -j ACCEPT 2>/dev/null
iptables -D INPUT -i $TAP_DEVICE -j ACCEPT 2>/dev/null
iptables -D FORWARD -i $TAP_DEVICE -o $WAN_INTERFACE -s $VPN_SUBNET \
-m conntrack --ctstate NEW,RELATED,ESTABLISHED -j ACCEPT 2>/dev/null
iptables -D FORWARD -i $TAP_DEVICE -o $TAP_DEVICE -j ACCEPT 2>/dev/null
iptables -D FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT 2>/dev/null
rm -f $LOCK
echo "VPN Server stopped."
}
case "$1" in
start)
do_start
;;
stop)
do_stop
;;
restart)
do_stop
sleep 3
do_start
;;
status)
if [ -f $LOCK ]; then
echo "VPN Server is running"
else
echo "VPN Server is stopped"
fi
;;
*)
echo "Usage: $0 {start|stop|restart|status}"
exit 1
esac
exit 0
INITEOF
chmod 755 /etc/init.d/vpnserver
mkdir -p /var/lock/subsys
update-rc.d vpnserver defaultsStep 3: Enable IP Forwarding #
echo "net.ipv4.ip_forward = 1" > /etc/sysctl.d/ipv4_forwarding.conf
sysctl -p /etc/sysctl.d/ipv4_forwarding.conf
# Verify
cat /proc/sys/net/ipv4/ip_forward
# Should output: 1Step 4: Configure SoftEther VPN #
Use the SoftEther management CLI:
cd /usr/local/vpnserver
./vpncmdIn the vpncmd console:
# 1. Connect to the local server
1 (select "Management of VPN Server")
localhost (connect to localhost)
(press Enter for no hub)
# 2. Set admin password
ServerPasswordSet
# 3. Create a Virtual Hub
HubCreate site2site
# 4. Switch to that hub
Hub site2site
# 5. Create users for each site
UserCreate onprem-gw /GROUP:none /REALNAME:"On-Premise Gateway" /NOTE:none
UserPasswordSet onprem-gw
UserCreate aws-gw /GROUP:none /REALNAME:"AWS Gateway" /NOTE:none
UserPasswordSet aws-gw
UserCreate azure-gw /GROUP:none /REALNAME:"Azure Gateway" /NOTE:none
UserPasswordSet azure-gw
# 6. Create Local Bridge to TAP device
Hub
BridgeCreate site2site /DEVICE:soft /TAP:yes
# 7. Enable L2TP/IPsec
IPsecEnable /L2TP:yes /L2TPRAW:yes /ETHERIP:yes /PSK:your-preshared-key /DEFAULTHUB:site2site
# 8. Enable OpenVPN compatible mode
OpenVpnEnable yes /PORTS:1194
# 9. Enable SecureNAT DHCP (assigns VPN IPs to connecting sites)
Hub site2site
SecureNatEnable
DhcpSet /START:192.168.30.10 /END:192.168.30.200 /MASK:255.255.255.0 \
/EXPIRE:7200 /GW:192.168.30.1 /DNS:192.168.30.1 /DNS2:none \
/DOMAIN:none /LOG:yes /PUSHROUTE:none
# 10. Exit
exitSecureNAT vs dnsmasq: SoftEther’s built-in SecureNAT provides DHCP, but dnsmasq is preferred for this setup because it also handles DNS forwarding and allows pushing static routes to clients via DHCP options. If you use dnsmasq for DHCP, disable SecureNAT:
SecureNatDisable.
Step 5: Configure dnsmasq #
cp /etc/dnsmasq.conf /etc/dnsmasq.conf.bakWrite /etc/dnsmasq.conf:
# --- DNS Settings ---
domain-needed
bogus-priv
no-resolv
server=8.8.8.8
server=8.8.4.4
# --- Interface binding ---
# Only listen on the VPN TAP interface and loopback
interface=tap_soft
interface=lo
bind-interfaces
# --- DHCP Settings ---
dhcp-range=tap_soft,192.168.30.10,192.168.30.100,24h
dhcp-option=tap_soft,3,192.168.30.1
dhcp-option=tap_soft,6,192.168.30.1
dhcp-authoritative
dhcp-leasefile=/var/lib/misc/dnsmasq.leases
# --- Push routes for all site subnets ---
# Option 121 (RFC 3442) for Linux/Mac clients
dhcp-option=tap_soft,121,192.168.1.0/24,192.168.30.1,10.1.0.0/16,192.168.30.1,10.2.0.0/16,192.168.30.1
# Option 249 for Windows clients
dhcp-option=tap_soft,249,192.168.1.0/24,192.168.30.1,10.1.0.0/16,192.168.30.1,10.2.0.0/16,192.168.30.1The DHCP route push is the key to site-to-site routing: every VPN client learns routes to all three site subnets (on-prem, AWS, Azure) via the VPN gateway at 192.168.30.1.
Step 6: Configure Internal DNS (Optional) #
If you want VPN clients to resolve internal hostnames for services across sites, add entries to /etc/hosts:
cat >> /etc/hosts << 'EOF'
# On-premise services
192.168.1.10 gitlab.internal
192.168.1.20 jenkins.internal
# AWS services
10.1.0.50 api.internal
10.1.0.51 postgres.internal
# Azure services
10.2.0.50 grafana.internal
10.2.0.51 keyvault.internal
EOFSince dnsmasq reads /etc/hosts by default, VPN clients using 192.168.30.1 as their DNS server will resolve these names to the correct site-specific IPs.
Step 7: Disable UFW (or Configure It) #
UFW conflicts with the manual iptables rules. Either disable it or configure UFW to allow VPN traffic:
ufw disableStep 8: Start the VPN Hub #
sysctl -p /etc/sysctl.d/ipv4_forwarding.conf
/etc/init.d/vpnserver startConnecting the Sites #
Site A: On-Premise Gateway #
Install a SoftEther VPN Client (or use L2TP/IPsec, OpenVPN) on a machine in your on-premise network that acts as the gateway. This machine needs IP forwarding enabled and a route back to the VPN subnet.
Option 1: SoftEther native client (Linux gateway):
# On the on-prem gateway machine
apt-get install -y softether-vpnclient # or compile from source
# Create a VPN connection
vpncmd localhost /CLIENT
NicCreate vpn0
AccountCreate onprem /SERVER:<hub-public-ip>:443 /HUB:site2site /USERNAME:onprem-gw /NICNAME:vpn0
AccountPasswordSet onprem /PASSWORD:<password> /TYPE:standard
AccountConnect onprem
# Enable IP forwarding on this gateway
echo "net.ipv4.ip_forward = 1" > /etc/sysctl.d/ipv4_forwarding.conf
sysctl -p /etc/sysctl.d/ipv4_forwarding.conf
# Add routes for the other sites via the VPN
ip route add 10.1.0.0/16 via 192.168.30.1 # AWS via VPN
ip route add 10.2.0.0/16 via 192.168.30.1 # Azure via VPNThen configure your on-prem router/firewall to route 10.1.0.0/16, 10.2.0.0/16, and 192.168.30.0/24 via the gateway machine.
Option 2: L2TP/IPsec (any OS):
- Server:
<hub-public-ip> - Pre-shared key: as set during
IPsecEnable - Username/password:
onprem-gw/ as set
Option 3: OpenVPN:
Generate a client config from the hub:
cd /usr/local/vpnserver
./vpncmd localhost /SERVER /CMD OpenVpnMakeConfig ~/openvpn-client-config.zipSite B: AWS VPC Gateway #
Launch a small EC2 instance (t3.micro is sufficient) in your VPC as the VPN gateway. It needs:
- A public IP or NAT gateway for outbound access to the hub
- Source/dest check disabled (required for routing – EC2 default drops packets not addressed to the instance)
- Security group allowing traffic from the VPN subnet
# On the AWS gateway EC2 instance
# Install SoftEther client and connect (same as on-prem)
# Disable source/dest check via AWS CLI
aws ec2 modify-instance-attribute \
--instance-id i-xxxxxxxxxxxx \
--no-source-dest-check
# Enable IP forwarding
echo "net.ipv4.ip_forward = 1" > /etc/sysctl.d/ipv4_forwarding.conf
sysctl -p /etc/sysctl.d/ipv4_forwarding.conf
# Add routes for the other sites
ip route add 192.168.1.0/24 via 192.168.30.1 # On-prem via VPN
ip route add 10.2.0.0/16 via 192.168.30.1 # Azure via VPNThen add a route in your VPC route table pointing 192.168.1.0/24, 10.2.0.0/16, and 192.168.30.0/24 to the gateway EC2 instance’s ENI.
Site C: Azure VNet Gateway #
Deploy a small VM (B1s is sufficient) in your VNet as the VPN gateway. It needs:
- IP forwarding enabled on the NIC (Azure Portal: NIC > IP configurations > IP forwarding = Enabled)
- NSG rules allowing traffic from the VPN subnet
# On the Azure gateway VM
# Install SoftEther client and connect (same as on-prem)
# Enable IP forwarding
echo "net.ipv4.ip_forward = 1" > /etc/sysctl.d/ipv4_forwarding.conf
sysctl -p /etc/sysctl.d/ipv4_forwarding.conf
# Add routes for the other sites
ip route add 192.168.1.0/24 via 192.168.30.1 # On-prem via VPN
ip route add 10.1.0.0/16 via 192.168.30.1 # AWS via VPNThen add a User Defined Route (UDR) in your Azure route table pointing 192.168.1.0/24, 10.1.0.0/16, and 192.168.30.0/24 to the gateway VM’s private IP as the next hop.
The Routing Flow #
Once all three sites are connected, here’s how traffic flows between them:
On-Prem machine (192.168.1.50) wants to reach AWS RDS (10.1.0.51):
1. Packet hits on-prem gateway (192.168.1.1)
2. Gateway has route: 10.1.0.0/16 via VPN (192.168.30.x)
3. Packet enters SoftEther tunnel to hub
4. Hub receives on tap_soft, forwards to AWS client (also on tap_soft)
5. AWS gateway (192.168.30.x / 10.1.0.x) receives packet
6. AWS gateway forwards to 10.1.0.51 on the VPC
7. Response follows the same path backSoftEther bridges at Layer 2 – all connected clients share the same virtual Ethernet segment on tap_soft. The hub’s iptables FORWARD rule for tap_soft -> tap_soft allows inter-client traffic. Each site’s gateway then routes between the VPN subnet and its local network.
Verifying the Mesh #
From the VPN hub:
# Check all connected clients
cd /usr/local/vpnserver
./vpncmd localhost /SERVER /HUB:site2site /CMD SessionList
# Check TAP interface
ip addr show tap_soft
# Check DHCP leases
cat /var/lib/misc/dnsmasq.leases
# Check routing
ip route show
# Should include: 192.168.30.0/24 dev tap_soft
# Check iptables
iptables -L FORWARD -n -v
# Should show traffic counters on the tap_soft rulesFrom any site gateway:
# Ping the VPN hub
ping 192.168.30.1
# Ping another site's gateway (e.g., from on-prem, ping AWS gateway)
ping 192.168.30.x # AWS gateway's VPN IP
# Ping a machine on another site's network (through the gateway)
ping 10.1.0.50 # AWS service from on-prem
# Trace the route
traceroute 10.1.0.50
# Should show: on-prem gateway -> VPN hub (192.168.30.1) -> AWS gateway -> 10.1.0.50Security Considerations #
- Rotate the pre-shared key regularly. Use SoftEther native protocol or OpenVPN with certificates for stronger auth.
- Restrict management access. SoftEther’s management port (5555) should only be accessible from trusted IPs. Use iptables to block it from the public interface.
- Use per-site users. Each site gets its own VPN credentials. If a site is compromised, revoke only that user.
- Monitor connected sessions. Use
SessionListandSessionGetin vpncmd to audit who’s connected. - Encrypt at rest. SoftEther’s config file (
vpn_server.config) contains hashed passwords and the PSK. Protect it with file permissions (chmod 600). - Consider certificate-based auth instead of passwords for site-to-site gateways. SoftEther supports X.509 client certificates.
Troubleshooting #
TAP device not appearing #
cd /usr/local/vpnserver && ./vpncmd localhost /SERVER /CMD BridgeList
/etc/init.d/vpnserver restartDNS not resolving for VPN clients #
ss -ulnp | grep :53
dnsmasq --test
journalctl -u dnsmasq --no-pager -n 50Site A can’t reach Site B through the VPN #
# On the hub: verify both sites are connected
./vpncmd localhost /SERVER /HUB:site2site /CMD SessionList
# Verify IP forwarding on the hub
cat /proc/sys/net/ipv4/ip_forward
# Verify the inter-client forward rule
iptables -L FORWARD -n -v | grep tap_soft
# On the site gateway: verify routes exist
ip route show | grep 192.168.30AWS: packets dropped at EC2 instance #
# Source/dest check must be disabled
aws ec2 describe-instance-attribute \
--instance-id i-xxxxxxxxxxxx \
--attribute sourceDestCheck
# Should show: "Value": falsePort 53 conflict with systemd-resolved #
ss -ulnp | grep :53
systemctl stop systemd-resolved
systemctl disable systemd-resolvedSummary #
| Component | File / Location |
|---|---|
| SoftEther VPN Server | /usr/local/vpnserver/ |
| SoftEther config | /usr/local/vpnserver/vpn_server.config |
| Service script | /etc/init.d/vpnserver |
| DHCP + DNS for VPN | /etc/dnsmasq.conf |
| IP forwarding | /etc/sysctl.d/ipv4_forwarding.conf |
| DNS stub disabled | /etc/systemd/resolved.conf |
| Internal DNS entries | /etc/hosts |
The setup gives you a fully routed mesh between on-premise, AWS, and Azure – all through a single SoftEther hub. Each site connects as a VPN client, receives routes to all other sites via DHCP, and forwards traffic between its local network and the VPN. SoftEther’s Layer 2 bridging means the hub doesn’t need to know about individual subnets – it just forwards Ethernet frames between connected clients on the same virtual switch.