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 an Electron or Tauri 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 fs from 'node:fs/promises';
import { CasaziumLicenseClient } from '@casazium/license-sdk';
const client = new CasaziumLicenseClient({ publicKey });
// { license, signature }, as the export below returns it
const file = JSON.parse(await fs.readFile(process.env.LICENSE_FILE, 'utf8'));
const valid = await client.verifySignedFile(file, MY_PRODUCT_UUID);
valid is true only for a genuine license for your product that
hasn't expired.
Issue that file from
POST /v1/license-export/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. Offline license
verification explains how the
signature works and what it can't prove.
What a file cannot tell you is whether the license was revoked after
it was issued. It proves the license is genuine, yours and unexpired as
of when it was exported, nothing more. If revocation has to be
enforceable, add an optional online check with
verifyKey:
const onlineClient = new CasaziumLicenseClient({ baseUrl: LICENSE_SERVER_URL });
await onlineClient.verifyKey(file.license.key, MY_PRODUCT_UUID);
LICENSE_SERVER_URL is your License Server's API address, including
/v1: https://license-cloud-api.casazium.com/v1 on a hosted account
(not the console's license-cloud.casazium.com), or your own server's
address plus /v1 self-hosted.
It throws when the license is revoked, expired or unknown (an error with
a 4xx err.status), and when it belongs to another product (a
product_uuid mismatch error with no status). That is a real answer:
log it, surface it, and degrade as the next section describes. When the
license is still good, export a fresh file now and then, so a renewal
or a change of terms reaches the installation. Optional is the operative
word: when the call fails with no status (other than that product
mismatch), a 429 or a 5xx, you have
learned nothing about the license, only about the network. Treat it
accordingly.
POST /v1/verify-license-file
is a different check, for a different file: the HMAC-signed export from
POST /v1/license-export, not the RSA-signed { license, signature }
file above, which it can't read.
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
More in Guides, or the full documentation.