System-Wide Deployment: Cloudflare Tunnel + oauth2-proxy
This guide covers the system-wide deployment mode where kaged runs as a system service behind a Cloudflare Tunnel, with oauth2-proxy handling authentication via the header contract defined in ADR-0007.
Architecture
operator device
│
▼
Cloudflare Tunnel (TLS termination)
│
▼
oauth2-proxy (OIDC auth, header injection)
│ injects: X-Kaged-User-Id, X-Kaged-Auth-Nonce, X-Kaged-User-Email
▼
kaged daemon (127.0.0.1:7777)
The daemon validates the nonce and trusts the identity headers. It implements zero OIDC/OAuth logic itself.
Prerequisites
- A Linux host (ARM64 or x86_64) with systemd
- A domain managed by Cloudflare
cloudflaredinstalled and authenticated (cloudflared tunnel login)- An OIDC provider (Google, GitHub, Authentik, Keycloak, etc.) with an OAuth2 client registered
- kaged binary at
/usr/local/bin/kaged
Step 1: Create the kaged system user
sudo useradd --system --home-dir /var/lib/kaged --shell /usr/sbin/nologin kaged
sudo mkdir -p /var/lib/kaged
sudo chown kaged:kaged /var/lib/kaged
Step 2: Install the daemon config
Create /etc/kaged/config.toml:
[daemon]
bind = "127.0.0.1:7777"
home = "/var/lib/kaged"
[auth]
mode = "secure"
[storage]
url = "sqlite:///var/lib/kaged/kaged.db"
[sandbox]
mode = "enabled"
default_seccomp = "default"
[logging]
operational = "stderr"
audit = "file:/var/lib/kaged/audit.log"
level = "info"
[plugins]
dir = "/var/lib/kaged/plugins"
enabled = []
[ui]
serve = true
Create /etc/kaged/env with the shared nonce:
# Generate a fresh nonce at each deploy
KAGED_AUTH_SIDECAR_NONCE=$(openssl rand -hex 32)
echo "KAGED_AUTH_SIDECAR_NONCE=${KAGED_AUTH_SIDECAR_NONCE}" | sudo tee /etc/kaged/env
sudo chmod 600 /etc/kaged/env
sudo chown kaged:kaged /etc/kaged/env
Step 3: Install the systemd unit
Copy the system-wide unit from the examples:
sudo cp examples/deployment/systemd/kaged.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now kaged
Verify the daemon is running:
sudo systemctl status kaged
curl http://127.0.0.1:7777/healthz
Step 4: Configure oauth2-proxy
Copy the reference config from examples/deployment/cloudflare-oauth2-proxy/oauth2-proxy.cfg and edit:
- Set
client_idandclient_secretfrom your OIDC provider - Generate a
cookie_secret(python3 -c "import secrets; print(secrets.token_urlsafe(32))") - Set
redirect_urltohttps://your-kaged-domain/oauth2/callback - Set
email_domainsto restrict access
Install oauth2-proxy as a systemd service pointing at the config file. See the compose.yaml in the same directory for a Docker-based alternative.
Critical: The oauth2-proxy must inject X-Kaged-Auth-Nonce with the same value as KAGED_AUTH_SIDECAR_NONCE in /etc/kaged/env. Both processes must agree on the nonce.
Step 5: Configure the Cloudflare Tunnel
Create a tunnel and route it to oauth2-proxy:
cloudflared tunnel create kaged
cloudflared tunnel route dns kaged kaged.yourdomain.com
Create ~/.cloudflared/config.yml:
tunnel: kaged
credentials-file: ~/.cloudflared/<tunnel-id>.json
ingress:
- hostname: kaged.yourdomain.com
service: http://127.0.0.1:4180
- service: http_status:404
Run as a system service:
sudo cloudflared service install
Step 6: Open the UI
Navigate to https://kaged.yourdomain.com. You'll be redirected through the OIDC login flow. After authentication, oauth2-proxy injects the identity headers and forwards the request to kaged.
WebSocket support
oauth2-proxy supports WebSocket forwarding by default. kaged uses WebSockets for:
- Agent output streaming (live token relay)
- PTY terminal sessions (project terminals)
No additional oauth2-proxy configuration is needed.
Rotating the nonce
The nonce should be rotated on a schedule or after any suspected compromise:
# Generate new nonce
NEW_NONCE=$(openssl rand -hex 32)
echo "KAGED_AUTH_SIDECAR_NONCE=${NEW_NONCE}" | sudo tee /etc/kaged/env
# Update oauth2-proxy config with the same nonce
# (mechanism depends on your deployment)
# Restart both services
sudo systemctl restart kaged
sudo systemctl restart oauth2-proxy
Alternative sidecars
The daemon doesn't care which sidecar you use, as long as the header contract is met:
- Pomerium: Use Pomerium's policy engine to set custom headers per route
- Authelia: Use Authelia's forward-auth mode with header injection
- Tailscale Funnel + tsidp: Map Tailscale identity headers to
X-Kaged-* - traefik-forward-auth: Same pattern as oauth2-proxy
The contract is always the same four headers. See ADR-0007 for the full specification.