Licensing

Nexus GSLB enforces per-tier requests-per-second (RPS) limits and feature gates via cryptographically signed license tokens. The DNS server consults the licensing manager on every query — if the token bucket is exhausted the query is rejected with SERVFAIL.


License Token Format

A license token is a two-part, dot-separated structure:

base64url(JSON_payload).base64url(Ed25519_signature)

The signature covers the raw JSON bytes. Both parts use base64.RawURLEncoding (no padding).

JSON payload fields:

Field Type Description
tier string free, paid, pro, enterprise, or custom
maxRps int Per-license RPS override (0 = use tier default)
iat int64 Unix timestamp — issue time
exp int64 Unix timestamp — expiry (0 = never expires)
id string 32-char hex UUID — unique per license
cid string Customer ID bound to this token
iid string (optional) Installation ID — node-locks the token

License Tiers

Tier Default RPS Notes
unlicensed 20 (configurable) No valid token present
free 20 (configurable) Signed free token required
trial 500 Auto-issued 30-day evaluation; enterprise-level feature access
paid 500 Entry-level commercial tier
pro 2,000 Mid-market tier
enterprise 1,000,000 (effectively unlimited) Full-featured top tier
custom 1,000,000 Custom contract terms
expired 20 (configurable) expiredFallbackRPSdefaultUnlicensedRPS → 20

Effective RPS is determined by:

  1. If maxRps > 0 in the token → use that value (overrides tier default)
  2. If maxRps == 0 → use tier default above

Per-tier feature and limit matrix:

Feature / Limit Free / Unlicensed Paid Pro Enterprise / Custom
Pool members per pool 3 10 50 Unlimited
Tenants 1 1 1 Unlimited
Cluster nodes 1 1 3 Unlimited
Remote probe agents 0 0 10 Unlimited
DNSSEC online signing
Filter chains
HTTP redirects
ACME certificate management
Alerting — email/webhook
Service discovery — Consul
Dual-run zone push (zonesync)
SLA uptime % / incident history
BGP route health injection
NATS clustering
Geo rules
DNS response rate limiting
Alerting — PagerDuty/Opsgenie
Service discovery — etcd
RUM (real-user measurement) routing
Progressive delivery rollouts
Remote probe agents (external vantage)
Signal-weight routing (carbon/cost)
DNS query anomaly detection (insights)
RPZ (DNS firewall)
Traffic shadowing (dark-testing)
Traffic globe (query-origin map)
Deployment gates (soak + auto-rollback)
Scriptable filter-chain steps
OIDC / SSO
Custom RBAC roles
Multi-tenancy
Audit logging

Trial licenses receive enterprise-level feature access for the duration of the trial period.

Trial Online Activation

On first startup with no license, gslbd auto-issues a trial. When license.serverURL is set (the default, https://www.gslb.nexus), it first registers the machine fingerprint with POST {serverURL}/api/v1/trial/register (500 ms timeout) before issuing the local trial token:

  • First registration (HTTP 201) — a full 30-day trial is issued.
  • Prior trial detected (HTTP 409) — a 7-day grace trial is issued, with a warning to contact licensing@gslb.nexus to purchase.
  • Server unreachable / timeout — activation fails open: a full 30-day trial is issued offline (logged at INFO).

Activation reuses license.serverURL, so setting it to "" (air-gap deployments) skips online activation entirely and always issues a full 30-day trial. The fingerprint is sha256(machine-id + install-id) truncated to 16 bytes — the same value used for node-locking — so it carries no PII.


Node Identity and Installation ID

Every gslbd installation generates a random install ID (install_id) on first startup and stores it in the rqlite datastore. In a cluster, the install ID is shared automatically because the datastore is Raft-replicated, so nodes which have not yet generated their own ID adopt the cluster's value.

The install ID is used to node-lock licenses. If a license token contains an iid field, the daemon rejects it unless iid matches the local install ID.

Floating vs. Node-Locked Licenses

Type iid field Accepted by Use case
Floating Absent Any installation Multi-node clusters, simple deployments
Node-locked Present One specific installation Single-node, strong anti-reuse

Floating licenses are the recommended choice for clusters. Node-locked licenses are suited to single-node deployments where you want to prevent a token being reused elsewhere.

Viewing the Install ID

WebUI: Admin → License → Installation ID section

CLI:

gslbctl license status --server https://your-node:8880

API:

GET /api/v1/license

Response includes "installId" in the JSON body.


gslbd Configuration

License credentials are loaded in priority order:

  1. Environment variables (highest priority)
  2. settings table in the SQLite database (applied via API/WebUI/TUI)
  3. config.yaml

Environment variables:

Variable Description
GSLB_LICENSE_PUBLIC_KEY Ed25519 public key (base64url, 32 bytes)
GSLB_LICENSE_KEY License token
GSLB_LICENSE_SERVER_URL Override the revocation-check server URL (see below)

config.yaml:

license:
  publicKey: ""            # Ed25519 public key (base64url)
  licenseKey: ""           # License token
  defaultFreeRPS: 50       # RPS for free-tier licenses (default: 50)
  defaultUnlicensedRPS: 5  # RPS when no valid license is present (default: 5)
  expiredFallbackRPS: 20   # RPS when license has expired (default: 20)
  serverURL: "https://www.gslb.nexus"  # revocation-check endpoint; "" disables polling entirely
  refreshInterval: 1h      # how often to poll serverURL (default: 1h)

The publicKey must match the public key of the operator keypair used to sign tokens. It is safe to distribute; only the licensectl operator database contains the private key.


Applying a License

Licenses can be applied without restarting the daemon on all admin surfaces.

WebUI

Admin → License → paste token into the Apply License Token field → Apply Token.

The token is validated, hot-loaded into the running daemon, and persisted to the database for restart survivability.

gslbctl CLI

gslbctl license status --server https://your-node:8880

gslbctl license apply --server https://your-node:8880 --token <token>
# or positionally:
gslbctl license apply --server https://your-node:8880 <token>

REST API

# View current license status
GET /api/v1/license

# Apply a new token
POST /api/v1/license
Content-Type: application/json
{"token": "eyJ0aWVy..."}

Both endpoints require license:read / license:write permission. The system API key (GSLB_API_KEY) always has access.

Environment Variable (Traditional)

Set in /etc/gslb/env and restart the service:

GSLB_LICENSE_PUBLIC_KEY=<base64url-ed25519-public-key>
GSLB_LICENSE_KEY=<token>

When GSLB_LICENSE_KEY is set, it takes precedence over any token stored via the API.


Revocation Checking

gslbd polls GET {serverURL}/api/v1/verify/{token} on refreshInterval (default 1h) to catch licenses revoked after the daemon already loaded them — without this, a revoked license stays valid until the next restart. On {"valid": false, "error": "revoked"} the daemon immediately drops to the unlicensed RPS limit (defaultUnlicensedRPS) without restarting. The check fails open: any network error, timeout, or non-200 response just logs a warning and keeps the current license state — a license server outage never takes down a paying customer's DNS.

This is intentionally configurable (serverURL, including setting it to "" to disable polling entirely) rather than hardcoded, for two reasons:

  1. Air-gap support is a shipped, marketed feature (see docs/ops/AirGap.md) — a customer running fully offline cannot have a mandatory phone-home baked in, full stop.
  2. It wouldn't meaningfully raise the bar anyway. Because the check fails open, a customer who actually wants to dodge revocation doesn't need to touch serverURL — blocking the default host at the firewall has the identical effect. Hardcoding the URL would only inconvenience legitimate air-gapped/restricted-network customers, not stop anyone determined to bypass it.

If stronger enforcement is ever wanted, the lever is the fail-open behavior itself (e.g. fail closed after N consecutive missed checks), not the URL's configurability — and that's a real product tradeoff (a license-server outage would then degrade paying customers), not a one-line change.


Token Bucket Rate Limiter

The licensing manager implements a token bucket:

  • Bucket capacity = effective RPS
  • Refill rate = effective RPS tokens per second
  • Each DNS query consumes one token
  • Empty bucket → SERVFAIL
  • Bucket starts full at daemon startup and after Reload

License Lifecycle Behaviour

State Effective RPS Notes
No license defaultUnlicensedRPS (default 5)
Valid floating license Tier or maxRps
Valid node-locked license (iid matches) Tier or maxRps
Node-locked license (iid mismatch) defaultUnlicensedRPS Wrong installation
Expired license expiredFallbackRPSdefaultUnlicensedRPS → 20
Invalid signature defaultUnlicensedRPS Tampered or wrong public key
Missing id field Rejected Prevents predictable tokens

Generating Licenses

Licenses are issued via the Nexus website. Log in, create a license for the relevant customer and tier, and copy the generated token.

The website signs tokens using an Ed25519 keypair it manages internally. You will need the corresponding public key to configure your nodes — this is available in the website's operator settings.


Cluster Licensing

New Installations

For a new cluster, start all nodes before any snapshot is exchanged and the install IDs will be generated independently on each node. To ensure the entire cluster shares one install ID (required for a single node-locked license to cover all nodes):

  1. Start the primary node first — it generates the install ID
  2. Start remaining nodes before they process any snapshots, or wipe their databases so they receive the primary node's snapshot before EnsureInstallID runs

In practice, floating licenses are the recommended approach for clusters. They require no install ID coordination and one token covers all nodes.

Existing Cluster: Upgrading to This Version

When upgrading an existing cluster to a version that includes install ID support:

  • Each node generates its own install ID independently on first startup with the new binary
  • No existing functionality is affected — licenses without an iid field (floating) continue to validate on all nodes
  • Cluster sync, DNSSEC, and all other features are unaffected

To check each node's install ID after upgrading:

# From any machine with gslbctl access:
gslbctl license status --server https://lon-01:8880
gslbctl license status --server https://eu-01:8880
gslbctl license status --server https://lab-01:8880

If you later want a single node-locked token covering the whole cluster, the install IDs must all match. The simplest path is to issue one floating license — no install ID binding, works across all nodes with no additional configuration.


API Reference

GET /api/v1/license

Returns the current license state for this node.

Permissions: license:read (all built-in roles)

Response:

{
  "valid": true,
  "tier": "enterprise",
  "maxRps": 50000,
  "expiresAt": 2094742275,
  "licenseId": "c5f8cc4ea66715cb45379dfb4b6656ad",
  "customerId": "star-storm-development",
  "installId": "786a318b5f8ffa3daa36552f4d0b81a7"
}

POST /api/v1/license

Applies a new license token. Validates, hot-reloads, and persists to the database.

Permissions: license:write (tenant_admin role only)

Request:

{"token": "eyJ0aWVy..."}

Response: Same as GET /api/v1/license reflecting the new state.


Security Notes

  • The Ed25519 private key never leaves the license issuer. Distributing the public key to nodes does not enable token forgery.
  • Tokens are cryptographically bound to a specific payload — they cannot be modified without invalidating the signature.
  • Rotating the issuer keypair requires updating GSLB_LICENSE_PUBLIC_KEY (or the API-stored value) on all nodes and re-issuing all active licenses signed with the new key.
  • Tokens applied via the API are stored in the settings table of the local SQLite database. Access to the database file grants read access to the token — protect database file permissions accordingly.

Was this article helpful?
© 2026