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) | expiredFallbackRPS → defaultUnlicensedRPS → 20 |
Effective RPS is determined by:
- If
maxRps > 0in the token → use that value (overrides tier default) - 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.nexusto 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:8880API:
GET /api/v1/licenseResponse includes "installId" in the JSON body.
gslbd Configuration
License credentials are loaded in priority order:
- Environment variables (highest priority)
settingstable in the SQLite database (applied via API/WebUI/TUI)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:
- 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. - 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 | expiredFallbackRPS → defaultUnlicensedRPS → 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):
- Start the primary node first — it generates the install ID
- Start remaining nodes before they process any snapshots, or wipe their databases so they receive the primary node's snapshot before
EnsureInstallIDruns
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
iidfield (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:8880If 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
settingstable of the local SQLite database. Access to the database file grants read access to the token — protect database file permissions accordingly.