Skip to content

OpenVPN companion relay for Windows

Status: Installed on all eight cities | Last reviewed: 2026-09-21

Use this path when the WireGuard client cannot be installed or operated on a developer's Windows laptop. Shimla was the first validated target. This is a separate OpenVPN server beside the existing WireGuard relay; an OpenVPN client cannot use a WireGuard profile.

The September 18 fleet rollout passed full Dev/QA network checks on every city. See the rollout report for the installed revision, preserved services, laptop acceptance and Grafana token status. Following the shared Dev token rotation, authenticated Dev and QA log queries pass from all eight cities and all six active devcontainers.

The complete laptop-only guide, also included in each private bundle, is Windows OpenVPN setup. It explains installation, import, connection, testing, switching and cleanup. Require OpenVPN Community 2.7+, OpenVPN GUI, Interactive Service and DCO. Device policy may still require IT installation. The separate WireGuard PowerShell helper is not used for OpenVPN DNS.

For periodic host DNS refresh, laptop startup/recovery and 30-minute health checks, see Automatic laptop access maintenance. This optional pilot requires updated client helpers; existing manual profiles retain their snapshot behavior.

Shimla design

Setting Value
Private endpoint 10.120.206.38:51830/udp
OpenVPN subnet 10.254.248.0/24
Scoped DNS 10.254.248.1
Container trueserve-openvpn
Private state /var/lib/trueserve-openvpn
Existing WireGuard Remains on UDP 51828 and 10.254.245.0/24

The laptop first connects its existing core VPC VPN. OpenVPN then carries the explicit TrueServe IPv4 destinations through Shimla's existing egress path. Dev and QA work in one both profile. UDP is supported, so WebRTC does not depend on an HTTP proxy. Actual browser audio still needs a laptop test.

The container has its own network namespace, forwarding firewall and NAT. Forwarding is limited to the endpoint snapshot and explicitly reviewed extra media routes. Clients can reach its DNS service but not other container listeners or one another. Private-address destinations are allowed for the official management services. The host's routes, application branch, application containers and WireGuard state must remain unchanged.

No default route, global DNS server, block-outside-dns, or IPv6 tunnel route is installed. The scoped resolver serves exact known names and filters AAAA responses. Traffic to destinations outside the bundle, including new media addresses, is not automatically proxied.

Prepare a city host

For fleet installations, transfer the committed scripts/studio-access/ runtime and related guides into /opt/trueserve-access/releases/<commit>. Use the city values in cities.json, then run on that city host:

release=/opt/trueserve-access/releases/FULL_COMMIT_SHA
sudo python3 "$release/scripts/studio-access/install-city.py" \
  --city CITY --endpoint CITY_PRIVATE_IP --network CITY_WIREGUARD_SUBNET \
  --revision FULL_COMMIT_SHA --files-only
sudo python3 "$release/scripts/studio-access/install-openvpn-city.py" \
  --city CITY --revision FULL_COMMIT_SHA

The first command updates tools, laptop guides and managed agent instructions on an existing WireGuard relay without rebuilding or restarting it. The second creates the OpenVPN companion if needed; an existing running OpenVPN container keeps its process, image, credentials and connected clients. Neither command changes the application branch or restarts application services. The two current symlinks may point to the same committed tools release, while private state remains separate. Later deliberate runtime/image upgrades need a planned relay restart.

The files-only update checks the managed container and probes upstreams without requiring an optional wg show process inside it. Keep development tools and AI assistants in the normal city devcontainer, not attached to a VPN container; the relay containers have small process budgets for networking services.

City Private OpenVPN endpoint OpenVPN subnet
Richmond 10.120.206.52:51830/udp 10.253.240.0/24
Rome 10.120.206.10:51830/udp 10.253.241.0/24
Kyiv 10.120.206.42:51830/udp 10.253.242.0/24
Istanbul 10.120.206.24:51830/udp 10.253.243.0/24
Macau 10.120.206.58:51830/udp 10.253.244.0/24
Shimla 10.120.206.38:51830/udp 10.254.248.0/24
Seattle 10.120.206.47:51830/udp 10.253.246.0/24
Athens 10.120.206.59:51830/udp 10.253.247.0/24

DNS is .1 in each subnet. To enroll an actual laptop after validation:

sudo trueserve-openvpn add-peer alex-windows

Deliver /var/lib/trueserve-openvpn/exports/alex-windows.zip privately. No developer certificate is shared across cities or laptops. The application checkout also receives the scripts and guides so city assistants can find them, but privileged manager commands must run on the host.

Manual single-city setup

Run on the EC2 host, with sudo, not inside its devcontainer. For a remote city, use SSM Run Command to execute these host operations. Copy only openvpn.py, relay.py, OpenVPN.Dockerfile, openvpn-gateway.sh, .dockerignore, probe.py, environments.json and OPENVPN-WINDOWS.md to a versioned directory under /opt/trueserve-openvpn/releases/. Keep that release separate from the application checkout and existing WireGuard release. Do not move its symlink.

Before installation, record application HEAD, running container IDs and startup times. Check host routes for subnet overlap, UDP listeners for a free port, /dev/net/tun, and the city security group's existing rules. The private UDP endpoint must be reachable through the core VPN. Do not expose a new public listener or broaden security groups automatically.

Example after placing the release at /opt/trueserve-openvpn/current:

manager=/opt/trueserve-openvpn/current/scripts/studio-access/openvpn.py
sudo python3 "$manager" build
sudo python3 "$manager" init --city shimla --network 10.254.248.0/24 --port 51830
sudo python3 "$manager" add-peer jyoti-windows
sudo python3 "$manager" start
sudo python3 "$manager" status

Initialization uses the already-reviewed /var/lib/trueserve-access/state.json endpoint and DNS snapshot. It validates that the new subnet differs from WireGuard and host routes, and rejects recursive/metadata/broad routes. Resolve missing/stale endpoint records in the existing inventory before initialization. Do not delete state and rerun init to update DNS: that would replace the CA. This pilot does not automatically track DNS changes. When addresses change, first update and verify the existing WireGuard inventory using its operator workflow. Then schedule a brief OpenVPN interruption and run:

sudo python3 "$manager" stop
sudo python3 "$manager" refresh
sudo python3 "$manager" start
sudo python3 "$manager" export jyoti-windows

refresh preserves the CA and peers, imports the reviewed records, and refuses to run while an OpenVPN container exists. Verify the relay again and distribute replacement bundles to every enrolled laptop so client routes match the server.

Each city needs a distinct nonoverlapping OpenVPN subnet and a checked port. The example above is for Shimla, not a command to run unchanged on every city.

Stage new addresses before replacing client profiles

If the published application addresses change while the old ones still work, verify the new addresses with normal TLS/SNI and stage additional routes before changing the shared DNS snapshot. For the September 21 QA address change:

sudo trueserve-openvpn stage-routes --environment qa 52.40.176.56/32 54.185.47.193/32
sudo trueserve-openvpn export jyoti-windows

This adds only the reviewed forwarding destinations, persists them in the server configuration and includes them in newly exported qa and both profiles. It preserves DNS answers, existing routes, certificates and active sessions, and does not restart the container. The dev profile does not gain these QA-only routes. This is a controlled transition, not automatic DNS discovery.

Deliver the refreshed private bundle. The Windows user keeps the core VPN up, disconnects only the TrueServe OpenVPN profile, replaces the imported profile with its refreshed .ovpn file, then reconnects. After all affected clients have the new routes, the operator can move the shared DNS snapshot using the reviewed refresh workflow above. While the old addresses remain usable, avoid switching DNS first: an old client could receive an address it does not route into its tunnel.

On September 21, Shimla's old QA application addresses stopped working: one timed out and the other failed hostname certificate validation. The OpenVPN relay now serves 52.40.176.56 and 54.185.47.193 for the QA application names. The routes were staged before this controlled DNS update; existing client identities, container process and earlier destinations were preserved. Jyoti must import the refreshed private both profile and reconnect. Reconnecting her previous profile alone does not install the new routes. Keep the core VPN connected and leave the previous TrueServe profile disconnected.

A separate temporary OpenVPN client verified both new addresses with valid TLS/SNI, QA Studio HTTPS, Janus WebSocket upgrade, TURN UDP binding/TLS, and QA management HTTPS. Dev Studio, Janus and TURN checks also passed. Dev management initially timed out because its private addresses had changed too. Shimla's automatic refresh pilot subsequently updated those records, and a fresh OpenVPN client passed all Dev/QA network checks, including ArgoCD, Dex and Grafana. The OpenVPN refresh timer is active on Shimla; laptop automatic startup still requires the one-time helper installation and native acceptance. These checks do not replace Jyoti's Windows browser and two-way audio test.

Credentials, delivery and revocation

The manager creates a private local CA, a server certificate and a separate certificate plus TLS control-channel key for each laptop. Client certificates expire after 30 days, server certificates after 365 days, and the CA after 730 days. Track those dates; certificate renewal is not automatic in this pilot. For a replacement client, revoke the old device and issue a new device name. Back up state through an approved encrypted host backup, not Git.

add-peer exports a private ZIP at:

/var/lib/trueserve-openvpn/exports/jyoti-windows.zip

Deliver it through an authenticated private channel. Do not print its contents in SSM output, commit it, or publish it on a city web server. It includes both, dev, qa profiles and START-HERE.md. The both profile needs only one connection to support both environments. The environment variants limit routes, not authorization: normal application access controls still apply.

sudo python3 "$manager" export jyoti-windows
sudo python3 "$manager" revoke jyoti-windows

Revocation records the certificate serial, removes its allowed client entry, disconnects its active session and removes exported private keys. Do not reuse a revoked device name. Other peers keep working.

To stop/remove only the OpenVPN container:

sudo python3 "$manager" stop

State and the existing WireGuard relay remain. start creates the container again. It deliberately refuses to replace an existing named container.

Validation

Shimla was tested on 2026-09-18 using a separate authenticated OpenVPN client through 10.120.206.38:51830. Full Dev and QA checks passed: Studio HTTP 200, Janus WebSocket 101, UDP STUN, TLS TURN, ArgoCD, Dex, Grafana and shared SSO. The client's default route was preserved; scoped DNS returned the expected addresses and no AAAA answers; a non-inventory destination was blocked. The relay recovered after a container restart and QA passed again. Revoking the temporary identity disconnected its live session and the server rejected its cached certificate on a fresh connection attempt. Windows driver/NRPT and a browser call remain the laptop acceptance checks. The user subsequently confirmed that Jyoti's Windows OpenVPN setup on Shimla worked. That confirms her laptop path; it does not prove browser media on every other city or device.

Issue a temporary device and connect an isolated OpenVPN client through the published private endpoint. Check its assigned address, scoped DNS, preserved default route, rejection of destinations outside the inventory, and execute probe.py --environments qa and probe.py --environments both --transport-only. The probe checks HTTPS, WebSockets, UDP STUN, TLS TURN and management endpoints. Report application health separately from network transport. Revoke the test device and verify it cannot reconnect. Verify original containers and application HEAD still match the preflight snapshot.

Linux probe containers require their DNS explicitly pointed at the tunnel resolver: the Windows profile's native NRPT handling is not a Linux DNS script. A Linux test proves relay transport, not Windows driver installation or NRPT. Only the Windows laptop test can complete those checks.

References