WireGuard remote access VPN

A zero trust remote access VPN over WireGuard

Octelium is a free and open source, self-hosted zero trust access platform. Client-based access runs over WireGuard tunnels and behaves as a zero-config VPN: a single stable route, stable private dual-stack addresses and automatic private DNS. What makes it zero trust is what sits above the tunnel, where every request is authorized on its own.

  • Free and open source
  • Designed for self-hosting
  • Kernel WireGuard where available
service.yaml
A ServiceEach protected resource is a Service with a stable private address and DNS name.
A Service. Each protected resource is a Service with a stable private address and DNS name.
kind: Service
metadata:
  name: grafana
spec:
  mode: HTTP
  config:
    upstream:
      url: http://10.0.6.12:3000
A Policy. Connecting to the Cluster grants nothing. Each request is authorized on its own.
kind: Policy
metadata:
  name: sre
spec:
  rules:
    - effect: ALLOW
      condition:
        all:
          of:
            - match: ctx.user.spec.type == "HUMAN"
            - match: '"sre" in ctx.user.spec.groups'
            - match: ctx.session.status.type == "CLIENT"
A server behind NAT. The upstream address is resolved from the side of a connected host, so no port is opened.
kind: Service
metadata:
  name: workstation
spec:
  mode: SSH
  port: 22
  config:
    upstream:
      url: ssh://192.168.1.24
      user: alice
service-private.yaml
A private resourceA resource behind NAT in any environment, reached over the tunnel through a single stable route.
A private resource. A resource behind NAT in any environment, reached over the tunnel through a single stable route.
kind: Service
metadata:
  name: grafana
spec:
  mode: HTTP
  config:
    upstream:
      url: http://10.0.6.12:3000
A group of resources. A Namespace groups Services and gives them a shared private DNS suffix and Policies.
kind: Namespace
metadata:
  name: prod
spec:
  authorization:
    policies:
      - sre
The same resource, clientlessly. One added field publishes the very same Service over HTTPS for browsers and for workloads.
kind: Service
metadata:
  name: grafana
spec:
  mode: HTTP
  isPublic: true
  config:
    upstream:
      url: http://10.0.6.12:3000
policy-context.yaml
Identity and contextDevice posture, session type and group membership are attributes like any other.
Identity and context. Device posture, session type and group membership are attributes like any other.
kind: Policy
metadata:
  name: sensitive
spec:
  rules:
    - effect: ALLOW
      condition:
        all:
          of:
            - match: ctx.user.spec.type == "HUMAN"
            - match: ctx.session.status.type == "CLIENT"
            - match: ctx.device.status.osType in ["LINUX", "MAC"]
            - match: '"sre" in ctx.user.spec.groups'
The request itself. Method, path, headers and serialized JSON body are all part of the request context.
kind: Policy
metadata:
  name: api-read-only
spec:
  rules:
    - effect: ALLOW
      condition:
        all:
          of:
            - match: ctx.request.http.method == "GET"
            - match: ctx.request.http.path.startsWith("/v1/")
            - match: '"analysts" in ctx.user.spec.groups'
Beyond what a tunnel sees. A layer-4 tunnel cannot see this. The SSH user and the database user are request attributes.
kind: Policy
metadata:
  name: no-root-ssh
spec:
  rules:
    - effect: ALLOW
      condition:
        all:
          of:
            - match: ctx.request.ssh.connect.user != "root"
            - match: '"ops" in ctx.user.spec.groups'
Secretless access

Credentials that are never distributed

Application-layer credentials are stored in the Cluster as Secrets and injected into the upstream connection once a request is authorized. A tunnel on its own does nothing about the API keys, database passwords and private keys that reach a resource once the tunnel is up.

Read the secretless access guide
ssh.yaml
SSHNo private keys or passwords are distributed, and no PKI has to be operated.
SSH. No private keys or passwords are distributed, and no PKI has to be operated.
kind: Service
metadata:
  name: bastion
spec:
  mode: SSH
  port: 22
  config:
    upstream:
      url: ssh://10.0.2.14
    ssh:
      user: deploy
      auth:
        privateKey:
          fromSecret: bastion-key
PostgreSQL. The database password lives in the Cluster, and the Service forces the upstream user.
kind: Service
metadata:
  name: pg-prod
spec:
  mode: POSTGRES
  port: 5432
  config:
    upstream:
      url: postgres://10.0.4.21:5432
    postgres:
      user: analytics_ro
      database: analytics
      auth:
        password:
          fromSecret: pg-password
      sslMode: REQUIRE
APIs and SaaS. An API key is injected upstream after authorization, so a team reaches a SaaS API without holding it.
kind: Service
metadata:
  name: stripe
spec:
  mode: HTTP
  config:
    upstream:
      url: https://api.stripe.com
    http:
      auth:
        bearer:
          fromSecret: stripe-api-key
idp.yaml
Secretless workload identityWorkloads authenticate with OIDC assertions issued by the platform running them.
Secretless workload identity. Workloads authenticate with OIDC assertions issued by the platform running them.
kind: IdentityProvider
metadata:
  name: github-actions
spec:
  oidcIdentityToken:
    issuerURL: https://token.actions.githubusercontent.com
    audience: https://example.com
A workload User. A container, a CI job or an agent is a User with the same groups and Policies as a person.
kind: User
metadata:
  name: ci-runner
spec:
  type: WORKLOAD
  groups:
    - ci
An MCP gateway. AI agents reach tools through the same identity, Policies and audit trail as everyone else.
kind: Service
metadata:
  name: tools-mcp
spec:
  mode: MCP
  isPublic: true
  config:
    upstream:
      url: http://mcp.internal:8080
    mcp:
      endpoint: /mcp
Visibility and auditing

Real-time visibility at the application layer

OpenTelemetry-ready, application-layer aware, structured auditing and visibility emitted to your OpenTelemetry OTLP receivers, where they can be exported to your log management and SIEM tools.

Read about visibility and auditing
AccessLogOTLP
ALLOWlinus@acme.compg-prod.dbPOSTGRESquery: select id, email from users limit 50
ALLOWci-runnerprod-k8sKUBERNETESverb=list resource=pods namespace=production
DENYagent-07tools-mcpMCPtools/call name=transfer policy=mcp-tools
ALLOWjane@acme.combastionSSHsession recorded, upstream user=deploy
ALLOWagent-07openaiLLMCHAT_COMPLETIONS model=gpt-5-mini tokens=1284
octeliumctl
octeliumctl apply ./cluster
$ octeliumctl apply ./cluster
Secret: pg-password Updated
Secret: bastion-key Updated
IdentityProvider: okta Updated
Policy: sre Created
Group: sre Created
Group: analysts Updated
Service: grafana.prod Created
Service: bastion Created
Service: pg-prod.prod Created
Namespace: prod Created
Cluster Core resources successfully applied
 7 resources created
 3 resources updated

 ClusterConfig updated
The client

What the client does on the host, mode by mode

WireGuard is the transport, and the client adapts to whatever privileges it has. These are the parts that matter in practice, next to what each one actually does.

  1. 01

    Kernel WireGuard where it exists

    On Linux 5.6 and later, or any kernel with the WireGuard module loaded, the client uses the kernel implementation, which is by far the most performant mode. It needs root or the NET_ADMIN capability.

    Read more
  2. 02

    A userspace TUN device otherwise

    The wireguard-go TUN device implementation covers every platform, including macOS and Windows. It also needs permission to create a TUN device, and can be selected explicitly over the kernel mode.

    Read more
  3. 03

    No privileges at all, if necessary

    Without permission to create a TUN device, the client runs its own userspace network stack on gVisor Netstack as an ordinary non-root process. Services are then reached by mapping them to the host, which is what makes the client usable inside containers, CI runners and confined environments.

    Read more
  4. 04

    One route, not a routing table

    Services are addressed through the Cluster rather than through the networks their upstreams sit on, so the client keeps a single stable route however many environments, clouds and clusters are behind it. There are no remote subnets to advertise and no overlapping ranges to reconcile.

    Read more
  5. 05

    Dual-stack without translation

    The client uses a private IPv6 address by default and can be forced to IPv4-only or dual-stack, independently of what each upstream speaks, so there is no NAT64 or DNS64 to operate between them.

    Read more
  6. 06

    Private DNS that configures itself

    Every Service has a stable private FQDN assigned by the Cluster, resolved through a split DNS server the client sets up automatically using resolvectl on Linux and networksetup on macOS. Nothing has to be memorized or kept in sync as upstream addresses change.

    Read more
  7. 07

    Serving, as well as consuming

    The same client can serve resources to the Cluster with the --serve and --serve-all flags, so a laptop, a container or a pod in another cluster becomes a reachable upstream with no inbound port opened beside it.

    Read more
  8. 08

    Authorization above the tunnel

    Bringing the tunnel up grants nothing on its own. Every request is authorized against identity, device, session and the application-layer request itself, which is the part a WireGuard configuration cannot express.

    Read more

A WireGuard configuration describes peers, keys and allowed addresses. None of those are identities, and none of them can express that a particular person may run one query on one database at one time of day.

Questions

Frequently asked

Is this real WireGuard?
Yes. Client-based access runs over WireGuard, using the kernel implementation where it is available and an unprivileged userspace implementation elsewhere. A QUIC-based tunneling mode is supported alongside it. Everything above the transport, including identity, Policies, secretless access and auditing, is identical either way.
Do clients need root privileges?
Not necessarily. On Linux the client first tries the WireGuard kernel implementation, which needs root or the NET_ADMIN capability. Without those it falls back to a wireguard-go TUN device, and without permission to create a TUN device it runs an unprivileged userspace network stack over gVisor Netstack. In that last mode Services are reached by mapping them to the host, which is what makes the client work inside containers, CI runners and IoT devices.
Do I have to manage peers and keys?
No. There is no peer list to maintain, no key to distribute and no configuration file to hand out. A person runs octelium connect, authenticates through your identity provider, and the Cluster establishes the tunnel and assigns addressing and DNS. A Session can be revoked centrally, which ends access immediately.
What about routes and split tunneling?
Each Service is reached through a single stable route, so there are no remote subnets to advertise into a user's routing table and no overlapping ranges to reconcile. Because only Cluster traffic uses the tunnel, ordinary internet traffic is never pulled through a gateway to keep a connection usable, which is the usual reason full tunneling gets switched on.
Can resources behind NAT be reached without opening a port?
Yes. A Service upstream can be an address that is only reachable from the side of a connected client, which serves it to the Cluster over its own outbound tunnel using the --serve or --serve-all flags. That covers a laptop behind NAT, containers, Kubernetes pods in other clusters, private clouds and IoT devices, with no inbound port and no firewall change on that side.
Is Octelium free and open source?
Yes. Octelium is free and open source and is designed for single-tenant self-hosting. There is no proprietary cloud-based control plane, and it is not a limited edition of a separate paid product. An enterprise package is available for organizations that need capabilities such as a web console, SCIM provisioning and secret encryption at rest, and it is free for personal, homelab and evaluation use.
Get started

Deploy Octelium on your own infrastructure in minutes

Free and open source. Self-hosted. No vendor lock-in.