Skip to main content
  1. Posts/

Site-to-Site VPN with SoftEther -- Connecting On-Premise, AWS, and Azure Networks

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 iptables

Disable 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.conf

Step 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 vpncmd

Step 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 defaults

Step 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: 1

Step 4: Configure SoftEther VPN
#

Use the SoftEther management CLI:

cd /usr/local/vpnserver
./vpncmd

In 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
exit

SecureNAT 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.bak

Write /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.1

The 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
EOF

Since 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 disable

Step 8: Start the VPN Hub
#

sysctl -p /etc/sysctl.d/ipv4_forwarding.conf
/etc/init.d/vpnserver start

Connecting 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 VPN

Then 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.zip

Site 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 VPN

Then 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 VPN

Then 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 back

SoftEther 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 rules

From 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.50

Security 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 SessionList and SessionGet in 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 restart

DNS not resolving for VPN clients
#

ss -ulnp | grep :53
dnsmasq --test
journalctl -u dnsmasq --no-pager -n 50

Site 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.30

AWS: 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": false

Port 53 conflict with systemd-resolved
#

ss -ulnp | grep :53
systemctl stop systemd-resolved
systemctl disable systemd-resolved

Summary
#

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.

Kevin Keller
Author
Kevin Keller
Personal blog about AI, Observability & Data Sovereignty. Snowflake-related articles explore the art of the possible and are not official Snowflake solutions or endorsed by Snowflake unless explicitly stated. Opinions are my own. Content is meant as educational inspiration, not production guidance.
Share this article

Related

SSH Tunnels from Snowflake Container Services — Bidirectional Access to Your DMZ, Home Lab, or PC

How to establish persistent, bidirectional SSH tunnels from Snowflake Container Services to any machine you control — using base64-encoded keys in Snowflake Secrets, autossh for resilience, reverse port forwards, and nginx to expose SPCS services with SSL on your own domain.