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 app | Server software | |
|---|---|---|
| What consumes a seat | A person's machine | A deployment |
| Who installs it | The end user | An operator, often not the buyer |
| Network access | Usually available | Sometimes deliberately absent |
| An outage means | One person is annoyed | Production is down for a company |
| Renewal | The user notices a prompt | Nobody 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:
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:
- Log loudly, at startup and periodically, with the expiry date and who to contact.
- Surface it in the product — an admin banner, a health-check field, a metric your customer's monitoring already scrapes.
- Block new work, not existing work. Refuse to provision new tenants, create new projects, or accept new integrations, while letting everything already running continue.
- 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:
- Where the license file goes — a path, a volume mount, or an environment variable, with one worked example.
- What happens when it expires — state your degradation policy plainly. Operators will ask; answering in advance builds more trust than any feature.
- How to get a new one — a contact, and whether existing installations keep running during the renewal.
Next steps
- Issuing Licenses — creating the licenses you hand out
- License Verification — file and key verification in detail
- Self-hosted or hosted? — where to run the licensing service itself