KubeMQ
Licensing

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:latest

Keep the key out of the file by reading it from the shell environment:

docker-compose.yml
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:
Terminal
export KUBEMQ_LICENSE_KEY=YOUR_LICENSE_KEY
docker compose up -d

Create a Secret and point the chart at it. The key never appears in the Helm release values or on the KubemqCluster object:

Terminal
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=5Gi

This 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:

kubemq-license.yaml
apiVersion: v1
kind: Secret
metadata:
  name: kubemq-license
  namespace: kubemq
stringData:
  key: YOUR_LICENSE_KEY
statefulset-excerpt.yaml
spec:
  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: 20Gi

The 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 a Retry-After header up to a cap of 30 minutes, for KUBEMQ_LICENSE_FIRST_BOOT_RETRY_MINUTES (default 30 minutes), then refuses to start with #first-boot-unreachable, which points at the pre-activated file path. A 429 rate_limited here 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 log first boot: activate answered HTTP 429 rate_limited; retrying in …s #first-boot-unreachable and 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-unreachable anchor 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:

FilePurpose
<store>/license/lease.jwsThe last good lease, replaced atomically on every successful refresh
<store>/license/instance.idThe installation fingerprint outside Kubernetes (a random id generated on first boot)
<store>/license/clock.hwmThe clock high-water mark used to detect a clock rolled backwards
<store>/license/revocation.jsonCounted 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.

Terminal
curl -s http://localhost:8080/api/v1/license | jq
Response (abridged)
{
  "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:

FieldMeaning
lease_expires_atWhen the current lease ends. Healthy online servers show a value about 7 days ahead that moves forward every hour
license_expires_atThe commercial expiry — the end of your contract or trial
run_untilLease expiry + grace: the moment the server drains if it never reaches the service again
last_refresh.outcomeok, 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
fingerprintThis installation's identity — what you send when requesting an offline file
token_versionThe entitlement version of the current token; it increases whenever KubeMQ changes the license (plan, expiry, cap, reinstatement)
kidThe id of the signing key that signed the current token (license-signing-v1 or license-signing-v2)
revocation.seen_countNon-zero means a signed revocation has been seen; at 3 the server drains
over_cap, silentThe license has more installations than allowed, or has not reported usage for 72 hours
warningsThe 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:

Terminal
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.

VariableDefaultMeaning
KUBEMQ_LICENSE_KEYunsetThe 43-character license key
KUBEMQ_LICENSE_ENDPOINThttps://license.kubemq.ioBase URL of the licensing service
KUBEMQ_LICENSE_DRAIN_SECONDS300Drain window before a licensing exit (0–3600)
KUBEMQ_LICENSE_FIRST_BOOT_RETRY_MINUTES30How long a first boot retries activation when the service is unreachable (1–1440)
KUBEMQ_LICENSE_CACHED_LEASEunsetA 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?

On this page