---
title: Licensing and Activation
description: RackHelm is licensed software and checks its licence against a remote server. The check is designed to be invisible when everything is healthy — one call a day, cached in between, with a grace window that keeps your portal running…
url: "https://rackhelm.com/documentation/licensing-and-activation/"
updated: "2026-09-24"
category: "Installation & Server Setup"
---

# Licensing and Activation

RackHelm is licensed software and checks its licence against a remote server. The check is designed to be invisible when everything is healthy — one call a day, cached in between, with a grace window that keeps your portal running through a network outage. This article explains what is checked, what is transmitted, and what happens when the check fails.

## Where the key lives

Your licence key sits in `.env`:

```ini
RACKHELM_LICENSE_KEY="your-licence-key"
```

The installer writes it for you from the **License Key** prompt. Installs that predate the RackHelm rename may instead have `ORBI_LICENSE_KEY`, which is still read as a fallback. Set one or the other, not both.

> **Warning:** The key is read from `.env` at runtime, which is why RackHelm never uses `php artisan config:cache`. Running it would freeze whatever key was present at the moment of caching and ignore anything set afterwards.

## How the check runs

RackHelm enforces the licence at two independent points in every web request: once when the application boots and once in the `EnsureLicensed` middleware, which is prepended to the global middleware stack. The check itself is memoised per request, so having both costs nothing when the licence is valid. Console commands — Artisan, the queue worker, the scheduler — do not run the boot-time check.

A single check works like this:

1. Build a **hardware fingerprint** for this server: an MD5 of the server's IP address and its hostname.
2. Read the cached verdict from `storage/app/private/.orbi_license_cache`. The cache is only trusted if its cryptographic signature verifies against a public key baked into the build, it is bound to *this* licence key and *this* fingerprint, its status is `valid`, and it is not past its signed expiry.
3. If that cached verdict is less than **24 hours** old, use it and make no network call at all.
4. Otherwise send a heartbeat to `https://license.orbi-hosting.com/api/v1/licenses/heartbeat` over HTTPS with certificate verification enforced. The connection phase is limited to 4 seconds and the whole call to 7 seconds.
5. Verify the reply's signature, check it echoes the one-time nonce this server generated, and check it is bound to the same licence key and fingerprint. If the status is `valid`, cache it and continue.

Concurrent requests that all miss the cache are serialised behind a lock, so a busy portal makes one heartbeat rather than hundreds.

### Offline behaviour

If the licence server cannot be reached — a network failure or an HTTP 5xx — RackHelm falls back to the cached verdict for up to **3 days** from the moment the licence server issued it, and never past the licence's own expiry date. During a known outage, further network attempts are suppressed for **5 minutes** at a time so that a dead endpoint does not add a 7-second stall to every page load.

Two edge cases are handled strictly rather than generously. A cached verdict whose issue time is more than an hour in the server's future is treated as a rolled-back clock and refused. And a perpetual licence with no signed issue time is never served from offline grace, because nothing would ever bound it — it forces a fresh online check instead.

## What is transmitted

Every heartbeat carries four protocol fields — the licence key, the hardware fingerprint, a random nonce and the request's hostname — plus the platform version and a statistics snapshot.

The snapshot is **counts and version strings only**. It is support and sizing data:

| Group | Contents |
| --- | --- |
| Platform | Product name, version number, release channel, release date. |
| Environment | PHP version and SAPI, Laravel version, OS family, kernel release, architecture, web-server banner, database driver and server version, the configured queue, cache, session, mail and filesystem drivers, and whether the app is in production, has debug on, and is on HTTPS. |
| Runtime | ionCube Loader version, whether OPcache is on, `memory_limit`, `max_execution_time`, and which of ten expected PHP extensions are loaded. |
| Install | Approximate first-boot date, when the build was last deployed, and the migration count, latest migration name and batch number. |
| Usage | Counts of clients, staff, sub-accounts and guest records; products, product groups, addons and coupons; orders, services, active services, invoices, unpaid invoices and SSL certificates; tickets, open tickets and departments; content pages and menus; the number of staff roles; the *names* of enabled integration drivers and payment gateways with a flag for test mode. |

What is deliberately **not** sent: customer names, e-mail addresses, the domains you manage, order contents, ticket text, API credentials and any financial figure. The rule the code holds itself to is that every field is a count or a version string.

Telemetry collection is wrapped so it can never take a licensed platform offline over a metric — if the database is down or mid-migration, each probe degrades to "unknown" and the heartbeat still goes out with the version.

## The version number

The reported version comes from a constant compiled into the build. There is no `.env` variable or database setting that overrides it, deliberately: the number is only useful if it describes the code that is actually running. You can see it in the admin sidebar footer.

![The admin sidebar footer showing the RackHelm product name and version number](/wp-content/uploads/rackhelm-docs/installation-admin-version-footer.png)
The version in the sidebar footer is the same number reported to the licence server.

## When the check fails

A failed check ends the request with a plain page reading `Licence validation error:` followed by a short message. The detail is written to `storage/logs/laravel.log` prefixed `[License]` with a code:

| Code | Meaning | What to do |
| --- | --- | --- |
| `missing_key` | No licence key is configured. | Set `RACKHELM_LICENSE_KEY` in `.env`. |
| `unreachable` | The licence server could not be reached and there is no cached verdict within grace. | Check outbound HTTPS to `license.orbi-hosting.com` is not blocked by a firewall or egress proxy. |
| `bad_response` | The server replied, but not with a signed payload — usually a rejection with an explanatory message. | Read the logged message; it is passed through from the licence server. |
| `bad_signature` | The reply's signature did not verify. | Something is intercepting the connection. Check for a TLS-inspecting proxy. |
| `binding_mismatch` | The reply was not bound to this key and this server. | Usually a proxied or replayed response. Contact support. |
| `rejected` | The licence is invalid, suspended or expired. | Contact support with the reason shown. |

![Browser showing the plain Licence validation error page](/wp-content/uploads/rackhelm-docs/installation-license-failure-page.png)
A licence failure replaces the whole page; the diagnostic detail is in laravel.log, not on screen.

## Moving a server

The fingerprint is derived from the server's IP address and hostname, so migrating to a new machine, changing the primary IP, or renaming the host produces a new fingerprint. The cached verdict from the old machine will not be accepted — it is bound to the old fingerprint — and the next heartbeat will present the new one. If your licence is bound to a single host, arrange the rebinding with support before you cut over.

> **Note:** Copying `storage/app/private/.orbi_license_cache` to another server does nothing. The cache is signature-verified and fingerprint-bound on every read, so it is rejected on any machine but the one it was issued for.

## Troubleshooting

| Symptom | Cause and fix |
| --- | --- |
| Portal works, then stops exactly three days later | Offline grace expired. The licence server has been unreachable since the last successful heartbeat — check egress firewalling and DNS. |
| Every page is slow by roughly 7 seconds | Heartbeats are timing out. RackHelm suppresses retries for 5 minutes after a failure, so persistent slowness means something is answering slowly rather than failing outright. |
| The licence works on the website but Artisan commands behave oddly | Unrelated — the boot-time check is skipped in console. Look elsewhere. |
| Licence failed immediately after restoring a backup onto a new box | New IP or hostname means a new fingerprint. Contact support to rebind. |
| Key set in `.env` but still `missing_key` | A duplicate `RACKHELM_LICENSE_KEY=` line earlier in the file is winning — the first occurrence takes precedence. Delete the empty one. |
