QUIC remote access VPN

A zero trust remote access VPN over QUIC

Octelium is a free and open source, self-hosted zero trust access platform. Client-based access runs over QUIC or 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
  • QUIC and WireGuard transports
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 database. The upstream password stays in the Cluster. Users connect without ever receiving it.
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
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
Served from a connected host. The upstream address is resolved from the side of a connected client, so nothing needs an inbound port.
kind: Service
metadata:
  name: workstation
spec:
  mode: SSH
  port: 22
  config:
    upstream:
      url: ssh://192.168.1.24
      user: alice
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'
HTTP. 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'
SSH and databases. Layer-4 tunnels 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 Created
Service: pg-prod.db Created
Service: bastion Created
Namespace: db Created
Cluster Core resources successfully applied
 7 resources created
 3 resources updated

 ClusterConfig updated
Remote access

What a remote access VPN leaves open, and what closes it

A tunnel solves reachability. These are the problems that remain once the tunnel is up, next to the mechanism in an Octelium Cluster that handles each one.

  1. 01

    Route tables that grow with the network

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

    Read more
  2. 02

    Split tunneling as a workaround

    Because only Cluster traffic uses the tunnel, ordinary internet traffic never has to be pulled through a gateway to keep a connection usable, which is the usual reason full tunneling gets switched on and the usual source of its egress cost.

    Read more
  3. 03

    Dual-stack translation

    Clients connect with a private IPv6 address by default and can be forced to IPv4-only or dual-stack, independently of what each upstream speaks. No NAT64 or DNS64 has to be operated to bridge the two.

    Read more
  4. 04

    Keeping a private DNS in sync

    Every Service has a stable private FQDN assigned by the Cluster and resolved through an automatically configured split DNS, so dynamic upstreams such as containers, Kubernetes services and cloud-assigned endpoints do not need a DNS zone maintained by hand.

    Read more
  5. 05

    Access described at the network level

    Authorization is expressed against Services, identities and requests rather than IP addresses and subnets, so a new database created inside an allowed subnet is not automatically reachable, and a reused address does not silently inherit an old permission.

    Read more
  6. 06

    Direct exposure of the resource

    Traffic terminates at an identity-aware proxy that understands the protocol in front of each upstream, so the upstream is not directly reachable by whoever is on the network, including an attacker already inside the perimeter.

    Read more
  7. 07

    Credentials that go stale

    Human Users re-authenticate through your identity provider and workload Users through short-lived assertions or OAuth2 credentials, and application-layer credentials for upstreams are held by the Cluster rather than copied to laptops and CI systems.

    Read more
  8. 08

    Logging that stops at layer 4

    Every request produces an identity-aware AccessLog with the application-layer detail of the request itself, streamed over OpenTelemetry, instead of connection records that show an address reached a port.

    Read more

Encrypting the path between two networks is a transport problem and a solved one. What is left is deciding, per request, who may reach which resource and with which credential, and being able to show afterwards what happened.

Questions

Frequently asked

Is the tunnel QUIC or WireGuard?
Both are supported. Octelium uses WireGuard for tunneling by default, and a Cluster can additionally enable a QUIC-based tunneling mode. When the Cluster has it enabled, a client selects it with octelium connect --tunnel-mode quicv0 or by setting the OCTELIUM_QUIC environment variable. Everything above the transport, including identity, Policies, secretless access and auditing, is identical either way.
How is the QUIC mode enabled on a Cluster?
The quick installer takes a --quicv0 flag, and existing Clusters enable it through the pre-installation configuration. The QUIC listener uses UDP port 8443, so that port needs to be open if the machine sits behind a firewall, and an additional public DNS entry is required. WireGuard remains available alongside it.
Do clients need root privileges?
Not necessarily. On Linux the client first tries the WireGuard kernel implementation, which is the most performant and 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 usable inside containers, CI runners and confined environments.
What happens to DNS?
Each Service has a private FQDN in the form service.namespace.local.yourdomain, with a shorter form for the default Namespace. The client configures a split DNS server for that suffix automatically, using resolvectl on Linux and networksetup on macOS. Nothing has to be memorized, distributed or kept in sync as upstream addresses change.
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 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.