Voltrus Remote Documentation

Overview

Voltrus Remote gives support engineers secure remote access to industrial PLC networks — without TeamViewer and without ad-hoc VPNs punched through the OT firewall.

A lightweight gateway enrols into a self-hosted WireGuard mesh (Headscale). The support engineer tunnels in and reaches the PLC subnet directly, so native engineering tools — TIA Portal, GX Works, SoMachine — talk to the PLC exactly as if they were on the local network.

MVP scope: reach-PLC only. The first release validates the hard, valuable use case: reaching a PLC subnet over a managed tunnel. Screen-share, time-boxed grants, session recording, and billing are on the roadmap (see below).

Architecture

   support engineer              gateway (OT side)               PLC
   ┌────────────┐                ┌──────────────┐         192.168.10.0/24
   │  client    │◄── WireGuard ─►│  agent + WG  │────────────► PLC @ .5
   │ (--accept- │    mesh        │ (advertise   │   S7 TCP102 / Modbus TCP502
   │  routes)   │   UDP/443 or   │  subnet)     │
   └─────┬──────┘    DERP        └──────┬───────┘
         │                            │ HTTPS heartbeat
         │ enrol (preauthkey)         ▼
         │                    ┌─────────────────┐
         │                    │ control-api (Go)│  Voltrus auth, grants,
         │                    │ /health /nodes  │  billing hooks, audit
         │                    └────────┬────────┘
         │                             │ Bearer /api/v1
         │                             ▼
         └────── dashboard ───►┌─────────────────┐
                  (Next.js)     │   Headscale     │  WireGuard control plane
                                │ (self-hosted)   │  pubkey registry + ACL
                                └─────────────────┘

Components

  • Headscale — the WireGuard control plane. Holds pubkeys, enforces ACLs, issues preauthkeys, and runs the DERP relay (the fallback path through restrictive OT firewalls).
  • agent (gateway, Go) — enrols via tailscale up --login-server=<headscale>, advertises only the PLC subnet(s) behind it, and heartbeats liveness to the control API.
  • control-api (Go) — the Voltrus backend. Wraps the Headscale HTTP API and adds device health, access grants, and (later) billing, multi-tenancy, and audit.
  • dashboard (Next.js) — gateway inventory and live status for the support engineer / admin.

Installation

Voltrus Remote ships as a Docker Compose stack: Headscale + control-api + dashboard. The gateway agent is a single Go binary deployed on the Linux machine at the edge of the OT network.

Control plane (your server / VPS)

cp .env.example .env
docker compose up -d headscale
docker exec voltrus-headscale headscale users create default
docker exec voltrus-headscale headscale apikeys create   # -> HEADSCALE_API_KEY
echo "VOLTRUS_DEVICE_TOKEN=$(openssl rand -hex 32)"       # -> .env
docker compose up -d
# dashboard:    http://localhost:3000
# control-api:  http://localhost:8081

Gateway agent (OT edge, Linux)

The agent drives the official tailscale client at your Headscale — it does not reimplement WireGuard crypto. It needs NET_ADMIN to manage the tunnel interface.

# enrol a gateway and advertise the PLC subnet behind it
./voltrus-remote-agent enrol \
  --hostname siteA-gw \
  --routes 192.168.10.0/24

Quick Start

  1. Bring up the control plane on a server reachable from the OT network over outbound HTTPS/UDP.
  2. Enrol a gateway on the Linux machine at the OT edge. It advertises only the specific PLC CIDR (e.g. 192.168.10.0/24).
  3. Connect as the support engineer with --accept-routes. Your engineering laptop now has a route to the PLC subnet.
  4. Open your engineering tool (TIA Portal, GX Works, SoMachine) and point it at the PLC's local IP — e.g. 192.168.10.5. S7 on TCP/102 or Modbus on TCP/502 reach the PLC directly over the tunnel.
The PLC is never exposed to the internet. The only outbound connection is the gateway phoning home to the control plane.

Configuration

Subnet advertisement

The gateway advertises only the PLC CIDR you specify — not the whole corporate LAN. This is enforced both at the agent and by the Headscale ACL. A site with PLCs at 192.168.10.0/24 advertises exactly that prefix.

ACL policy (default-deny)

Headscale enforces a policy.hujson ACL. The default stance is deny: an engineer can reach only the RFC1918 PLC subnets explicitly listed, never the wider corporate LAN or the public internet through the tunnel.

DERP relay fallback

When symmetric NAT prevents a direct WireGuard path, traffic falls back to the Headscale DERP relay over TCP/443 — the port almost every OT firewall already allows outbound for HTTPS. Direct UDP/41641 is preferred when reachable; DERP is the safety net.

Security Model

ThreatMitigation
PLC exposed to the internetOutbound-only agent. No inbound ports opened on the OT firewall.
Engineer reaches corp LAN / internetACL is default-deny; destinations limited to the advertised PLC subnets.
Lateral movement across sitesEach subnet router advertises only its specific PLC CIDR.
Private key theftThe WireGuard private key never leaves the device. Headscale holds the public key only.
Standing accessRoadmap: time-boxed grants (e.g. 2h), default-deny between sessions.

Crypto is not reimplemented — the agent drives the audited Noise/WireGuard implementation in the official tailscale client.

Troubleshooting

Gateway shows online but PLC unreachable

Symptom: The dashboard shows the gateway healthy, but ping 192.168.10.5 fails from the engineer client.
Fix: The engineer client must connect with --accept-routes, or it will not install the advertised subnet route. Then confirm the advertised CIDR actually covers the PLC IP and that the ACL permits the engineer's identity to that subnet.

Tunnel only works over DERP (no direct path)

Symptom: Traffic flows but latency is high; logs show derp relay.
Fix: Symmetric NAT on the OT side is blocking UDP/41641. DERP over TCP/443 is the intended fallback. If you need a direct path, allow outbound UDP/41641, or lower MTU on the gateway interface (try 1280) to rule out fragmentation.

Enrolment fails (preauthkey rejected)

Symptom: tailscale up against the Headscale login server errors out.
Fix: Confirm the preauthkey is valid and unused, the agent's clock is correct (Noise handshake is time-sensitive), and the gateway can reach the Headscale control URL over HTTPS.

Engineering tool can discover but not connect

Symptom: TIA Portal sees the PLC but the connection drops.
Fix: Some protocols open additional ephemeral ports. Confirm the PLC's full service port set (S7 = TCP/102; Modbus = TCP/502) is within the advertised subnet and permitted by the ACL. Check MTU — S7 connection setup is sensitive to fragmentation.

Roadmap

Voltrus Remote is in active development. The MVP delivers reach-PLC; the rest follows.

  • MVP (now): reach-PLC over a self-hosted WireGuard mesh. Linux gateway.
  • Next: time-boxed access grants with expiry scheduler and default-deny between sessions.
  • Then: session recording / audit (pcap on gateway, or RustDesk recording) and screen-share.
  • Later: billing, multi-tenant isolation, Windows HMI agent, and an on-prem (air-gapped) Headscale option.

Support

Voltrus Remote is pre-launch. To join the early-access cohort for your deployment, reach out directly.