Proxy Registration

End-to-end guide for connecting zentinel proxy instances to the control plane.

Prerequisites

  • Control plane running and accessible (see Deployment)
  • An organization and project created (see Getting Started)
  • The zentinel proxy binary installed on target machines
  • curl or equivalent HTTP client for API calls

1. Register the Proxy

Register a node with the control plane via the REST API. No authentication is required for registration — only the project slug.

curl -X POST http://localhost:4000/api/v1/projects/my-project/nodes/register \
  -H "Content-Type: application/json" \
  -d '{
    "name": "proxy-us-east-1",
    "labels": {"env": "production", "region": "us-east-1"},
    "version": "1.0.0",
    "ip": "10.0.1.50",
    "hostname": "proxy-us-east-1.internal"
  }'

Request Fields

FieldRequiredDescription
nameYesUnique name within the project (1–100 chars, alphanumeric + _ . -)
labelsNoKey-value metadata for targeting rollouts
versionNoProxy version string
ipNoNode IP address (auto-detected from request if omitted)
hostnameNoNode hostname
capabilitiesNoFeature capabilities array
metadataNoAdditional JSON metadata

Response (201 Created)

{
  "node_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "node_key": "liPd5OsMRw8cr-yKFRwAr6O3zmqOdFUcfuY8W-XTiJE",
  "poll_interval_s": 30
}

Important: The node_key is returned only once. Store it securely — it cannot be retrieved again.

2. Configure the Proxy

Configure the zentinel proxy to communicate with the control plane using the credentials from registration:

SettingValue
control_plane_urlURL of the control plane (e.g., http://localhost:4000)
node_idThe node_id from the registration response
node_keyThe node_key from the registration response

The proxy uses these to authenticate heartbeats, bundle polling, and event reporting.

3. Verify Connection

Once the proxy is configured and started, it begins sending heartbeats. Verify the connection:

Via the dashboard: Navigate to your project in the web UI — the node should appear with status online.

Via the API:

curl http://localhost:4000/api/v1/projects/my-project/nodes \
  -H "Authorization: Bearer $API_KEY"

Look for your node with "status": "online" and a recent last_seen_at timestamp.

Heartbeat Details

The proxy sends heartbeats to POST /api/v1/nodes/:node_id/heartbeat at the interval specified by poll_interval_s (default: 30 seconds). Each heartbeat can include:

{
  "health": {"cpu_percent": 12.5, "memory_percent": 45.0},
  "metrics": {"requests_per_second": 1500},
  "active_bundle_id": "bundle-uuid-if-running",
  "version": "1.0.0"
}

Nodes that stop sending heartbeats for 120 seconds are marked offline by the staleness worker.

The static node_key works for authentication, but JWTs are recommended for production. JWTs are short-lived (12-hour TTL) and signed with Ed25519 keys.

Prerequisites

Your organization needs an active signing key. Create one in the web UI under Organization > Signing Keys.

Exchange Key for Token

curl -X POST http://localhost:4000/api/v1/nodes/$NODE_ID/token \
  -H "X-Zentinel-Node-Key: $NODE_KEY"

200 OK:

{
  "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6InNrXy4uLiJ9...",
  "token_type": "Bearer",
  "expires_at": "2026-02-24T06:30:00Z",
  "expires_in": 43200
}

Use the JWT

Use the token in the Authorization header instead of X-Zentinel-Node-Key:

curl -X POST http://localhost:4000/api/v1/nodes/$NODE_ID/heartbeat \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"health": {"cpu_percent": 10}}'

The proxy should automatically refresh the token before expiry.

Authentication Methods

MethodHeaderUse Case
Static keyX-Zentinel-Node-Key: sk_...Initial setup, simple deployments
JWT BearerAuthorization: Bearer eyJ...Production (recommended)

5. Deploy a Bundle

With a node registered and connected, deploy a configuration bundle:

  1. Compile a bundle — Create via the web UI (Bundles > New Bundle) or the API. The control plane validates KDL config and packages it as .tar.zst.

  2. Create a rollout — Target your node(s), choose a strategy (rolling, canary, blue-green, all-at-once), and configure health gates.

  3. Node pulls the bundle — The proxy polls GET /api/v1/nodes/:node_id/bundles/latest. When a rollout assigns a new bundle, the response includes a presigned download URL:

{
  "bundle_id": "bundle-uuid",
  "version": "v1.2.3",
  "checksum": "sha256:abc123...",
  "size_bytes": 51200,
  "download_url": "https://s3.../bundle.tar.zst?X-Amz-...",
  "poll_after_s": 30
}

When no update is pending:

{
  "no_update": true,
  "poll_after_s": 30
}
  1. Node reports status — After applying the bundle, the proxy reports active_bundle_id in its next heartbeat. The rollout engine uses this to track progress and evaluate health gates.

6. Reporting Events and Metrics

Registered proxies can send operational data back to the control plane.

Events

Valid event types: config_reload, bundle_switch, error, startup, shutdown, warning, info.

curl -X POST http://localhost:4000/api/v1/nodes/$NODE_ID/events \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "event_type": "startup",
    "severity": "info",
    "message": "Proxy started successfully"
  }'

Batch events (wrap in events array):

curl -X POST http://localhost:4000/api/v1/nodes/$NODE_ID/events \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [
      {"event_type": "bundle_switch", "severity": "info", "message": "Switched to bundle v1.2.3"},
      {"event_type": "info", "severity": "info", "message": "All upstreams healthy"}
    ]
  }'

Metrics

Service-level metrics require service_id and project_id:

curl -X POST http://localhost:4000/api/v1/nodes/$NODE_ID/metrics \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "metrics": [{
      "service_id": "SERVICE_UUID",
      "project_id": "PROJECT_UUID",
      "period_start": "2026-02-23T10:00:00Z",
      "period_seconds": 60,
      "request_count": 1500,
      "error_count": 3,
      "status_2xx": 1450,
      "status_4xx": 47,
      "status_5xx": 3
    }]
  }'

WAF Events

WAF events require rule_type, rule_id, action, and severity:

curl -X POST http://localhost:4000/api/v1/nodes/$NODE_ID/waf-events \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [{
      "rule_type": "crs",
      "rule_id": "942100",
      "action": "blocked",
      "severity": "high",
      "client_ip": "1.2.3.4",
      "method": "POST",
      "path": "/login",
      "timestamp": "2026-02-23T10:00:00Z",
      "matched_data": "1 OR 1=1",
      "metadata": {"category": "sql_injection"}
    }]
  }'

Runtime Config

curl -X POST http://localhost:4000/api/v1/nodes/$NODE_ID/config \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"config_kdl": "route \"/api/*\" { upstream \"http://api:8080\" }"}'

7. Bulk Registration

Register multiple nodes with a shell script:

#!/bin/bash
set -euo pipefail

CP_URL="http://localhost:4000"
PROJECT="my-project"

NODES=(
  "proxy-us-east-1:us-east-1:10.0.1.50"
  "proxy-us-west-2:us-west-2:10.0.2.50"
  "proxy-eu-west-1:eu-west-1:10.1.1.50"
  "proxy-ap-south-1:ap-south-1:10.2.1.50"
)

for entry in "${NODES[@]}"; do
  IFS=":" read -r name region ip <<< "$entry"

  echo "Registering $name..."
  response=$(curl -s -X POST "$CP_URL/api/v1/projects/$PROJECT/nodes/register" \
    -H "Content-Type: application/json" \
    -d "{
      \"name\": \"$name\",
      \"labels\": {\"env\": \"production\", \"region\": \"$region\"},
      \"ip\": \"$ip\"
    }")

  node_id=$(echo "$response" | jq -r '.node_id')
  node_key=$(echo "$response" | jq -r '.node_key')

  echo "  node_id: $node_id"
  echo "  node_key: $node_key"

  echo "$name,$node_id,$node_key" >> node-credentials.csv
done

echo "Done. Credentials saved to node-credentials.csv"

Protect node-credentials.csv — it contains authentication keys that cannot be retrieved again.

8. Troubleshooting

Node Shows as “Offline”

  • Check heartbeats: Nodes are marked offline after 120 seconds without a heartbeat. Verify the proxy is running and can reach the control plane.
  • Check network: Ensure the proxy can reach POST /api/v1/nodes/:node_id/heartbeat on the control plane host and port.
  • Check credentials: Verify the node_id and node_key match the registration response.

Authentication Failures (401)

Error MessageCauseFix
Missing authentication...No auth header sentAdd X-Zentinel-Node-Key or Authorization: Bearer header
Invalid node keyWrong or corrupted static keyRe-register the node to get a new key
Invalid token signatureJWT signed with wrong keyRe-issue token via POST /nodes/:id/token
Token expiredJWT past 12-hour TTLRequest a new token
Signing key has been deactivatedOrg key was deactivatedCreate a new signing key and re-issue tokens
Node not foundNode was deletedRe-register the node

Bundle Not Pulling

  • No rollout active: Bundles are only assigned to nodes through rollouts. Create a rollout targeting the node.
  • Rollout paused/failed: Check rollout status in the dashboard. Health gate failures pause rollouts automatically.
  • Wrong labels: If the rollout uses label-based targeting, verify the node’s labels match the target selector.
  • Poll interval: The node may not have polled yet. Default interval is 30 seconds.

Registration Fails (422)

  • Duplicate name: Node names must be unique within a project. Choose a different name or delete the existing node.
  • Invalid name format: Names must be 1–100 characters, start with alphanumeric, and contain only alphanumeric characters, underscores, dots, and hyphens.
  • Project not found (404): Verify the project slug in the URL matches an existing project.

Node API Quick Reference

EndpointMethodAuthPurpose
/api/v1/projects/:slug/nodes/registerPOSTNoneRegister a new node
/api/v1/nodes/:id/heartbeatPOSTNodeSend heartbeat
/api/v1/nodes/:id/bundles/latestGETNodePoll for bundle updates
/api/v1/nodes/:id/tokenPOSTNodeExchange key for JWT
/api/v1/nodes/:id/eventsPOSTNodeReport events
/api/v1/nodes/:id/configPOSTNodeReport runtime config
/api/v1/nodes/:id/metricsPOSTNodeUpload metrics
/api/v1/nodes/:id/waf-eventsPOSTNodeReport WAF events

“Node” auth accepts either X-Zentinel-Node-Key or Authorization: Bearer <jwt>.

See the API Reference for full endpoint documentation.