Skip to main content

Culvert Client Setup Guide

This guide covers installing and configuring OpenVPN and WireGuard clients on Linux, macOS, and Windows.

Client Requirements​

Minimum Versions​

ClientMinimum VersionOAuth2/SSO SupportDownload
OpenVPN Connect3.4.0+Yeshttps://openvpn.net/client/
OpenVPN GUI (Windows)2.6.0+Yeshttps://openvpn.net/community-downloads/
OpenVPN (Linux CLI)2.6.0+No (certificate only)https://openvpn.net/community-downloads/
Tunnelblick (macOS)3.8.8+No (certificate only)https://tunnelblick.net/
WireGuardAnyN/A (key-based)https://www.wireguard.com/install/

OAuth2/SSO Authentication​

If your organisation uses OAuth2/OIDC SSO (e.g., Microsoft Entra ID, Okta, Google):

Use OpenVPN Connect 3.4.0+ or OpenVPN GUI 2.6.0+ (Windows)

These clients support the web-based OAuth2 authentication flow required for SSO. When connecting, your browser will open to complete authentication.

Certificate-only authentication works with any OpenVPN 2.6+ client.

WireGuard uses key-based authentication and does not support OAuth2/SSO. Keys are distributed via configuration files provided by the VPN administrator.


Configuration Files Included​

OpenVPN​

FileProtocolUse Case
*-udp-split.ovpnUDP 1194Recommended - Fastest, VPN routes only
*-udp-full.ovpnUDP 1194All traffic through VPN
*-tcp-split.ovpnTCP 1194Fallback when UDP blocked
*-tcp-full.ovpnTCP 1194All traffic, TCP fallback
*-https-split.ovpnTCP 443 (stunnel)over HTTPS, VPN routes only
*-https-full.ovpnTCP 443 (stunnel)over HTTPS, all traffic
*-stunnel.conf-stunnel config for HTTPS tunnel

WireGuard​

WireGuard configs come in a folder named for you, because the file name is the name of the tunnel and Linux and Android allow it 15 characters.

FileProtocolUse Case
wg-split.confUDP 51820Recommended - VPN routes only
wg-full.confUDP 51820All traffic through VPN
wg-https-split.confWSS 4443 (wstunnel)over HTTPS, VPN routes only
wg-https-full.confWSS 4443 (wstunnel)over HTTPS, all traffic

Using more than one device​

The same .ovpn works on a laptop and a phone at once. WireGuard does not: a key connects from one place at a time, so you get a set of files per device - wg-split.conf for the first, wg2-split.conf for the second. Load one set per device, or they will keep knocking each other offline.

Which to use:

  1. Try WireGuard first (best performance, simplest setup)
  2. If WireGuard is blocked, try OpenVPN UDP (UDP 1194)
  3. If UDP blocked, try OpenVPN TCP (TCP 1194)
  4. If still blocked, run the VPN over HTTPS - WireGuard via wstunnel (WSS 4443) or OpenVPN via stunnel (TCP 443)

When to get a new file​

These files are a snapshot of the server's settings when they were issued. Ask your administrator for a new set after the server's address, ports, DNS, MTU, routes or keys change. Restarting the server does not update a file already imported on a device.


WireGuard Client Setup​

Installation​

  • Windows: Download from https://www.wireguard.com/install/
  • macOS: brew install wireguard-tools or "WireGuard" from the App Store
  • Linux (Ubuntu/Debian): sudo apt install wireguard-tools
  • Linux (Fedora/RHEL): sudo dnf install wireguard-tools
  • Linux (Arch): sudo pacman -S wireguard-tools
  • iOS/Android: WireGuard app from the App Store / Play Store

Importing Configuration​

  1. Request your configuration files from the VPN administrator
  2. You will receive wg-split.conf and wg-full.conf, in a folder named for you
  3. Import into your WireGuard client (drag and drop, or "Import tunnel from file")
  4. Activate the tunnel

Split vs Full Tunnel​

  • Split tunnel (wg-split.conf): Only VPN traffic goes through the tunnel. Internet traffic uses your normal connection.
  • Full tunnel (wg-full.conf): All traffic goes through the tunnel. Use when you need full network isolation.

Linux: Command Line​

# Import and bring up the tunnel
sudo wg-quick up ./wg-split.conf

# Check status
sudo wg show

# Bring down the tunnel
sudo wg-quick down ./wg-split.conf

Linux: Always-On WireGuard (systemd)​

# Copy config to system directory
sudo cp wg-split.conf /etc/wireguard/culvert.conf

# Enable and start
sudo systemctl enable wg-quick@culvert
sudo systemctl start wg-quick@culvert

# Check status
sudo systemctl status wg-quick@culvert

macOS​

GUI (recommended):

  1. Install WireGuard from the App Store or brew install wireguard-tools
  2. Open WireGuard, click "Import tunnel(s) from file"
  3. Select your .conf file
  4. Click "Activate"

Command line:

brew install wireguard-tools
sudo wg-quick up ./wg-split.conf

Windows​

  1. Download and install WireGuard from https://www.wireguard.com/install/
  2. Open WireGuard, click "Import tunnel(s) from file"
  3. Select your .conf file
  4. Click "Activate"

Running the VPN over HTTPS​

Use these configs where the network blocks the VPN protocols themselves but allows ordinary HTTPS: tight corporate egress, hotel and conference wifi, captive portals, cloud egress policy.

What this does and does not buy you. To on-path inspection the traffic is TLS on 443 (or 4443) presenting a valid certificate for your server name, so a network that drops "things that look like OpenVPN or WireGuard" does not see either. It is NOT tested against, and makes no claim about, a censor that actively probes your endpoint or does traffic-flow analysis on timing, volume and connection duration. That is a different game. Treat this as "indistinguishable from HTTPS at a glance", not as invisibility, and do not rely on it where being wrong carries a personal risk.

OpenVPN over HTTPS (stunnel)​

The *-https-*.ovpn configs use stunnel to wrap OpenVPN in real TLS on port 443. Most OpenVPN clients handle this automatically. See the OpenVPN HTTPS tunnel sections below for platform-specific instructions.

WireGuard over HTTPS (wstunnel)​

For networks that block WireGuard, use the HTTPS-tunnel configs (wg-https-*.conf). These require wstunnel on your machine.

Installing wstunnel​

Connecting​

  1. Start wstunnel first:

    wstunnel client wss://vpn.example.com:4443 -L udp://51820:127.0.0.1:51820

    (Replace vpn.example.com with your VPN server hostname. The first lines of each wg-https-*.conf carry the exact command for that config.)

    A full tunnel would route wstunnel's own connection into the tunnel it carries. Before you activate wg-https-full.conf, route the server's address through the gateway you use now, for example sudo ip route add 203.0.113.10 via 192.168.1.1 on Linux. The config's first lines give the macOS and Windows forms.

  2. Then import and activate the wg-https-*.conf config in your WireGuard client

  3. The VPN traffic is tunnelled over WebSocket/TLS on port 4443, which looks like normal HTTPS to firewalls

Wrapper Script (Linux/macOS)​

#!/usr/bin/env bash
# Start wstunnel + WireGuard in one command
VPN_SERVER="vpn.example.com"
WS_PORT="4443"

wstunnel client "wss://${VPN_SERVER}:${WS_PORT}" -L udp://51820:127.0.0.1:51820 &
WSTUNNEL_PID=$!
sleep 2

sudo wg-quick up ./wg-https-split.conf

# Cleanup on exit
trap 'sudo wg-quick down ./wg-https-split.conf; kill $WSTUNNEL_PID' EXIT
wait

OpenVPN Client Setup​

Linux​

# Ubuntu/Debian - Add OpenVPN repository
curl -fsSL https://swupdate.openvpn.net/repos/openvpn-repo-pkg-key.pub | sudo gpg --dearmor -o /etc/apt/keyrings/openvpn.gpg
echo "deb [signed-by=/etc/apt/keyrings/openvpn.gpg] https://swupdate.openvpn.net/community/openvpn3/repos/$(lsb_release -cs) $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/openvpn3.list
sudo apt update
sudo apt install -y openvpn3

# Import and connect
openvpn3 config-import --config alice-udp-split.ovpn --name culvert
openvpn3 session-start --config culvert

Download: https://openvpn.net/client/

Install OpenVPN (Open Source - Certificate Only)​

# Ubuntu/Debian
sudo apt update && sudo apt install -y openvpn

# Fedora/RHEL
sudo dnf install -y openvpn

# Arch
sudo pacman -S openvpn

Minimum version: 2.6.0 (run openvpn --version to check)

Direct Connection (UDP/TCP)​

# Connect (runs in foreground, Ctrl+C to disconnect)
sudo openvpn --config alice-udp-split.ovpn

# Or run in background
sudo openvpn --config alice-udp-split.ovpn --daemon
sudo killall openvpn # To disconnect

HTTPS Tunnel (stunnel + OpenVPN)​

The HTTPS tunnel wraps OpenVPN in TLS on port 443, so on the wire it is an ordinary HTTPS session. That is what gets it through networks which only permit web traffic - and past filters that block VPN protocols by signature.

Install stunnel:

# Ubuntu/Debian
sudo apt install -y stunnel4

# Fedora/RHEL
sudo dnf install -y stunnel

# Arch
sudo pacman -S stunnel

Connect via HTTPS tunnel:

# Terminal 1: Start stunnel (keep running)
stunnel alice-stunnel.conf

# Terminal 2: Connect OpenVPN through stunnel
sudo openvpn --config alice-https-split.ovpn

One-liner (background):

stunnel alice-stunnel.conf &
sleep 2
sudo openvpn --config alice-https-split.ovpn --daemon

Disconnect:

sudo killall openvpn
killall stunnel

Linux: Always-On OpenVPN (systemd)​

Create a systemd service for automatic VPN connection on boot.

For direct connection (UDP/TCP):

# Copy config to system directory
sudo mkdir -p /etc/openvpn/client
sudo cp alice-udp-split.ovpn /etc/openvpn/client/culvert.conf

# Enable and start
sudo systemctl enable openvpn-client@culvert
sudo systemctl start openvpn-client@culvert

# Check status
sudo systemctl status openvpn-client@culvert

For HTTPS tunnel (stunnel + OpenVPN):

Create stunnel service:

sudo tee /etc/systemd/system/stunnel-vpn.service << 'EOF'
[Unit]
Description=stunnel VPN tunnel
Before=openvpn-client@culvert-https.service

[Service]
Type=forking
ExecStart=/usr/bin/stunnel /etc/openvpn/client/culvert-stunnel.conf
ExecStop=/usr/bin/killall stunnel
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

Update stunnel config for daemon mode:

sudo mkdir -p /etc/openvpn/client
sudo cp alice-stunnel.conf /etc/openvpn/client/culvert-stunnel.conf

# Edit to run as daemon (change foreground = yes to foreground = no)
sudo sed -i 's/foreground = yes/foreground = no/' /etc/openvpn/client/culvert-stunnel.conf

Copy OpenVPN config:

sudo cp alice-https-split.ovpn /etc/openvpn/client/culvert-https.conf

Create OpenVPN service dependency:

sudo mkdir -p /etc/systemd/system/openvpn-client@culvert-https.service.d
sudo tee /etc/systemd/system/openvpn-client@culvert-https.service.d/override.conf << 'EOF'
[Unit]
Requires=stunnel-vpn.service
After=stunnel-vpn.service
EOF

Enable and start:

sudo systemctl daemon-reload
sudo systemctl enable stunnel-vpn.service
sudo systemctl enable openvpn-client@culvert-https
sudo systemctl start stunnel-vpn.service
sudo systemctl start openvpn-client@culvert-https

# Check status
sudo systemctl status stunnel-vpn.service
sudo systemctl status openvpn-client@culvert-https

macOS​

Download: https://openvpn.net/client/ or from the Mac App Store

Minimum version: 3.4.0

  1. Download and install OpenVPN Connect
  2. Open the app and click Import Profile -> FILE
  3. Select your .ovpn file
  4. Click Connect

If OAuth2/SSO is enabled, your browser will open for authentication.

Option 2: Tunnelblick (Certificate Only)​

Tunnelblick is a free, open-source OpenVPN GUI for macOS.

Minimum version: 3.8.8

  1. Download and install from https://tunnelblick.net/
  2. Double-click the .ovpn file to import
  3. Click the Tunnelblick icon in the menu bar
  4. Select "Connect" for your VPN configuration

Note: Tunnelblick does not support OAuth2/SSO or stunnel. Use for certificate-only authentication with direct UDP/TCP connections.

Option 3: Command Line (Certificate Only)​

# Install via Homebrew
brew install openvpn

# Connect (may need to run with sudo)
sudo /opt/homebrew/sbin/openvpn --config alice-udp-split.ovpn

HTTPS Tunnel on macOS​

# Install stunnel
brew install stunnel

# Start stunnel
stunnel alice-stunnel.conf &

# Connect OpenVPN
sudo /opt/homebrew/sbin/openvpn --config alice-https-split.ovpn

Windows​

Download: https://openvpn.net/client/

Minimum version: 3.4.0

  1. Download and install OpenVPN Connect
  2. Open the app and click Import Profile -> FILE
  3. Select your .ovpn file
  4. Click Connect

If OAuth2/SSO is enabled, your browser will open for authentication.

Option 2: OpenVPN GUI (Supports OAuth2/SSO)​

Download: https://openvpn.net/community-downloads/

Minimum version: 2.6.0

  1. Download and install OpenVPN (includes GUI)
  2. Copy .ovpn file to C:\Users\<username>\OpenVPN\config\
  3. Right-click the OpenVPN GUI icon in system tray
  4. Select your configuration and click Connect

If OAuth2/SSO is enabled, your browser will open for authentication.

HTTPS Tunnel on Windows​

  1. Install stunnel:

  2. Configure stunnel:

    • Copy alice-stunnel.conf to C:\Program Files (x86)\stunnel\config\

    • Edit the file and update paths if needed:

      # Windows paths use forward slashes or escaped backslashes
      CApath = C:/Program Files (x86)/stunnel/certs
  3. Start stunnel:

    • Run stunnel from Start Menu, or

    • Open Command Prompt as Administrator:

      "C:\Program Files (x86)\stunnel\bin\stunnel.exe" "C:\Program Files (x86)\stunnel\config\alice-stunnel.conf"
  4. Connect OpenVPN:

    • Import and connect the *-https-split.ovpn file as normal

Windows: Always-On OpenVPN​

Using OpenVPN Connect:

  1. Open OpenVPN Connect settings
  2. Enable "Launch on Windows startup"
  3. Enable "Connect on launch" for your profile

Using OpenVPN GUI:

  1. Install OpenVPN with the option "Launch on Windows startup"
  2. Copy .ovpn to C:\Users\<username>\OpenVPN\config\
  3. Right-click the config in system tray -> Connect on startup

Using OpenVPN Service:

  1. Copy .ovpn to C:\Program Files\OpenVPN\config\
  2. Rename to client.ovpn
  3. Open Services (services.msc)
  4. Find "OpenVPN Service" -> Set to "Automatic"
  5. Start the service

Troubleshooting​

OAuth2/SSO Issues​

  • "Browser doesn't open": Ensure you're using OpenVPN Connect 3.4.0+
  • "Authentication failed": Check with your administrator that your account has VPN access
  • "Redirect URL mismatch": Contact administrator - OAuth2 callback URL may be misconfigured

Connection Timeouts​

  1. Check firewall: Ensure outbound UDP/TCP 1194 or TCP 443 is allowed
  2. Try different protocol: If UDP fails, try TCP; if TCP fails, try HTTPS tunnel
  3. Check DNS: Verify your VPN server hostname resolves correctly from the client

TLS Handshake Failed​

  • Ensure system time is accurate (TLS certificates are time-sensitive)
  • Check if your network blocks VPN protocols (use HTTPS tunnel)
  • Verify you're using OpenVPN 2.6.0+ (older versions may not support TLS 1.3)

HTTPS Tunnel Issues​

  • Verify stunnel is running before starting OpenVPN
  • Check stunnel logs for certificate verification errors
  • Ensure the .ovpn file connects to 127.0.0.1:1195 (local stunnel)

WireGuard Issues​

  • "Handshake did not complete" - check that the server is reachable on port 51820 (or 4443 for the HTTPS tunnel)
  • DNS not working - check DNS settings in the .conf file
  • Connected but no internet - try the split tunnel config instead of full tunnel
  • wstunnel connection refused - ensure WireGuard over HTTPS is enabled on the server (CULVERT_WG_HTTPS_TUNNEL_ENABLED=true)

Handshake succeeds but nothing flows, in a container​

Running the client inside a container or a Kubernetes pod, on a full tunnel (AllowedIPs = 0.0.0.0/0)? Look for this line in the wg-quick up output:

[#] sysctl -q net.ipv4.conf.all.src_valid_mark=1
sysctl: setting key "net.ipv4.conf.all.src_valid_mark", ignoring: Read-only file system

wg-quick routes a full tunnel with an fwmark and a policy rule, and that needs src_valid_mark=1 or strict reverse-path filtering discards every encrypted packet coming back. A container's /proc/sys is read-only however many capabilities it holds, so wg-quick's own attempt fails - and it fails quietly, leaving a tunnel that shows a recent handshake and carries no data. wg show gives it away: a few hundred bytes sent, ~92 bytes received, and nothing more.

Two ways out:

  • Set the sysctl from a privileged container in the same network namespace before the client starts. In Kubernetes that is an init container: command: ["sysctl", "-w", "net.ipv4.conf.all.src_valid_mark=1"] with securityContext.privileged: true.
  • Or use the split tunnel config, which routes normally and needs no sysctl.

This is a client-side container limitation, not a server setting - the same config works unchanged on a normal host.

Split vs Full Tunnel​

  • Split tunnel: Only routes to your site's internal networks go through the VPN
  • Full tunnel: All traffic, internet included. OpenVPN refuses IPv6 while it is up, as the tunnel carries IPv4 only.

Security Notes​

  • Keep .ovpn and .conf files secure - they contain your VPN credentials or keys
  • OpenVPN uses TLS 1.3 with AES-256-GCM encryption (the CNSA 2.0 classical suite)
  • tls-crypt-v2 provides metadata protection and DoS mitigation
  • HTTPS tunnel adds an additional layer of TLS 1.3 encryption
  • WireGuard uses ChaCha20-Poly1305 encryption with Curve25519 key exchange
  • WireGuard over HTTPS (wstunnel) adds a WebSocket/TLS layer on port 4443

Support​

For issues, contact your system administrator or open a ticket.

Internal networks accessible via VPN​

The list of internal networks routed through the VPN is configured by your administrator via CULVERT_PUSH_ROUTES. Consult your site's documentation for the specific routes.