Creating a Site-to-Site VPN with OpenVPN

Creating a Site-to-Site VPN with OpenVPN

Disclosure: Some links on this page are affiliate links. We may earn a commission if you make a purchase through them, at no additional cost to you.

Building a site-to-site VPN with OpenVPN lets you securely bridge two (or more) private networks over the public Internet so that hosts on each side can communicate as if they were on the same LAN. OpenVPN is open-source, widely audited, and extremely flexible: it supports strong modern cryptography (TLS 1.3, AEAD ciphers), IPv4/IPv6, policy routing, HA/failover, and fine-grained access control. This end-to-end, copy-paste-ready guide walks you through a production-style, routed deployment using a TLS-based public key infrastructure (PKI), client-specific routing (CCD), firewalling, MTU/MSS tuning, logging, monitoring, and optional high availability.

 

1) Prerequisites & Example Topology

  • Two Linux gateways with public Internet reachability (Ubuntu/Debian examples below).
  • Root/administrative access, OpenVPN >= 2.5 recommended (2.6+ preferred).
  • Basic knowledge of routing/subnetting and firewall management (iptables or nftables).
  • Open UDP/1194 (or alternative) on any upstream firewalls/NATs.

Example topology

  • Site A (Headquarters): WAN: A_WAN, LAN: 10.10.0.0/24
  • Site B (Branch): WAN: B_WAN, LAN: 10.20.0.0/24
  • OpenVPN tunnel network (point-to-multipoint routed): 10.8.0.0/24
  • Server’s tunnel IP: 10.8.0.1; Client’s tunnel IP: 10.8.0.2

We’ll build a routed TUN tunnel (Layer-3). Bridged TAP is possible but generally unnecessary and heavier. Routed TUN is simpler, faster, and scales well.


2) PKI & Security Model (TLS, tls-crypt, CRL)

For a secure site-to-site VPN with OpenVPN, use TLS authentication with your own certificate authority (CA). A minimal but robust setup uses:

  • A self-hosted CA (easy-rsa or OpenSSL) to issue server and client certificates.
  • tls-crypt (or tls-auth) to authenticate and conceal control channel packets (mitigates some scanning/DoS vectors and hides TLS handshake metadata).
  • A Certificate Revocation List (CRL) to revoke compromised client certs.

Install tooling

sudo apt update sudo apt install -y openvpn easy-rsa 

Initialize PKI (on Site A)

make-cadir ~/easy-rsa cd ~/easy-rsa ./easyrsa init-pki ./easyrsa build-ca nopass # or omit nopass for a protected CA key ./easyrsa gen-dh ./easyrsa build-server-full server nopass ./easyrsa build-client-full siteB nopass ./easyrsa gen-crl openvpn --genkey --secret ta.key # legacy tls-auth; for tls-crypt use: openvpn --genkey tls-crypt.key 

Artifacts of interest (paths may vary by distro):

  • CA: pki/ca.crt
  • Server: pki/issued/server.crt, pki/private/server.key
  • Client (Site B): pki/issued/siteB.crt, pki/private/siteB.key
  • DH: pki/dh.pem (not required for TLS 1.3 With ECDHE; OpenVPN 2.6+ can use data-ciphers + providers)
  • CRL: pki/crl.pem
  • tls-crypt.key (preferred) or ta.key for tls-auth

Distribute securely: ship to Site B its own cert/key (siteB.crt, siteB.key), the CA certificate (ca.crt), and the tls-crypt.key (or ta.key) via a secure channel (e.g., scp over a trusted management VPN or out-of-band means).


3) OpenVPN Server Configuration (Site A)

Place the server config at /etc/openvpn/server.conf (systemd instance name openvpn@server).

port 1194 proto udp dev tun # TLS & PKI ca /etc/openvpn/pki/ca.crt cert /etc/openvpn/pki/issued/server.crt key /etc/openvpn/pki/private/server.key # For OpenVPN 2.6+, TLS 1.3 defaults apply; for compatibility you can set: tls-version-min 1.2 tls-crypt /etc/openvpn/tls-crypt.key # Cipher & crypto settings (AEAD is recommended) data-ciphers AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305 data-ciphers-fallback AES-256-GCM auth SHA256 # Server-side tunnel network server 10.8.0.0 255.255.255.0 topology subnet ifconfig-pool-persist /var/log/openvpn/ipp.txt # Client-specific config directory (for per-site iroutes and options) client-config-dir /etc/openvpn/ccd # Keep the tunnel alive keepalive 10 60 persist-key persist-tun ping-timer-rem explicit-exit-notify 1 # Route Site A LAN to clients # We'll use CCD to push remote LANs, but here we advertise our side: push "route 10.10.0.0 255.255.255.0" # Security hardening user nobody group nogroup crl-verify /etc/openvpn/pki/crl.pem remote-cert-eku "TLS Web Client Authentication" remote-cert-tls client verify-x509-name "siteB" name # optional: pin CN if you enforce CN=siteB # Logging & status status /var/log/openvpn/status.log log-append /var/log/openvpn/openvpn.log verb 3 

Create the CCD path and a client-specific file for Site B (more in Section 5):

sudo mkdir -p /etc/openvpn/ccd echo "ifconfig-push 10.8.0.2 255.255.255.0" | sudo tee /etc/openvpn/ccd/siteB echo "iroute 10.20.0.0 255.255.255.0" | sudo tee -a /etc/openvpn/ccd/siteB 

Enable and start:

sudo systemctl enable openvpn@server sudo systemctl start openvpn@server sudo systemctl status openvpn@server 

4) OpenVPN Peer Configuration (Site B)

On Site B, create /etc/openvpn/client.conf (systemd instance openvpn@client):

client dev tun proto udp remote A_WAN 1194 resolv-retry infinite nobind persist-key persist-tun # TLS & crypto ca /etc/openvpn/pki/ca.crt cert /etc/openvpn/pki/issued/siteB.crt key /etc/openvpn/pki/private/siteB.key tls-crypt /etc/openvpn/tls-crypt.key tls-version-min 1.2 remote-cert-tls server data-ciphers AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305 data-ciphers-fallback AES-256-GCM auth SHA256 # Robustness ping 10 ping-restart 60 verb 3 # Optional: pin expected server name (CN) verify-x509-name "server" name 

Start the client:

sudo systemctl enable openvpn@client sudo systemctl start openvpn@client sudo systemctl status openvpn@client 

5) Routing Between Sites: push vs ccd + iroute

In a site-to-site setup, the server must know which client owns which LAN, and clients must know how to reach the remote LAN. There are two mechanisms:

  • push route (server → client): informs the client how to reach Site A (and optionally other sites).
  • iroute (in ccd/<common-name>): tells OpenVPN’s internal routing which client is responsible for a given subnet. Without the iroute, OpenVPN won’t forward packets to that client, even if a kernel route exists.

In our example:

  • Server pushes 10.10.0.0/24 (Site A LAN) to Site B via push "route 10.10.0.0 255.255.255.0".
  • Server’s ccd/siteB file sets iroute 10.20.0.0 255.255.255.0 so the server forwards Site B LAN traffic to the Site B client.

On the client (Site B), you typically don’t need to push a route back to the server because the server is the “hub.” The server’s kernel routing table will learn the client LAN via iroute + tun, and we’ll add a kernel route from Site A LAN to the tunnel (Section 6 covers firewall and forwarding).


6) Kernel IP Forwarding, Firewall & NAT

Enable IPv4 forwarding on both gateways:

echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.conf sudo sysctl -p 

On Site A, allow forwarding between tun0 and LAN interface (example uses iptables; nftables equivalent also works):

# Accept forwarding on the tunnel sudo iptables -A FORWARD -i tun0 -o eth0 -j ACCEPT sudo iptables -A FORWARD -i eth0 -o tun0 -j ACCEPT
NAT is NOT required for pure site-to-site if both sides know routes.
Only use masquerade if you must hide one side behind the VPN:
sudo iptables -t nat -A POSTROUTING -s 10.10.0.0/24 -o tun0 -j MASQUERADE

On Site B, mirror the forward policy:

sudo iptables -A FORWARD -i tun0 -o eth0 -j ACCEPT sudo iptables -A FORWARD -i eth0 -o tun0 -j ACCEPT 

Persist rules (Debian/Ubuntu):

sudo apt install -y iptables-persistent sudo netfilter-persistent save 

Routing the LANs: Ensure your LAN hosts (or their default gateway) know the route to the remote LAN via the VPN gateway. Typically you add a static route on the gateway (router) at each site:

  • On Site A’s LAN router: ip route add 10.20.0.0/24 via <Site A VPN gateway LAN IP>
  • On Site B’s LAN router: ip route add 10.10.0.0/24 via <Site B VPN gateway LAN IP>

7) Dual-stack (IPv6) Site-to-Site

OpenVPN supports IPv6 for both the tunnel and routed subnets:

  • Add server-ipv6 fddd:10:8::/64 on the server and push "route-ipv6 fddd:10:10::/64" for Site A’s v6 LAN.
  • In ccd/siteB, add iroute-ipv6 fddd:10:20::/64 for Site B’s v6 LAN.
  • Enable net.ipv6.conf.all.forwarding=1.

8) MTU/MSS, Performance & Reliability Tuning

  • UDP vs TCP: Prefer proto udp for performance. TCP over TCP (tunneling TCP inside TCP) can cause head-of-line blocking and poor throughput.
  • MTU: Tunnel encapsulation adds overhead. Set tun-mtu or rely on OpenVPN’s auto-tuning. If you see PMTU blackholes, clamp MSS.
  • MSS Clamp (iptables):
sudo iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu 
  • Buffers: On high-latency links, consider:
sndbuf 0 rcvbuf 0 txqueuelen 2000 tun-mtu 1500 # or lower if path MTU requires, e.g., 1400-1472 mssfix 1360 # test and adjust if necessary 
  • Keepalives: keepalive 10 60 and ping-restart help detect dead peers.
  • Modern Ciphers: Use AEAD (AES-GCM or CHACHA20-POLY1305). Avoid legacy BF-CBC, etc.

9) High Availability, Multi-Remote & Failover

To survive data-center or ISP outages, add a secondary remote or run active/active to two hubs.

# On client (Site B) remote A_WAN 1194 remote A_BACKUP_WAN 1194 remote-random connect-retry-max infinite connect-retry 5 

For active/active with load distribution you generally run multiple OpenVPN instances (different ports or servers) and use policy routing/ECMP on the gateways to split flows. For active/standby, remote-random and resolv-retry infinite suffice.


10) Operations: Logging, Monitoring, and Maintenance

  • Logs: /var/log/openvpn/openvpn.log, status.log. Increase verb temporarily when diagnosing.
  • Metrics: Expose tunnel reachability via node_exporter script or a simple health endpoint. Log bytes in/out and session counts for capacity planning.
  • CRL Rotation: Revoke compromised clients and refresh crl.pem on the server regularly.
  • Key Rotation: Periodically re-issue certs (short lifetimes on client certs reduce blast radius).
  • Backups: Protect your CA private key offline. Keep encrypted backups of PKI artifacts.

11) Verification & Troubleshooting Checklist

Bringup & interfaces

sudo systemctl status openvpn@server sudo systemctl status openvpn@client ip addr show tun0 

Routing & iroutes

ip route cat /etc/openvpn/ccd/siteB cat /var/log/openvpn/status.log 

Connectivity tests

# From Site A VPN gateway: ping -I 10.8.0.1 10.8.0.2 ping 10.20.0.1 # a host in Site B LAN
From Site B:

ping 10.10.0.1
traceroute 10.10.0.1

Common pitfalls

  • Missing iroute: The server won’t forward packets for Site B’s LAN without iroute in the CCD file.
  • Asymmetric routing: Ensure both site routers know to return traffic via the VPN gateway.
  • Firewall blocks: Confirm iptables/nftables allow FORWARD on tun0 and the correct LAN interface.
  • PMTU blackholes: Apply TCPMSS clamp and/or reduce MTU on the tunnel.
  • Wrong CN match: If you pin verify-x509-name, ensure the certificate CN/Subject matches.

12) Hardening Checklist

  • Use tls-crypt (conceal/control channel) or tls-auth at minimum.
  • Enforce TLS 1.2/1.3, AEAD ciphers with data-ciphers, remote-cert-tls, and EKU checks.
  • Run OpenVPN as non-root: user nobody, group nogroup.
  • Restrict file permissions on keys (0600), store CA key offline; use CRL.
  • Disable unused features (no management interface exposed to WAN).
  • Consider firewall pinholing: permit UDP/1194 only from the peer’s IP if static.
  • Set client-config-dir and define only the exact subnets needed (least privilege).

13) Full Reference Configs (Copy/Paste)

13.1 Site A (Server) — /etc/openvpn/server.conf

port 1194 proto udp dev tun

ca /etc/openvpn/pki/ca.crt
cert /etc/openvpn/pki/issued/server.crt
key /etc/openvpn/pki/private/server.key

Modern crypto

tls-version-min 1.2
tls-crypt /etc/openvpn/tls-crypt.key
data-ciphers AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305
data-ciphers-fallback AES-256-GCM
auth SHA256

Tunnel network

server 10.8.0.0 255.255.255.0
topology subnet
ifconfig-pool-persist /var/log/openvpn/ipp.txt

Per-client routing

client-config-dir /etc/openvpn/ccd
push "route 10.10.0.0 255.255.255.0"

Keepalive & persistence

keepalive 10 60
persist-key
persist-tun
ping-timer-rem
explicit-exit-notify 1

Security

crl-verify /etc/openvpn/pki/crl.pem
remote-cert-tls client
user nobody
group nogroup

Logs

status /var/log/openvpn/status.log
log-append /var/log/openvpn/openvpn.log
verb 3

CCD for Site B — /etc/openvpn/ccd/siteB

# Assign specific tunnel IP to Site B ifconfig-push 10.8.0.2 255.255.255.0 # Tell OpenVPN that Site B is responsible for 10.20.0.0/24 iroute 10.20.0.0 255.255.255.0 

13.2 Site B (Client) — /etc/openvpn/client.conf

client dev tun proto udp remote A_WAN 1194 resolv-retry infinite nobind persist-key persist-tun

ca /etc/openvpn/pki/ca.crt
cert /etc/openvpn/pki/issued/siteB.crt
key /etc/openvpn/pki/private/siteB.key
tls-crypt /etc/openvpn/tls-crypt.key
tls-version-min 1.2
remote-cert-tls server

data-ciphers AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305
data-ciphers-fallback AES-256-GCM
auth SHA256

Reliability

ping 10
ping-restart 60

verb 3

13.3 Systemd enablement

# Site A (server) sudo systemctl enable --now openvpn@server
Site B (client)

sudo systemctl enable --now openvpn@client

13.4 IP forwarding & firewall (both sites)

echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.conf sudo sysctl -p
iptables example

sudo iptables -A FORWARD -i tun0 -o eth0 -j ACCEPT
sudo iptables -A FORWARD -i eth0 -o tun0 -j ACCEPT
sudo iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu

Persist

sudo apt install -y iptables-persistent
sudo netfilter-persistent save

Conclusion

Creating a Site-to-Site VPN with OpenVPN combines robust TLS security with flexible routing to securely connect branch offices, data centers, and cloud VPCs. By using a clean PKI, tls-crypt, CCD-based iroute, proper forwarding and firewall rules, and sensible performance tuning (MTU/MSS, UDP, AEAD ciphers), you get a resilient, high-throughput inter-site fabric. From there, you can extend to IPv6, add HA with multi-remote or multiple instances, and integrate monitoring and CRL/key rotation for long-term operations. Drop the configs above into place, verify routes both ways, and you’ll have a production-ready site-to-site OpenVPN deployment.

Leave a Comment

Your email address will not be published. Required fields are marked *