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 assetsui/ 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 executableOne 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.
install -d -o nexus -g nexus -m 0750 /var/lib/nexusThe 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
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:
openssl rand -hex 32Three 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
503instead, with noRetry-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=NexusFabricAfter=network.target
[Service]User=nexusGroup=nexusWorkingDirectory=/var/lib/nexusEnvironment=RUST_LOG=infoEnvironmentFile=/etc/nexus/nexus.envExecStart=/opt/nexus/nexus serve --host 127.0.0.1 --port 9090 --db /var/lib/nexus/registry.dbRestart=on-failureRestartSec=5
[Install]WantedBy=multi-user.targetRestart=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.
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.
nexus tenant create --id acme --display-name Acmenexus 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:
curl http://127.0.0.1:9090/healthanswers{"ok":true}.- The UI answers at
/ui/and the first account can log in. - TLS terminates in front, and the proxy passes what the platform needs and strips what it must — The gateway boundary.
nexus config checkreports nothing missing, if any deployed flow declaresconfig:.- One flow deployed, and one real message through it, with the key the callers will actually use.
- That delivery is visible in the log and in the UI — Observability.
- A backup runs on a schedule, and a restore has been rehearsed once, on this machine, from those files — Backup and restore.