Skip to content
Skip to content

Licensing server software you ship

This guide covers licensing a product your customer installs and runs on their infrastructure — an on-prem service, a self-hosted platform, a data pipeline, an appliance. It is the other half of Licensing a JavaScript desktop app, and the differences matter more than the similarities.

License Server itself is licensed this way. The installation guide's TIER_A_LICENSE_FILE is a signed license file, checked by the same mechanism described below — so what follows is not hypothetical.

What is different from a desktop app

Desktop appServer software
What consumes a seatA person's machineA deployment
Who installs itThe end userAn operator, often not the buyer
Network accessUsually availableSometimes deliberately absent
An outage meansOne person is annoyedProduction is down for a company
RenewalThe user notices a promptNobody notices until it breaks

The last two rows drive every recommendation here.

Use a signed license file, not a network check

For software running in someone else's data center, the license should be a file, not a request. Issue a signed license file to the customer, have the product verify it locally at startup, and treat the network as a bonus rather than a requirement:

js
import { CasaziumLicenseClient } from '@casazium/license-sdk';

const client = new CasaziumLicenseClient({ publicKey });
const license = JSON.parse(await fs.readFile(process.env.LICENSE_FILE, 'utf8'));

const valid = await client.verifySignedFile({
  license,
  signature: license.signature,
});

Issue that file from GET /v1/export-license/:key/offline, which RSA-signs the payload with the server's private key. Verification needs only the public key, so an air-gapped installation works by default rather than as a special case — and air-gapped is common precisely among the customers who buy on-prem software.

What a file cannot tell you is whether the license was revoked after it was issued. Signature verification proves the payload is untampered, nothing more. If revocation has to be enforceable, add an optional online check — verifyKey or POST /v1/verify-license-file, which checks live server state against the license's HMAC signature rather than the RSA one above. Optional is the operative word: when that call fails, you have learned nothing about the license, only about the network. Treat it accordingly — see the next section.

Count deployments, not machines

The desktop guide uses a machine fingerprint as instance_id. For server software that is usually wrong — containers are replaced, hosts are rebuilt, and an autoscaling group would burn a seat per instance within a week.

Pick an identifier that matches what you actually sell:

  • Per installation: generate a UUID on first start and persist it with the data, not with the container.
  • Per cluster: let the operator set it in configuration, and document that every node in a cluster shares it.
  • Per environment: acme-prod, acme-staging — readable in your own admin views, which matters when a customer emails about seats.

Whatever you choose, document it, because the customer's ops team will eventually ask why their seat count went up after a redeploy.

Never hard-fail a running system

This is the rule that separates licensing an on-prem product from licensing a desktop app.

If a license expires, or the check cannot complete, do not stop serving traffic. A licensing failure that takes down a customer's production system converts a renewal conversation into an incident review, and you will not win that argument even when you are right.

Degrade in this order:

  1. Log loudly, at startup and periodically, with the expiry date and who to contact.
  2. Surface it in the product — an admin banner, a health-check field, a metric your customer's monitoring already scrapes.
  3. Block new work, not existing work. Refuse to provision new tenants, create new projects, or accept new integrations, while letting everything already running continue.
  4. Only then consider refusing to start on the next restart — which is a decision a human makes during a planned window, not something your software does at 3 a.m.

License Server's own CASAZIUM_UNLICENSED_EVAL behaves this way: it warns loudly on every boot rather than refusing to run.

Make renewals visible before they bite

Nobody logs into a server to check a license. Give them something that does:

  • Expose expiry in a health or status endpoint, so it lands in the customer's own dashboards.
  • Include days-remaining in your startup log line, so it appears in whatever aggregates their logs.
  • Check in with your own License Server when the network allows, and use usage tracking to see which customers are approaching a limit before they hit it.

Shipping updates

If you gate downloads behind a license — Software Distribution — the same key that licenses the product controls who can fetch new builds. For on-prem customers this is often the only signal you get about who is running what version, and it is worth wiring up early.

What to hand the customer

A licensed on-prem deployment usually needs three things in the installation instructions, and leaving any of them implicit generates support email:

  1. Where the license file goes — a path, a volume mount, or an environment variable, with one worked example.
  2. What happens when it expires — state your degradation policy plainly. Operators will ask; answering in advance builds more trust than any feature.
  3. How to get a new one — a contact, and whether existing installations keep running during the renewal.

Next steps