Use a License Key
Pass KUBEMQ_LICENSE_KEY to Docker, Compose, Helm and manifests; activation, the lease cache and volume requirement, key regeneration, and GET /api/v1/license.
A license key licenses an online server: the server exchanges the key for a lease on first boot and refreshes it hourly. This page covers everything about running with a key. If your server cannot reach the internet at all, see Air-gapped installations instead.
Pass the key to the server
The key is read from one environment variable, KUBEMQ_LICENSE_KEY, in every
deployment mode. Replace YOUR_LICENSE_KEY with your key.
Pass the key with -e and mount a volume for the store — the lease cache lives
there (see The lease cache below):
docker run -d \ --name kubemq \ -p 50000:50000 \ -p 9090:9090 \ -p 8080:8080 \ -p 9092:9092 \ -p 5672:5672 \ -e KUBEMQ_LICENSE_KEY=YOUR_LICENSE_KEY \ --hostname kubemq \ -v "$(pwd)/kubemq-store:/kubemq/store" \ europe-docker.pkg.dev/kubemq/images/kubemq-next:latestKeep the key out of the file by reading it from the shell environment:
services:
kubemq:
image: europe-docker.pkg.dev/kubemq/images/kubemq-next:latest
container_name: kubemq
hostname: kubemq
ports:
- "50000:50000"
- "9090:9090"
- "8080:8080"
environment:
- KUBEMQ_LICENSE_KEY=${KUBEMQ_LICENSE_KEY:?set KUBEMQ_LICENSE_KEY in your shell}
volumes:
- kubemq-data:/kubemq/store
restart: unless-stopped
volumes:
kubemq-data:export KUBEMQ_LICENSE_KEY=YOUR_LICENSE_KEY
docker compose up -dCreate a Secret and point the chart at it. The key never appears in the Helm release
values or on the KubemqCluster object:
kubectl create namespace kubemq
kubectl create secret generic kubemq-license -n kubemq \
--from-literal=key=YOUR_LICENSE_KEY
helm repo add kubemq-next https://kubemq-io.github.io/charts-next
helm install kubemq-next kubemq-next/kubemq-next -n kubemq \
--set licenseKeySecretRef.name=kubemq-license \
--set licenseKeySecretRef.key=key \
--set replicas=1 --set standalone=true \
--set volume.size=5GiThis installs a single standalone node, which is what a trial license allows
(one installation, one node — max_instances=1); the chart's 3-node default only
starts on a paid license whose cap covers it, and every pod otherwise exits with
#cluster-over-cap. Choose replicas
at install time — it is immutable once the cluster exists. volume.size gives the
node a PersistentVolumeClaim; without one the cached lease is lost on every pod
replacement (#store-not-persistent).
For a quick, non-GitOps install you can pass the key directly with
--set licenseKey=YOUR_LICENSE_KEY. The full chart reference is in
Install with Helm; the operator's behaviour is in
Kubernetes.
Without the operator, inject the key from a Secret into the server container and give the server the RBAC it needs to read its fingerprint:
apiVersion: v1
kind: Secret
metadata:
name: kubemq-license
namespace: kubemq
stringData:
key: YOUR_LICENSE_KEYspec:
template:
spec:
serviceAccountName: kubemq-cluster-next
containers:
- name: kubemq
image: europe-docker.pkg.dev/kubemq/images/kubemq-next:latest
env:
- name: KUBEMQ_LICENSE_KEY
valueFrom:
secretKeyRef:
name: kubemq-license
key: key
volumeMounts:
- name: store
mountPath: /kubemq/store
volumeClaimTemplates:
- metadata:
name: store
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 20GiThe kubemq-cluster-next ServiceAccount and its ClusterRole are described in
Kubernetes without the operator.
The key is only read from the environment. It is not a config.yaml field, not a
command-line flag, and a license block inside the base64 CONFIG variable is rejected
by the loader. The value is redacted from GET /config and from the dashboard's
configuration page.
Activation
On the first boot of an installation the server has no cached lease, so it activates:
it sends the key, its installation fingerprint and a description of where it runs
(standalone, docker or kubernetes, plus the server version and, when
KUBEMQ_IMAGE_DIGEST is injected, the image digest — operator 1.1.1 and later inject
NODE_NAME on every pod and KUBEMQ_IMAGE_DIGEST when the cluster's image reference is
pinned by digest, …@sha256:…; a tag-only image reference leaves the digest empty) to
https://license.kubemq.io. The service answers with a signed lease — a 7-day token
bound to this fingerprint (24 hours for a trial) — which the server caches and verifies
from then on. The key itself is not stored by the server.
If activation fails:
- Bad key (
401, three refusals 30 seconds apart) — the server refuses to start. There is one error for an unknown key, a disabled license, a revoked one and an expired one; the log line ends in#first-boot-bad-key. - Bad request (
400, three times) — the service could not parse the activation request itself; the server refuses to start with#first-boot-bad-request. This is a version mismatch between the server build and the service, not a key problem. - Untrusted signing key (
422 untrusted_kid) — the service signs with a key this server build does not trust; the server refuses to start immediately with#untrusted-kid. Upgrade the server. - Service unreachable (DNS, timeout, 5xx) or any other answer (
429, an unexpected status) — the server retries with backoff (1, 2, 5, 15, then 30 minutes, ±10%), honouring aRetry-Afterheader up to a cap of 30 minutes, forKUBEMQ_LICENSE_FIRST_BOOT_RETRY_MINUTES(default 30 minutes), then refuses to start with#first-boot-unreachable, which points at the pre-activated file path. A429 rate_limitedhere is expected on a multi-node first boot: the pods of one installation activate in parallel and share a per-license activate rate limit, so some of them logfirst boot: activate answered HTTP 429 rate_limited; retrying in …s #first-boot-unreachableand wait 1–2 minutes before succeeding. That is not a fault and needs no action — the server is not unreachable, only rate-limited, and the same#first-boot-unreachableanchor covers both cases.
After activation, the server refreshes hourly and activates again only when the cached lease can no longer be used: it is past its expiry plus grace, or it is bound to another installation's fingerprint (a store volume copied from another host). In both cases the server logs a warning and activates with the key instead of refusing.
The lease cache
The server keeps four small files under its store directory:
| File | Purpose |
|---|---|
<store>/license/lease.jws | The last good lease, replaced atomically on every successful refresh |
<store>/license/instance.id | The installation fingerprint outside Kubernetes (a random id generated on first boot) |
<store>/license/clock.hwm | The clock high-water mark used to detect a clock rolled backwards |
<store>/license/revocation.json | Counted revocation confirmations for the current license, so a restart cannot reset the count. Three on record → the server refuses to boot; the record is cleared only by a lease carrying a newer token_version (a reinstated license), which you obtain by deleting both lease.jws and revocation.json and restarting |
The default store path inside the container is /kubemq/store.
The store must be on a persistent volume
The cache is what lets the server survive an outage of the licensing service — the fail-open window described in the overview exists only if the cached lease exists. Without a persistent store:
- every container restart is a first boot, which needs the service to be reachable at that moment or the server does not start;
- outside Kubernetes every restart generates a new fingerprint, so each restart counts as a new installation against your cap.
The server logs a warning at boot (#store-not-persistent) when the store path is not a
mount. Anything but a throwaway run should mount a volume at /kubemq/store.
Regenerate a key
A key can be regenerated — after a leak, a staff change, or simply because it was lost.
Regeneration issues a new key and invalidates the old one for new activations.
Running servers are unaffected: they hold a lease and refresh with it, never with the
key. Update the value wherever you store it (Secret, .env, CI variable) so that the
next new installation activates with the new key.
Trial keys are regenerated through the resend option on kubemq.io/trial; paid keys through your KubeMQ contact.
Check the license: GET /api/v1/license
The management API reports the full licensing state. It is authenticated like every other management route.
curl -s http://localhost:8080/api/v1/license | jq{
"license_id": "6f1c2a3e-9b4d-4c5e-8a7f-1d2e3f4a5b6c",
"issued_to": "Example Corp",
"plan": "pro",
"mode": "online",
"source": "key",
"license_expires_at": "2027-09-19T00:00:00Z",
"lease_expires_at": "2026-09-26T12:15:00Z",
"grace_days": 7,
"run_until": "2026-10-03T12:15:00Z",
"max_instances": 3,
"fingerprint": "3f2a9c1e-7b6d-4e5f-9a8b-0c1d2e3f4a5b",
"fingerprint_source": "persisted",
"token_version": 3,
"kid": "license-signing-v2",
"last_refresh": { "at": "2026-09-19T12:15:00Z", "outcome": "ok", "status": 200, "next_at": "2026-09-19T13:12:00Z" },
"clock": { "skew_seconds": 1, "skew_warning": false, "high_water_mark": "2026-09-19T12:15:00Z" },
"revocation": { "seen_count": 0, "first_seen": null, "last_seen": null, "reason": null, "draining": false },
"over_cap": false,
"silent": false,
"usage": { "enabled": true, "last_report_at": "2026-09-19T12:10:00Z", "last_outcome": "accepted", "last_status": 200, "cursor_to": "2026-09-19T12:10:00Z", "boot_id": "0b3d5f7a-9c1e-4a2b-8d6f-1e3c5a7b9d0f" },
"warnings": []
}The fields to watch:
| Field | Meaning |
|---|---|
lease_expires_at | When the current lease ends. Healthy online servers show a value about 7 days ahead that moves forward every hour |
license_expires_at | The commercial expiry — the end of your contract or trial |
run_until | Lease expiry + grace: the moment the server drains if it never reaches the service again |
last_refresh.outcome | ok, or why the last refresh failed (unreachable, unsigned_4xx, unsigned_5xx, malformed_200, rate_limited, revocation); never on an offline server, or online before the first activation or refresh has completed |
fingerprint | This installation's identity — what you send when requesting an offline file |
token_version | The entitlement version of the current token; it increases whenever KubeMQ changes the license (plan, expiry, cap, reinstatement) |
kid | The id of the signing key that signed the current token (license-signing-v1 or license-signing-v2) |
revocation.seen_count | Non-zero means a signed revocation has been seen; at 3 the server drains |
over_cap, silent | The license has more installations than allowed, or has not reported usage for 72 hours |
warnings | The exact warning lines currently logged every minute |
?include=lease
GET /api/v1/license?include=lease adds a lease field carrying the bare lease token —
the server's bearer credential. It is guarded accordingly: the call is refused with 403
when management-API authentication is disabled, it requires the admin role, and every
call (granted or denied) is written to the audit log as license.lease_read. It exists
for the operator's lease cache write-back
(Kubernetes); kmq license never
requests it.
kmq license
The CLI renders the same object:
kmq license
kmq license -o yaml
kmq license --fields plan,mode,lease_expires_at,run_until-o selects the output format (json, ndjson, yaml, table); --fields projects
the output to the named fields. The command needs a service key with at least the
read_only role — see kmq CLI.
Change the licensing endpoint
KUBEMQ_LICENSE_ENDPOINT (default https://license.kubemq.io) is the base URL the
server activates, refreshes and reports usage against. Change it only when told to by
KubeMQ — for example to point at a forwarder inside your network that reaches the same
host. The value is an http:// or https:// base URL (plain http:// is accepted for
a development endpoint); a trailing slash is trimmed and the /v1/... paths are
appended. Anything else is a configuration error and the server exits with code 1.
Pre-activated file for egress-restricted networks
Some networks can reach the internet in general but cannot open egress to
license.kubemq.io — a strict allow-list, a proxy that strips request bodies, a
regulated environment. The answer is not to open the firewall: ask KubeMQ for a
pre-activated file.
A pre-activated file is an offline license file bound to your installation's
fingerprint list, valid for up to one year. The server then runs exactly like an
air-gapped installation — no activation, no refresh, no usage reports — and is renewed
by reissuing the file before it expires. To request one, read each installation's
fingerprint from GET /api/v1/license and follow
Air-gapped installations.
Related settings
| Variable | Default | Meaning |
|---|---|---|
KUBEMQ_LICENSE_KEY | unset | The 43-character license key |
KUBEMQ_LICENSE_ENDPOINT | https://license.kubemq.io | Base URL of the licensing service |
KUBEMQ_LICENSE_DRAIN_SECONDS | 300 | Drain window before a licensing exit (0–3600) |
KUBEMQ_LICENSE_FIRST_BOOT_RETRY_MINUTES | 30 | How long a first boot retries activation when the service is unreachable (1–1440) |
KUBEMQ_LICENSE_CACHED_LEASE | unset | A lease used only when no cache file exists — written by the operator, not by you |
A drain or retry value outside its range (or one that is not an integer) is a configuration error: the server exits with code 1, not 3, and the log line names the variable.
The complete table, including the file variables, is in Core & Licensing.
Was this page helpful?
30-Day Trial
Start a free 30-day KubeMQ trial at kubemq.io/trial — what it allows, the 3-day check-in rule, recovering a lost key, conversion, and troubleshooting.
Air-Gapped Installations
License a KubeMQ server with no network access — request a signed file for your fingerprints, pass it with KUBEMQ_LICENSE_FILE or _DATA, and renew it.