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.
Why it exists
Section titled “Why it exists”- 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.
- 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.
- 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.
- Product decisions. Which source kinds and connectors are really used decides the order of the connector catalogue.
- 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.
What is sent
Section titled “What is sent”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.
The payload, in full
Section titled “The payload, in full”{ "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"}What is never sent
Section titled “What is never sent”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.
The installation identifier
Section titled “The installation identifier”/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.
When it sends
Section titled “When it sends”| 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.
The update check
Section titled “The update check”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.
Where to see what was sent
Section titled “Where to see what was sent”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:
ndr telemetry show # the payload that would be sent nextndr telemetry send --dry-run # build, sign, check and record it; still no connectionThere 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.
Switching it off
Section titled “Switching it off”# /etc/ndr/ndr.env (mode 0600)NDR_TELEMETRY=offsystemctl restart ndrThe 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.
Retention
Section titled “Retention”| 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.
The short version
Section titled “The short version”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=offand 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.