Skip to content

Installing a server

What goes on disk, where the state directory lives, the service unit, the container form, and what to check before calling an installation done.

The platform is one process. There is no cluster to join, no agent to install on the machines it talks to, and nothing to configure in a message broker before the first delivery. What follows is the whole installation.

What goes on disk

Two things, in the same directory:

/opt/nexus/
nexus # the executable
ui/ # locales/ templates/ static/ — the management UI's assets

ui/ beside the executable is the shipping layout and the one the binary looks for. Put it elsewhere and name the path in NEXUS_UI_ASSETS_DIR. A ui/ that is present but incomplete stops the start instead of quietly falling back to another path — a half-finished install must not come up serving a UI with no stylesheet. Whichever rule won is announced at boot, because from the outside the three of them are invisible:

INFO nexus_ui::assets: UI assets path=/opt/nexus/ui origin=the `ui/` directory beside the executable

One extra tool is worth installing: sqlite3. The server does not need it — SQLite is compiled into the binary — but the backup procedure is written with it. See Backup and restore.

The state directory

There is no separate setting for it. Everything derives from --db: with --db /var/lib/nexus/registry.db every other file the installation owns lands in that parent directory. What lands there, file by file, is in Where state lives.

Terminal window
install -d -o nexus -g nexus -m 0750 /var/lib/nexus

The mode carries weight. That directory holds the API keys as blake3 fingerprints, the audit chain, and — for any flow deployed with log_level: full — message payloads in the clear. 0750 and a dedicated user is the shape the rest of this manual assumes.

First boot

Terminal window
sudo -u nexus /opt/nexus/nexus serve \
--host 127.0.0.1 --port 9090 \
--db /var/lib/nexus/registry.db

--host 127.0.0.1, not the default 0.0.0.0: the platform speaks plaintext HTTP and TLS terminates in front of it. What the proxy has to do, and what it must leave alone, is The gateway boundary.

The start prints what it opened, what it resolved and what it refused — Starting the server reads the boot output line by line. Two flags are worth knowing here: --grpc-port opens the gRPC door on a listener of its own, and --port 0 asks the system for a port, which then appears in the listening on line and nowhere else.

The state key, if any flow needs one

A flow that declares dedup_key:, correlation_open: or correlation_close: keeps its deduplication memory and its open correlations keyed by a per-installation secret, 64 hexadecimal characters in NEXUS_SECRET_NEXUS_STATE_KEY:

Terminal window
openssl rand -hex 32

Three cases at startup, and only the first is an ordinary installation:

  • Absent, and no deployed flow declares one of those three keys. Normal. The boot line reads flow state (dedup + correlation): disabled — no NEXUS_SECRET_NEXUS_STATE_KEY set.
  • Absent, with such a flow deployed. The start is refused and the message names the flows. Those flows asked for exactly-once semantics; running without the key would read every duplicate as a fresh delivery. A flow deployed into an already-running server that has no key answers 503 instead, with no Retry-After — waiting a second does not produce a key.
  • Malformed. The start is refused whether or not anything needs it. Starting anyway would fingerprint later deliveries under a different key and split the deduplication memory in two.

The key is deliberately not in the databases: whoever holds the file with the fingerprints would otherwise also hold the key that made them. So it is not in a backup of the state directory either — keep it with the installation’s other secrets, and see what a restore does not bring back.

The first UI account

The management UI is served by the same process, under /ui/, on the same port. The first sysadmin comes from NEXUS_UI_ADMIN_USER and NEXUS_UI_ADMIN_PASS on first boot — the full procedure, and what to do with those variables afterwards, is under The first UI account.

A service unit

[Unit]
Description=NexusFabric
After=network.target
[Service]
User=nexus
Group=nexus
WorkingDirectory=/var/lib/nexus
Environment=RUST_LOG=info
EnvironmentFile=/etc/nexus/nexus.env
ExecStart=/opt/nexus/nexus serve --host 127.0.0.1 --port 9090 --db /var/lib/nexus/registry.db
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target

Restart=on-failure, not Restart=always. A start that fails on configuration — a missing NEXUS_CONFIG_* a flow declares, a state key that is not there — has to stay down and visible rather than loop every five seconds with the reason scrolling past. The refusal names what is missing; it is in the log the first time.

Secrets and configuration go in EnvironmentFile. The binary also reads a .env walking up from its working directory, but it does not override variables already in the environment, so EnvironmentFile wins — which is the order you want. .env is a development convenience.

The container form

The image carries the same two things, at /usr/local/bin/nexus and /usr/local/bin/ui/: a different path, the same rule, so NEXUS_UI_ASSETS_DIR is not needed. State lives under /data, which is the volume, and --db /data/registry.db is already the image’s default command.

Terminal window
docker run --rm -p 9090:9090 -v nexus-data:/data <the image you were given>

The process runs as an unprivileged user (uid 10001), which is why the port is 9090 and not 443, and why the volume is created owned by that user rather than by root. The entrypoint is nexus, so every command in this manual runs in the container as docker exec <container> nexus ….

services:
nexus:
build: .
volumes:
- nexus-data:/data
ports:
- "9090:9090"
environment:
NEXUS_UI_ADMIN_USER: admin
NEXUS_UI_ADMIN_PASS: change-me-before-first-boot
RUST_LOG: info
restart: unless-stopped
volumes:
nexus-data:

Two translations hold for the rest of the operator pages: where a procedure says systemctl stop nexus, read docker stop; where it says nexus …, read docker exec <container> nexus …. The backup is the one place that needs more than a translation, because of file ownership on a fresh volume — it says so on its own page.

No healthcheck is declared in the image, on purpose: the runtime image carries no HTTP client to ask with. An orchestrator uses its own check against /health.

The first tenant and the first key

The access gate is closed by default, so a fresh installation answers /health and the UI login and refuses everything else until a tenant and a key exist.

Terminal window
nexus tenant create --id acme --display-name Acme
nexus keys create --tenant acme --label integration --scopes '*'

The raw key is printed once and never stored — the registry keeps a blake3 fingerprint of it. Scopes, per-key rate limits and flow allowlists are in Authentication.

Before you call it installed

In this order:

  1. curl http://127.0.0.1:9090/health answers {"ok":true}.
  2. The UI answers at /ui/ and the first account can log in.
  3. TLS terminates in front, and the proxy passes what the platform needs and strips what it must — The gateway boundary.
  4. nexus config check reports nothing missing, if any deployed flow declares config:.
  5. One flow deployed, and one real message through it, with the key the callers will actually use.
  6. That delivery is visible in the log and in the UI — Observability.
  7. A backup runs on a schedule, and a restore has been rehearsed once, on this machine, from those files — Backup and restore.