> For the complete documentation index, see [llms.txt](https://docs.hex-rays.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.hex-rays.com/ida-9.5/add-ons/floating-license/admin/license-server.md).

# License Server Administration

## Introduction

{% hint style="success" %}
The Hex-Rays License Server is a single self-contained program. If you are coming from an earlier one, this is what changed:

* **Nothing to install.** Unzip the download and run it; there is no installer and no runtime to add.
* **One command sets it up.** [`setup`](#installation) writes the configuration, creates the database, makes the first administrator account, and registers the system service.
* **`license-client` replaces `lsadm`**, with the same commands.
* **The server license is no longer tied to a MAC address**, so a deployment can move between machines.
* [`setup` can migrate an existing deployment](#migrating-to-the-latest-version), while leaving the old installation available as a rollback point.

Existing IDA clients remain protocol-compatible. Update their server address only if the deployment address changes.
{% endhint %}

This manual describes the installation, management, and interaction with a Hex-Rays License Server deployment. It is primarily intended for administrators, and will focus on the setup and management of the Hex-Rays License Server.

{% hint style="info" %}
Refer to the [License Server Installation Checklist](/ida-9.5/add-ons/floating-license/admin/license-server/license-server-checklist.md) for a concise set of steps to follow.
{% endhint %}

## Installing the Hex-Rays License Server

### Prerequisites

After your purchase of a Hex-Rays product with floating licenses, open the [Download Center](https://my.hex-rays.com/dashboard/download-center), select the appropriate release channel, then open **ida-license-server** and the release you want to install. Before you begin, have:

* the Hex-Rays License Server download for your platform
* the installer for the product you have purchased
* the `license_server_<LID>.hexlic` and certificate bundle issued for this deployment (where \<LID> is your license ID)

{% hint style="info" %}
The certificate bundle contains the certificate and private key used for the TLS encryption between the clients and the license server. You should not try to create them yourself: IDA checks the server certificate against a single Hex-Rays root, and rejects a certificate from any other authority.
{% endhint %}

The server will not start without both of those files, so download them before you begin.

You need `root` on the host only if you want the server to run as a system service that starts at boot. Running it as an ordinary user needs no special privileges.

#### Supported platforms

The Hex-Rays License Server runs on **Linux x64 and Linux arm64**. We have tested it on Debian and Ubuntu, but other major flavors of Linux should be fine too.

{% hint style="info" %}

## Version numbering and compatibility

The License Server has its own version and release schedule. Check the installed version with `./license-server version`.

What governs whether a client can talk to a server is the protocol between them, not the version numbers — and on that, nothing has narrowed: this server speaks the same protocol as the one it replaces and still accepts the older revisions of it, so an IDA installation that worked with your previous Hex-Rays License Server works with this one. Only the address it connects to may need changing. (IDA 8.4 and earlier used Flexera-based licensing and cannot connect to any Hex-Rays License Server — see [Migrate from the Flexera-based license server](/ida-9.5/add-ons/floating-license/admin/hex-rays-license-server-migration-guide.md#from-the-flexera-based-license-server-ida-84-and-earlier).)

Run a current server release and include the output of `./license-server version` when reporting a problem.
{% endhint %}

### Installation

#### What you download

The download is one zip per platform, and it contains a folder with everything in it:

```
license-server-<version>-<target>.zip
└── license-<version>-<target>/
    ├── license-server     the server
    ├── license-client     the admin client
    └── README.md
```

Unzip it and both programs are ready to run — the executable bit survives the archive, so there is no `chmod` step. `cd` into the folder, and every command in this manual works as written.

{% hint style="info" %}
These are not the names the previous license server used. It shipped `hexlicsrv` and `lsadm`; a script written against that install needs those two words changed.

IDA installs its own `lsadm`, which is a different program from this one.
{% endhint %}

Both programs say which platform they are for. `./license-server version` prints the version, build date and target, so if you have lost track of which file is which, ask the file itself.

#### Where to unzip it, and whether to use `sudo`

Where the folder goes depends on how the server will run, so decide the two together. `sudo` is what makes the deployment belong to **the machine** rather than to your user account, and only a machine-wide deployment can be registered as a system service that starts at boot. Nothing else in `setup` needs `root`.

| What you want                     | Run setup as                  | Unzip it                               |
| --------------------------------- | ----------------------------- | -------------------------------------- |
| Try it out, or run it yourself    | `./license-server setup`      | anywhere                               |
| A system service, started at boot | `sudo ./license-server setup` | somewhere the service account can read |

The second row carries a constraint the first does not. A system service runs under an account of its own — `license-server`, created by `setup` — which is in neither the owner nor the group of your home directory. To read the deployment's data it needs execute permission for others on **every** directory on the path, and a home directory does not give it (`0750` on Ubuntu since 21.04, `0700` on RHEL).

So for a system service, put the folder somewhere that account can reach. `/opt` is where add-on software goes, and every account can pass through it:

```bash
sudo mv license-<version>-<target> /opt/license-server
cd /opt/license-server
```

{% hint style="info" %}
Use an unversioned path. An upgrade replaces the program where it stands and keeps the data beside it.
{% endhint %}

If you run `sudo ./license-server setup` from somewhere the service cannot reach, `setup` says so and stops **before writing anything**, naming the directory that is closed and the ways forward. Nothing is left to clean up.

#### Running setup

Hand `setup` the two files issued for this deployment and it does the rest:

```bash
sudo ./license-server setup --defaults \
  --license <your-license>.hexlic \
  --cert-bundle <your-bundle>.zip
```

`setup` writes the configuration, creates the data directory and database, records where your license file is, unpacks the certificate bundle, creates the first administrator account, and registers and starts the system service.

{% hint style="warning" %}
**The administrator password is printed once**, in the closing summary, and only to the terminal — never to a log. Store it before you close the terminal. If it is lost, reset it with `./license-server admin reset-password`.
{% endhint %}

Run `setup` with no flags for the guided version. It asks the same questions, and it looks for your two files in the folder holding the program and in the one your terminal is in, offering the newest match it can read. For a license file it also prints when that license expires — **read that line**, because a folder that has been through a renewal still holds the old file, and an expired license gives you a setup that reports success and a server that will not start.

The deployment keeps its data in `data/` beside the program:

| Path                        | What it holds                                   |
| --------------------------- | ----------------------------------------------- |
| `data/config/config.yaml`   | ports, session and borrow settings, TLS paths   |
| `data/config/acl.yml`       | access rules                                    |
| `data/db/license-server.db` | sessions, seat allocations, audit log, accounts |
| `data/licenses/`            | the license bundle the server hands out         |

{% hint style="info" %}
`setup` records the path of the program it was run from, and every later command reads it back. If you move or delete that file, the deployment stops working — `service register` refuses, and `service status` says so.
{% endhint %}

#### Activating the server license

The server needs its own `.hexlic` to start. Unlike the previous license server, **it is not bound to a MAC address**: there is no host ID to collect and no activation step tied to a network interface, so a deployment can move machines or run in a container without the license being reissued.

The gate checks the signature, that the file is a license-server license, that it has at least one seat, and that it is still valid. It runs before any listener binds, so a refusal leaves nothing running.

Expiry follows a 30/30-day pattern: more than 30 days out it boots silently; inside 30 days it boots and warns; expired it boots and warns for another 30 days; past that it refuses. A perpetual license never warns.

To renew, put the new file beside the old one. A license that passes cleanly beats one that only passes with warnings, so the renewal takes effect as soon as it is there.

#### Creating the initial database

There is nothing to do: `setup` creates the database, and schema changes are applied automatically when a newer version starts. There is no separate initialization step and no schema flag.

#### Testing the server

**If you installed it as a system service:**

```bash
sudo ./license-server service status
```

**If you are running it yourself**, start it in the foreground and leave it running:

```bash
./license-server start
```

Either way, confirm the server answers on the administration console port — 8080 unless you changed it:

```bash
curl http://<host>:8080/api/health
```

Then check that your licenses are actually being served, which is the thing that matters to IDA users:

```bash
./license-client -h <host>:65434 list
```

That should list your licenses with their seat counts, and the startup log names the bundle it loaded.

The first time the client connects to a server it shows you that server's certificate and asks whether to record it, so run this from a terminal. An empty list means the server is up but has nothing to hand out; check the Licenses page and the startup log to see which bundle was loaded.

{% hint style="info" %}
`start` and `service start` are different commands. `start` runs the program in your terminal; `service start` drives the installed service. Running both at once makes them collide on the ports.
{% endhint %}

### The administration console

The server serves a web console on its admin port — `http://<host>:8080/admin` by default — where you can see the licenses in force, who holds which seat, live sessions, the access rules and the audit log, and return a seat that a departed client is still holding.

Sign in with the administrator account `setup` created. Manage accounts from the command line with `./license-server admin create|list|reset-password|delete`.

{% hint style="warning" %}
Every console account is an administrator, and the pages disclose who is connected from where and who holds which seat. The console port is also where the unauthenticated health check is served, so keep that port on a trusted network. Only the RPC port needs to be reachable by your IDA users.
{% endhint %}

## Management

### Backup and restore

Nothing is backed up automatically. **Back up before every upgrade.**

Everything worth keeping is in the deployment's data directory — `/opt/license-server/data` if you followed the steps above:

| Path                                           | Why it matters                                  |
| ---------------------------------------------- | ----------------------------------------------- |
| `data/db/license-server.db` (+ `-wal`, `-shm`) | sessions, seat allocations, audit log, accounts |
| `data/config/config.yaml`                      | ports, session and borrow settings, TLS paths   |
| `data/config/acl.yml`                          | access rules                                    |
| `data/licenses/*.hexlic`                       | the licenses the server hands out               |

The database is in WAL mode, so copy it the way SQLite intends rather than with `cp`:

```bash
sqlite3 /opt/license-server/data/db/license-server.db \
  "VACUUM INTO '/backup/license-server-$(date +%Y%m%d).db'"

tar czf /backup/license-config-$(date +%Y%m%d).tgz \
  -C /opt/license-server/data config licenses
```

{% hint style="warning" %}
Check the backup is not empty. `sqlite3` creates a database it cannot find, so pointing the command at the wrong path succeeds and writes a valid, empty file — which looks exactly like a good backup until the day you need it.

```bash
sqlite3 /backup/license-server-$(date +%Y%m%d).db "select count(*) from seat_allocations;"
```

{% endhint %}

To restore, stop the server, put the files back, and start it again.

### Upgrading the server

The upgrade is a replacement of the program, in place:

1. Stop it: `sudo ./license-server service stop`
2. **Back up the database** (above).
3. Put the new `license-server` where the old one was — the folder the deployment was set up from.
4. Start it: `sudo ./license-server service start`

Schema changes are applied automatically at startup. A previous binary can read the newer additive schema and warns that the database is ahead, but it does not reverse schema or data changes. The CLI does not prepare an automatic rollback point, which is another reason to take the backup.

### License borrowing limits

The maximum duration a license can be borrowed for is `borrow.maxHours` in `data/config/config.yaml`. Zero means no limit. Restart the server after changing it.

A borrow that is refused is logged with the reason and the duration the client asked for, so the log answers "why can't this user borrow?" directly.

## Migrating to the latest version

{% hint style="info" %}
**Migrating from a Flexera-based server?** The procedure in this section is for the previous Hex-Rays License Server shipped with IDA 9.0–9.4. If your old server runs `lmadmin` or `lmgrd` together with the `hexrays` vendor daemon, follow [From the Flexera-based license server](/ida-9.5/add-ons/floating-license/admin/hex-rays-license-server-migration-guide.md#from-the-flexera-based-license-server-ida-84-and-earlier) instead.
{% endhint %}

`setup` can migrate an existing Hex-Rays License Server deployment. The new server reads the old configuration, TLS material, access rules and license, and **copies** the old database into its own data directory.

**Stop and disable the old server first.** Migration takes over its RPC port and copies its database, so anything the old server writes afterwards would be lost:

```bash
sudo systemctl stop hexlicsrv
sudo systemctl disable hexlicsrv     # or a reboot brings it back beside the new one
```

`setup` refuses to migrate while it can see the old server running, still enabled, or something answering on its RPC port, and it says which of the three it found before anything is written.

There are two ways to ask for the migration. **Interactively**, `setup` asks as its very first question — *Migrate an existing Hex-Rays License Server install?* — and, if you say yes, asks for the directory, offering `/opt/hexlicsrv`:

```bash
sudo mv license-<version>-<target> /opt/license-server
cd /opt/license-server
sudo ./license-server setup
```

Or name the directory up front, which is what a script does:

```bash
sudo ./license-server setup --from-legacy /opt/hexlicsrv
```

{% hint style="info" %}
The question is asked only when `setup` is run interactively and you did not pass `--from-legacy`. An unattended run never guesses: a leftover directory from an old install would otherwise turn a fresh deployment into a migration.

Do not unzip the new server into the old install's directory — it has to stay as it is until the migration has read it.
{% endhint %}

**Read the summary.** It lists every file carried across and everything that could not be, and it flags access rules whose meaning changes:

* A product filter on a rule is dropped, because the old server never applied its own — keeping it would make the migrated rule narrower than the one it replaces.
* `*` and `?` in a user name or machine name become wildcards. The old server compared names literally, so a rule naming `contractor-*` matched nobody; here it matches the whole family.
* Name matching is now case-insensitive, so a rule naming `Alice` also matches `alice`.

Review `data/config/acl.yml` before serving, especially if the old deployment's rules were never actually in effect.

### Rolling back

Your old install is opened read-only and never written to, so it is still exactly as it was. To go back, stop the new server and start the old one:

```bash
sudo ./license-server service stop
sudo systemctl enable --now hexlicsrv
```

{% hint style="warning" %}
**A rollback loses the seats issued after the migration.** The two servers no longer share a database, so borrows taken by the new server are in its copy and invisible to the old one. Those clients hold a record the rolled-back server does not know about, and their seats stay free until the original borrow period elapses.
{% endhint %}

## Configuration

Settings live in `data/config/config.yaml`, written by `setup`. The file covers ports, session timeouts, borrow limits, audit retention and the TLS material. Restart the server after editing it.

The two ports are the ones most often changed, and they can be set at install time instead — `--rest-port` for the administration console, `--rpc-port` for the port IDA connects on. `setup` writes them into the configuration file, so they hold however the server is started.

Some settings also have an `LS_`-prefixed environment variable; the tuning knobs in `config.yaml` do not. The file wins over the environment, so a value `setup` wrote cannot be contradicted by one.

{% hint style="info" %}
**Supported variables:** `LS_HOST`, `LS_PORT`, `LS_RPC_PORT`, `LS_RPC_TLS`, `LS_LOG_LEVEL` and `LS_WORK_DIR`, covering the host, the ports, the transport and logging. Further variables may be added over time; `config.yaml` remains the complete list of settings.
{% endhint %}

A broken `database:` or `tls:` block stops the server rather than falling back, because the fallback would be a different database or a different certificate. A borrow limit (`borrow.maxHours`) that is present but unreadable stops it too, for the same reason — its default, `0`, means *unlimited*, the opposite of the cap you meant to set. Every other setting falls back to its default with a warning.

### TLS

The RPC listener normally terminates TLS itself, with the certificate from your bundle. IDA verifies the endpoint certificate against a single Hex-Rays root, so a certificate from any other authority will not work. A fronting load balancer can either pass TCP through, or terminate TLS only if it presents the issued Hex-Rays certificate. For TLS termination at the load balancer, explicitly disable RPC TLS between it and the server with `LS_RPC_TLS=false` or a `tls` configuration whose `mode` is `disabled`.

To rotate a certificate, put the new pair in place, point `config.yaml` at it, and restart. The listener reads its material only at startup, so there is no hot reload.

{% hint style="warning" %}
An expired certificate passes every startup check and fails only at the TLS handshake, which reaches IDA as an unexplained connection error while the server looks healthy. Track the expiry date rather than waiting to be told.
{% endhint %}

The console is served as plain HTTP on its own port. Keep it on a trusted network, or terminate TLS in front of it.

## Access Control Lists (ACL)

Access rules live in `data/config/acl.yml`, and the console's Access Control page edits the same file. A new deployment starts permissive:

```yaml
defaultPolicy: allow
```

Rules match on user name, machine, product and license, and the default policy decides what happens when no rule matches. Name matching is case-insensitive, and `*` and `?` are wildcards.

{% hint style="info" %}
`defaultPolicy: deny` with no matching rule is the most common reason for a client being refused. Every refusal is logged with the rule that matched, or a note that none did, and the console has a rule tester for checking a rule before you rely on it.
{% endhint %}

## License Server Command Line

| Command    | What it does                                                                                                                                                             |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `setup`    | Set up the deployment: configuration, data directory, first administrator, service                                                                                       |
| `start`    | Run the server in this terminal                                                                                                                                          |
| `service`  | Manage the installed service: `register`, `unregister`, `start`, `stop`, `restart`, `status`, `logs`                                                                     |
| `admin`    | Manage console accounts: `create`, `list`, `reset-password`, `delete`                                                                                                    |
| `rollback` | Restore a rollback point prepared outside the normal upgrade flow. Normal upgrades do not create one; use the documented backup and manual replacement procedure instead |
| `teardown` | Stop and unregister the service and forget the deployment; `--purge` also deletes the data                                                                               |
| `version`  | Print the version, build date and platform                                                                                                                               |
| `notices`  | Print the third-party notices for the libraries the program is built from                                                                                                |

`./license-server --help` lists them all, and `--help` after any of them explains that one.

### Reading the log

Log lines are plain text with no colour codes, so they read the same in a terminal, in `journalctl`, or in a file. `LS_LOG_LEVEL` is `debug`, `info`, `warn` or `error`; at `info` a busy server logs a line per request and IDA clients send regular keepalives, so `warn` is a reasonable setting for a large deployment.

For a service deployment, `./license-server service logs` shows them.

Refusals carry a stable, greppable name and the identity involved, so `grep refuse.` answers most "why can't my user get a seat?" questions directly:

| Event                   | Meaning                                                                   |
| ----------------------- | ------------------------------------------------------------------------- |
| `refuse.acl_deny`       | an access rule, or the default policy, denied it                          |
| `refuse.checkout`       | no seat available, license expired, or unknown license                    |
| `refuse.borrow`         | past the license's own expiry, over the borrow limit, or already borrowed |
| `refuse.helo`           | the session could not be created, usually a version mismatch              |
| `license.file_rejected` | a `.hexlic` was skipped at startup, with the reason                       |

A checked-out seat left by a client that vanished uncleanly is reclaimed when the session times out, and the console can return it sooner. A borrowed seat survives session cleanup and remains held until it is returned, its borrow period expires, or an administrator force-returns it.

## license-client Command-Line Tool

`license-client` replaces `lsadm` and keeps its commands.

| Command                | What it does                                    |
| ---------------------- | ----------------------------------------------- |
| `info`                 | show server information                         |
| `list`                 | list the licenses the server offers             |
| `checkout` / `checkin` | take and return a seat                          |
| `borrow` / `return`    | borrow a license for offline use, and return it |
| `force-return`         | return a license held by another user           |
| `active`               | list the licenses currently in use              |

Give it the server with `-h <host>:<rpc port>`; `-u` acts as a specific `user@machine-id`, and `-j` prints JSON for scripting.

```bash
./license-client -h licenses.acme.com:65434 list
./license-client -h licenses.acme.com:65434 -j list
```

Run it with no command for an interactive session.

{% hint style="info" %}
The client records the certificate of each server it has connected to, and asks before recording a new one. A scripted call that has never connected before has nobody to ask, so it refuses unless you pass the fingerprint you have already checked with `--fingerprint <sha256>`.
{% endhint %}

{% hint style="info" %}
`borrow list` and the `wipe` commands act on IDA's own local Offline records, not on anything the server holds, so `license-client` does not carry them out. Manage borrowed licenses from IDA's **License manager** window, where you can view them and return them.
{% endhint %}

## Connecting IDA

In IDA, open the License manager, select **Use floating license server**, enter the host name your administrator gave you, and click **Connect**. See [Getting started with floating licenses](/ida-9.5/add-ons/floating-license/getting-started.md) for the full client-side walkthrough.

IDA connects on the RPC port, 65434 by default. The administration console shows the RPC port in use.

## Troubleshooting

### The service starts, then keeps restarting

Almost always a missing or rejected server license: the server refuses to start, and the service restarts it. `./license-server service logs` names the reason. A license placed by hand must be readable by the service account.

### The admin client refuses to connect: the server is not recorded

`license-client` remembers the certificate a server presented the first time it connected, and will not connect to one it has not recorded:

```
localhost:65434 is not in ~/.local/share/license-client/config/known-servers
and there is no terminal to ask on.
```

Run the command once from a terminal and it shows you the certificate and asks, recording your answer for next time. In a script, check the fingerprint against the server first and then pass it:

```bash
./license-client -h <host>:65434 --fingerprint <sha256> list
```

The same message appears if a server is reinstalled with a new certificate. If you cannot explain the change, treat it as one worth investigating rather than approving.

### Connection issues

The console and the IDA-facing listener are two different ports. The console is plain HTTP on the admin port; IDA connects on the RPC port over TLS. Check that the RPC port is the one IDA was given. If an upstream device terminates RPC TLS, it must present the issued Hex-Rays certificate.

### A seat is held by someone who is not there

A checkout left by a client that vanished uncleanly is reclaimed when its session times out. A borrowed seat instead lasts until return or expiry. The console's Allocations page can return either one sooner, as can `license-client force-return`.

### The server refuses to start after `config.yaml` was edited

A broken `database:` or `tls:` block stops the server rather than falling back to a different database or a different certificate. The message names the key and the problem.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.hex-rays.com/ida-9.5/add-ons/floating-license/admin/license-server.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
