Skip to content

Instance registration and telemetry

Every Nyxtrace installation — free and pro — registers itself with the Nyxtrace server (https://license.nyxtrace.com) and sends one small heartbeat per day.

Open source does not mean that nothing is ever sent home. The trade we make instead is radical transparency: the payload is written out field by field on this page, the installation shows you the exact bytes it last sent, and on the free plan you can switch the whole thing off with one variable.

  1. Deployment tracking. How many installations run, in which version, on which operating system, in which country, free or pro. Without that number every product decision is a guess.
  2. Update notices. The instance learns that a newer version exists and says so in the interface. There is no auto-update; nothing is downloaded and nothing is replaced.
  3. Support. When you ask for help, the version, the source kinds and the size class are already known, so you do not have to collect logs first.
  4. Product decisions. Which source kinds and connectors are really used decides the order of the connector catalogue.
  5. The licence check on pro. Registration is the seat binding described under licensing — on the pro plan it is not just statistics.

Explicitly not a purpose: user profiles, per-person usage measurement, advertising, passing data to third parties, or research on foreign networks.

The complete list. There is nothing else in the payload:

Field Type Example
schema_version int 1
install_id string ins_9f2b7c1d4e5a6b70c1d2e3f405162738
product_version string 0.3.1
bundle_version string 2026.09.11
os_id string debian
os_version_major string 13
kernel_major string 6.1
arch string amd64
plan free | pro free
license_key_id_hash string | null 4f1c2a9b7e0d3856 — the first 16 hex of a SHA-256 over the key identifier, pro only
tenant_count int 1
device_bucket enum 11-50
source_kinds string[] ["netflow","suricata","unbound","zeek"]
connector_counts map {"unifi":1} — the type and how many instances, never a name
uptime_bucket enum 1-7d
learning_phase enum learning | complete | unknown
ingest_error_bucket enum 0
sent_at string 2026-09-11T14:45:00Z

The counts are buckets, not figures:

Bucket Values
device_bucket 0-10, 11-50, 51-200, 201-1000, >1000
uptime_bucket <1h, 1-24h, 1-7d, 7-30d, 30-90d, >90d
ingest_error_bucket 0, 1-10, 11-100, 101-1000, >1000

That is deliberate. An exact device count, repeated daily, is a behavioural profile of a household; a bucket boundary is not. Every product decision this serves is answered at bucket resolution.

source_kinds are the source categories of the sensor registry — netflow, suricata, zeek, unbound — not sensor identifiers. zeek.conn and zeek.dns both become one zeek; a sensor name such as sensor_ndr01_vsensor is never sent.

The server adds three things of its own: the country the request came from, its own receipt time, and whether a plan: pro matches an active licence. The sender address itself is resolved to a country code in the request handler and then discarded — it is stored neither in the database nor in the proxy log.

{
"schema_version": 1,
"install_id": "ins_9f2b7c1d4e5a6b70c1d2e3f405162738",
"product_version": "0.3.1",
"bundle_version": "2026.09.11",
"os_id": "debian",
"os_version_major": "13",
"kernel_major": "6.1",
"arch": "amd64",
"plan": "free",
"license_key_id_hash": null,
"tenant_count": 1,
"device_bucket": "11-50",
"source_kinds": ["netflow", "suricata", "unbound", "zeek"],
"connector_counts": {"unifi": 1},
"uptime_bucket": "1-7d",
"learning_phase": "learning",
"ingest_error_bucket": "0",
"sent_at": "2026-09-11T14:45:00Z"
}

IP addresses — not even hashed — MAC addresses, host names, domain names, device names, user names, email addresses, tenant identifiers, sensor identifiers, site names, coordinates, VLAN names, CIDRs, flows, incident contents, incident titles, model names from your own YAML models, evidence, analyst text, configuration files, tokens, or a licence key in clear text.

That is not a statement of intent but an enforced property of the client. Two independent layers run before every send, and before every dry run:

  • A field whitelist. The finished payload is serialised and read back generically; every key must be in the table above and every value must match its rule. An unknown key — including one a later change adds by accident — fails the send.
  • A pattern guard over the whole JSON string: IPv4, IPv6, MAC in both notations, email addresses, and forbidden key names such as ip, host, hostname, domain, user, tenant, sensor, device_name, serial, flow, incident, evidence, token, secret, password.

A violation aborts the send rather than trimming the payload. One layer would not be enough: the whitelist catches a new field but not a host name that ended up inside bundle_version, and the guard catches the host name but not a harmlessly named new field with sensitive content. The code is open, so you can check this yourself instead of believing it.

/etc/ndr/install-id, mode 0600, owned by the service user: ins_ plus 32 random hex characters, written once and then left alone.

It is not a device identifier. It belongs to the installation, not to the hardware: no machine ID, no MAC address, no serial number and no CPU ID is read, so there is no hardware fingerprinting anywhere in the product. Creation is atomic and idempotent — two parallel starts produce one identifier, not two — and an existing but malformed file is reported rather than overwritten, because on the pro plan overwriting it costs a seat.

Put /etc/ndr/install-id in your backup. Losing it makes the installation look like a new one and costs a pro seat until the server side is corrected.

Event When
Registration On the first start, once
Heartbeat Daily, at a fixed minute derived from the install_id so not every instance sends at the same time
Extra heartbeat Immediately on a version change (product_version or bundle_version)
Update check Right after a successful heartbeat

Restarting does not send anything unless it is genuinely due or the version changed: a restart storm must not become a request storm. A failed send is retried with backoff — 30 seconds doubling to a 15-minute ceiling, at most five attempts per cycle — and does not displace the next regular slot.

A failing server is visible and harmless. The telemetry health component turns degraded, and ingest, analysis, detection, the interface and the API are untouched. Nothing about the heartbeat delays or blocks a start.

After a successful heartbeat the appliance asks whether a newer version exists and gets back the latest version, its channel, its release date, whether it is a security release, a short notice and a release URL. The interface renders that and nothing more: it downloads nothing and replaces nothing, and the signed update channel used by an actual upgrade is separate. See upgrades.

A notice from the server is treated as untrusted data: plain text, length limited, no markup, no link rendering, no authority to do anything.

Three places show the same record, so you never have to take a claim on trust:

The interface. System has a Telemetry & registration section with the status, the endpoint, the registration state, the last attempt and its result, and the exact last payload as expandable JSON — the stored record, not an example.

The command line, which opens no connection at all:

Terminal window
ndr telemetry show # the payload that would be sent next
ndr telemetry send --dry-run # build, sign, check and record it; still no connection

There is deliberately no ndr telemetry send without --dry-run: a tool that can send by accident contradicts the promise this page makes.

The health API. GET /api/v1/health carries a telemetry component line and a telemetry object with the same fields, including the payload. The payload is visible to every reader — hiding it would defeat the point — while the failure detail is admin-only, because a failure reason can contain a host-local address.

Terminal window
# /etc/ndr/ndr.env (mode 0600)
NDR_TELEMETRY=off
systemctl restart ndr

The switch takes effect at the next start of the unit, not immediately.

On the free plan that is all it does. With telemetry off the appliance opens no connection, creates no /etc/ndr/install-id and writes no log row; the System page shows Off and the telemetry health component reports unavailable with the reason disabled. No banner, no delay, no reduced function, no nagging.

On the pro plan registration is required, because it is part of the licence check — the seat is bound to the install_id. The variable still works, but switching it off also ends entitlement renewal, so the Pro modules go read-only after the grace period exactly as they do for an expired subscription. There is no state “Pro active, but not registered”, and there is no punishment beyond that: no data loss and no ingest stop.

NDR_TELEMETRY_URL points the client at a different origin, for an organisation that wants to collect this itself. In production it must be https.

Data Kept
Raw heartbeats, one record per send 90 days, then deleted
Aggregates (counts per day, version, country, plan, bucket) Indefinitely
Registration record (identifier, secret hash, plan, country, first and last seen) While the installation is active, plus 90 days
Sender IP address Not stored — neither in the database nor in the proxy log

A deletion request is answered through the install_id: the raw and the registration records are deleted, and the aggregates remain, because they carry no link to an installation.

Telemetry and instance registration

Every Nyxtrace installation registers itself with the Nyxtrace server and sends one small heartbeat per day. We do this to know how many installations exist, on which versions, so we can ship update notices, support you, and decide what to build next.

What we send: a random installation ID, the product and bundle version, the operating system and kernel major version, the CPU architecture, the plan (free or pro), the number of tenants, the number of devices as a bucket (0–10, 11–50, 51–200, 201–1000, more), which kinds of sources are active (NetFlow, Suricata, Zeek, Unbound, …), how many connectors of each type are enabled, an uptime bucket, whether the learning phase is still running, an ingest-error bucket, and a timestamp. Our server adds the country your request came from, derived from the sender IP.

What we never send: IP addresses, MAC addresses, host names, device names, user names, flows, incident contents, tenant data, configuration, keys. This is enforced in the client: the payload is checked against a field whitelist and a pattern guard before every send, and a violation aborts the send. The code is open, so you can verify this yourself.

We do not store your IP address. We keep raw heartbeats for 90 days and anonymous aggregates indefinitely.

The System page in your installation shows the exact payload that was last sent. To turn telemetry off, set NDR_TELEMETRY=off and restart the service. On the free plan that is all it does. On the pro plan registration is part of the licence check, so turning it off also ends entitlement renewal; your data, incidents and ingest keep working.