Sidecar authentication & configuration
The sidecar authenticates with a registration token and reads its settings from a YAML config file, with environment variables as overrides. This page covers the sidecar’s credentials and configuration in depth — for the platform-wide access model (user roles, API key scopes), see Authentication & permissions.
Registration tokens
Section titled “Registration tokens”Registration tokens (rz_reg_…) are issued by an Admin or Owner in
Settings → Hosts → Registration tokens. They are deliberately
single-purpose — a registration token can only register hosts; it cannot
read scans, findings, hosts, or anything else. They are:
- Per-org — a token registers hosts into exactly one organization.
- Expiring — you choose a lifetime at creation (default 90 days). An expired token stops registering new hosts; already-registered hosts keep heartbeating (heartbeats authenticate with a per-host credential, below).
- Revocable — revoking a token immediately blocks new registrations with it. Revoking a host immediately removes that host from scan scope.
Rotate by issuing a new token, rolling it out to your deploy pipeline, then revoking the old one — registrations made with the old token are unaffected.
Host credentials
Section titled “Host credentials”During registration the sidecar derives a host credential, stored only on
that host (under /var/lib/ringzero) and rotated automatically. It
authenticates heartbeats, challenge responses, and
deregistration
for that one host and nothing else: even a fully compromised host can’t read
your org’s data, register other hosts, or touch the API beyond its own
lifecycle. You never handle host credentials directly — they live and die
with the registration.
Registration lifecycle
Section titled “Registration lifecycle”On every start the sidecar re-attests and answers a fresh challenge — registration is not a one-time event. The rules:
- Idempotent per host. If the control plane matches an existing host
(same org + host identity), the existing
hst_…record is updated — addresses, labels, sidecar version — never duplicated. Restarting or upgrading the sidecar keeps the same host record with no gap in scan scope beyond the restart itself. - Labels are re-attested on registration. To change a host’s labels, edit
the config (or
RINGZERO_LABELS) and restart. Heartbeats don’t carry labels. Labels feed scan-time scoping. - Heartbeats retry, never slow down. Transient failures are retried with jittered backoff within the 60-second interval — the 3-missed-beats expiry math is the product’s freshness guarantee, so the sidecar never voluntarily stretches it. An API outage shorter than ~3 minutes costs nothing.
expiredhosts self-heal. If heartbeats are rejected because the host expired (say the sidecar was down for ten minutes), it automatically re-runs attestation + challenge using its host credential — no registration token needed, since it’s provably the same host. Port 7301 must be reachable at that moment.revokedis terminal. Revocation is an admin decision; the sidecar logshost_revokedand exits non-zero so your orchestrator’s restart loop surfaces it. Re-registering a revoked host requires a fresh registration token — deliberately.
Multiple sidecars on one host: a second instance in the same network
namespace fails fast with challenge_port_in_use (bind conflict on 7301). A
second instance with the same attested identity elsewhere just hits the
idempotent-re-registration path — both heartbeat the same hst_…, harmless
but pointless. One sidecar per registration unit.
Configuration file
Section titled “Configuration file”The sidecar reads /etc/ringzero/sidecar.yaml by default (override with
--config or RINGZERO_CONFIG). Full annotated reference:
# Connectivity mode. `direct` (default) = public/internet-reachable host;# RingZero dials the inbound challenge below. `gateway` = private host; the# sidecar connects OUT to an in-network Scan Gateway and everything (attest,# heartbeat, challenge) rides that outbound connection — no inbound at all.mode: direct
# Gateway mode only: the in-network Scan Gateway's sidecar-ingest address.# gateway_endpoint: 10.0.0.5:7300
# RingZero API endpoint. Only change if RingZero support tells you to.# (Not used directly in gateway mode — the gateway relays control traffic.)api_url: https://api.ringzero.tech/v1
# The registration token — provide EXACTLY ONE of the following.# Prefer registration_token_file (mounted secret) over the inline value.registration_token_file: /run/secrets/ringzero-token# registration_token: rz_reg_8c1d… # inline — avoid outside local testing
# Optional: labels attached to the host registration, visible in# Settings → Hosts and used for scan scoping. Re-attested on restart.labels: env: production service: acme-web team: platform
# Deregister on clean shutdown (SIGTERM/SIGINT). Leave false so rolling# restarts and upgrades don't flap the registration; set true for ephemeral# hosts (CI runners, preview environments).deregister_on_shutdown: false
heartbeat: # Interval between heartbeats. The host expires after 3 missed beats, # so with the default 60s a dead host leaves scan scope within ~3 minutes. interval: 60s
challenge: # DIRECT MODE ONLY. Port for the registration challenge listener, which # RingZero must reach whenever the sidecar (re)registers. Inert in gateway # mode (the challenge rides the outbound connection to the gateway instead). port: 7301 # Interface to bind the listener on. bind: 0.0.0.0 # Address (or host:port) the control plane should dial for the challenge # when the host's own interfaces aren't externally reachable (NAT, K8s). # advertise_address: 203.0.113.24
health: # Local health endpoint (GET /healthz) and Prometheus metrics # (GET /metrics). Bound to localhost by default — never expose publicly. port: 7302 bind: 127.0.0.1 metrics: true
# Outbound proxy for control-plane traffic (attestation + heartbeats).# Standard HTTPS_PROXY / NO_PROXY env vars are also honored; this key wins.# proxy:# url: http://proxy.internal:3128
log: level: info # debug | info | warn | error format: json # json | textEnvironment variable overrides
Section titled “Environment variable overrides”Every setting can be overridden by environment; env wins over file. Secrets
support _FILE variants that read the value from a file — prefer these with
Docker/Kubernetes secrets.
| Variable | Overrides | Notes |
|---|---|---|
RINGZERO_CONFIG | config file path | Default /etc/ringzero/sidecar.yaml |
RINGZERO_MODE | mode | direct (default) or gateway |
RINGZERO_GATEWAY_ENDPOINT | gateway_endpoint | Gateway mode only — the Scan Gateway’s host:7300 |
RINGZERO_API_URL | api_url | |
RINGZERO_REGISTRATION_TOKEN | registration_token | |
RINGZERO_REGISTRATION_TOKEN_FILE | registration_token_file | Preferred for secrets |
RINGZERO_LABELS | labels | Comma-separated key=value pairs. Replaces the whole map — no merging with the file’s labels |
RINGZERO_DEREGISTER_ON_SHUTDOWN | deregister_on_shutdown | |
RINGZERO_HEARTBEAT_INTERVAL | heartbeat.interval | e.g. 60s |
RINGZERO_CHALLENGE_PORT | challenge.port | |
RINGZERO_CHALLENGE_BIND | challenge.bind | |
RINGZERO_CHALLENGE_ADVERTISE_ADDRESS | challenge.advertise_address | host or host:port |
RINGZERO_HEALTH_PORT | health.port | |
RINGZERO_HEALTH_BIND | health.bind | |
RINGZERO_HEALTH_METRICS | health.metrics | |
RINGZERO_PROXY_URL | proxy.url | Beats HTTPS_PROXY |
RINGZERO_LOG_LEVEL | log.level |
Precedence, highest first: env var → config file → built-in default.
Connectivity mode
Section titled “Connectivity mode”The mode key decides how RingZero reaches this host — and it’s the single
most important choice for anything that isn’t a public cloud VM. See
Connectivity & network topologies for the
full comparison; in short:
direct(default) — public/internet-reachable hosts. Everything in the “Network requirements” table below applies, including the inbound challenge.gateway— private hosts. Setgateway_endpointto the in-network Scan Gateway; the sidecar makes one outbound connection to it and carries attestation, heartbeat, and challenge over that. No inbound 7301, and no internet egress from the host at all — it only needs to reach the gateway. Thechallenge.*andproxy.*keys are inert in gateway mode.
Network requirements
Section titled “Network requirements”These apply to direct mode. In gateway mode the host needs only outbound
reach to the gateway’s :7300 — see the
Scan Gateway page.
| Direction | What | Why |
|---|---|---|
Outbound 443 | sidecar → api.ringzero.tech | Attestation and heartbeats (TLS) |
Inbound 7301 | RingZero → sidecar | Challenge — required whenever the sidecar (re)registers |
Local 7302 | you → sidecar | /healthz + /metrics — keep private (binds 127.0.0.1 by default) |
When exactly is 7301 needed? At every registration — which means every
sidecar start, and any automatic reactivation of an
expired host. RingZero never initiates a challenge outside a
registration the sidecar itself began, so between restarts the port may be
firewalled. In practice, leave the rule in place: a restart with 7301 blocked
fails with challenge_unreachable. (If opening any inbound port is a
non-starter, that’s exactly what gateway mode
is for.)
Egress allowlist (for locked-down networks):
| Host | When |
|---|---|
api.ringzero.tech:443 | Runtime — attestation, heartbeats |
get.ringzero.tech:443 | Install-time only — binaries + checksums |
ghcr.io:443, pkg-containers.githubusercontent.com:443 | Install-time only — image pulls |
Ingress: challenge connections originate from RingZero’s published source
ranges — GET /v1/meta/ip-ranges — so you can
allowlist 7301 to those ranges instead of the whole internet. Range changes
are announced ≥30 days ahead.
Clock: the host clock must be within 60 seconds of true time (attestations are time-signed) — run NTP/chrony.
IPv6: the sidecar attests all routable v4 and v6 addresses; each is
challenge-verified independently, and
v6-only hosts are fully supported. advertise_address accepts either family.
Outbound proxy
Section titled “Outbound proxy”Set proxy.url (or rely on HTTPS_PROXY/NO_PROXY) to route control-plane
traffic through an egress proxy. The connection uses HTTP CONNECT tunneling —
TLS stays end-to-end to api.ringzero.tech, so there is no proxy-CA trust
configuration, and TLS-intercepting proxies will break the sidecar (you’ll
see certificate errors on heartbeats). The inbound challenge is unaffected
by any proxy.
What the challenge listener serves
Section titled “What the challenge listener serves”For security reviewers: during registration the control plane delivers a
single-use nonce (5-minute TTL) over the sidecar’s outbound TLS connection.
The sidecar then serves that nonce, signed with its host credential, at
GET /.well-known/ringzero-challenge on port 7301; the control plane dials
each attested address, fetches, and verifies. Properties that matter:
- The listener is read-only — it reflects no request input and executes nothing.
- The nonce is single-use and expires in 5 minutes.
- Outside an active registration window, the listener answers every
request with an empty
204— an internet scanner probing 7301 learns only that a RingZero sidecar exists. - The listener is deliberately plain HTTP: the payload is a signed, single-use nonce, so channel encryption would add certificate-provisioning complexity for no confidentiality gain.
Kubernetes: direct mode
Section titled “Kubernetes: direct mode”In direct mode the challenge must land on the specific sidecar that’s
registering — which is exactly what a Kubernetes Service can’t guarantee: a
Service in front of a multi-replica Deployment round-robins port 7301 to any
pod, so challenges reach the wrong sidecar nondeterministically
(challenge_wrong_responder). Pick a pattern by how your workload runs:
Per-node (recommended for replicated workloads in direct mode). Run the
sidecar as a DaemonSet with hostPort: 7301 and register each node.
Every node gets its own hst_…, the challenge dials the node’s address
directly, and replica counts stop mattering:
apiVersion: apps/v1kind: DaemonSetmetadata: name: ringzero-sidecarspec: selector: matchLabels: { app: ringzero-sidecar } template: metadata: labels: { app: ringzero-sidecar } spec: containers: - name: ringzero-sidecar image: ghcr.io/ringzero/sidecar:1 ports: - containerPort: 7301 hostPort: 7301 volumeMounts: - name: ringzero-config mountPath: /etc/ringzero readOnly: true volumes: - name: ringzero-config secret: secretName: ringzero-sidecarPer-pod. Keep the sidecar in the workload’s pod (the manifests in the
deployment guides) only when each pod has
its own reachable address — hostNetwork: true, a per-pod LoadBalancer, or
a routable pod network. Set challenge.advertise_address if the reachable
address isn’t one of the pod’s own interfaces.
Registration unit rule of thumb: whatever answers the challenge at an
attested address is what gets registered — a Compose machine, a node
(DaemonSet), or a pod. Each gets its own hst_… record.
Observability
Section titled “Observability”GET :7302/healthz — liveness and registration state:
{ "status": "ok", "host": "hst_2d8e", "registered": true, "lastHeartbeat": "2026-07-22T09:14:32Z" }status is ok, or degraded when one or more consecutive heartbeats have
failed (still HTTP 200 — the sidecar is alive and retrying). HTTP 500 only
when unregistered or revoked. Wire it as your container health probe via
ringzero-sidecar healthcheck
(the image is distroless — there’s no curl inside).
GET :7302/metrics — Prometheus text format (on by default; the port is
local-only). Metrics:
| Metric | Type |
|---|---|
ringzero_sidecar_registered | gauge (0/1) |
ringzero_sidecar_heartbeats_total | counter |
ringzero_sidecar_heartbeat_failures_total | counter |
ringzero_sidecar_last_heartbeat_timestamp_seconds | gauge |
ringzero_sidecar_build_info{version="1.6.3"} | gauge |
Alert on time() - ringzero_sidecar_last_heartbeat_timestamp_seconds > 120 —
that’s one beat away from leaving scan scope.
Logs are structured (log.format: json by default); every lifecycle event
seen in the manual setup verification
is a single line with a stable msg. Raise RINGZERO_LOG_LEVEL=debug while
diagnosing; drop back to info afterwards.
Secret-handling checklist
Section titled “Secret-handling checklist”- Mount the token as a file (
registration_token_file/ Docker secret / Kubernetes Secret volume) rather than an inline env value where possible — env vars leak intodocker inspect, crash dumps, and child processes. - Scope CI access so only the deploy pipeline can read the token.
- Rotate tokens on a schedule and when people with access leave.