Deploy a cluster
The quickstart is the short version: install script on the
first machine, nauka node add for every next one. This page is for
deployments that need to make choices.
The identity: token or key directory
Section titled “The identity: token or key directory”A cluster is defined by its key material — everything else (certificates, node identities) derives from it.
The token (default, recommended): one string instead of files.
nauka token# nauka1_yHc20GCphnJVTMJN8rib2DxUQuoDws13A_0gHnxOPA8The Ed25519 cluster CA is derived from it deterministically: every holder
computes the same key, so machines need to share nothing else. nauka init
generates one for you and stores it in /etc/nauka/nauka.env; node add
hands it to each machine it provisions. Prefer the NAUKA_TOKEN environment
variable over --token — command lines are visible in ps.
The key directory: for deployments that keep material in files (provisioning systems, vaults mounting secrets on disk).
nauka keygen --out ./nauka-keys # refuses to overwrite# copy to each machine, then on every node:nauka --keys /etc/nauka-keys serve --advertise <ip>:7311Same trust model, two spellings. Either way:
Ports and firewall
Section titled “Ports and firewall”| Port | Protocol | What | Exposure |
|---|---|---|---|
| 7311 | UDP | QUIC data plane (--listen) | open between nodes and to clients of the native protocol |
| 7312 | UDP | consensus plane (always listen + 1) | open between nodes |
| 8080 | TCP | HTTP API (--http) | public; reads and writes are space-scoped (signed links, space keys) |
| 443 | TCP | HTTPS API (only with NAUKA_HTTPS_DOMAIN) | public; same router as 8080, TLS terminated by the node itself |
| 53 | UDP+TCP | authoritative geo-DNS (on by default) | public if you delegate a name; binds the advertised IP only |
| 9100 | TCP | Prometheus (--metrics) | loopback by default; widen only onto a private scrape network |
| 8333 | TCP | S3 endpoint (only with the s3 build feature) | like 8080 |
All inter-node traffic is QUIC, hence UDP — the firewall step that catches everyone out. The consensus plane serves only Raft; a port collision fails loudly at bind time, before anything is written to disk.
The systemd deployment
Section titled “The systemd deployment”nauka init (and node add, for the machines it provisions) installs the
same layout the .deb/.rpm packages use:
| Path | Contents |
|---|---|
/etc/nauka/nauka.env | NAUKA_TOKEN=… and NAUKA_ARGS=--advertise <ip>:7311 [--join] — the one file to edit, then systemctl restart nauka |
/var/lib/nauka | shards, manifests, Raft log, node.key — owned by the dedicated nauka user |
/etc/systemd/system/nauka.service | hardened unit: Restart=always, ProtectSystem=strict, MemoryDenyWriteExecute, 64k file descriptors |
Knobs like NAUKA_EGRESS_QUOTA and NAUKA_CACHE_SIZE
go in nauka.env too. Logs: journalctl -u nauka — startup prints an
aligned banner (version, data dir, listen, advertise, http).
Founding rules — why there is no race
Section titled “Founding rules — why there is no race”The first serve on a blank data dir founds a single-node cluster;
with --join it waits to be added instead (what node add configures on
its targets); a data dir with existing Raft state just resumes. There is no
discovery and therefore nobody to race: a fork cannot happen because
founding is a local, deliberate act. serve probes every socket it needs
before founding — a busy port fails with nothing written.
Co-hosting several nodes
Section titled “Co-hosting several nodes”For tests on one machine: space the --listen ports by at least 2
(consensus lives on port+1) and give each node its own data dir and HTTP
port:
nauka --data-dir ./n1 serve --listen 0.0.0.0:7311 --http 0.0.0.0:8081nauka --data-dir ./n2 serve --listen 0.0.0.0:7313 --http 0.0.0.0:80827311, 7313, 7315 — never 7311, 7312, 7313, where the second node would
collide with the first one’s consensus port.
How many nodes do you need?
Section titled “How many nodes do you need?”With the default 4+2 profile every stripe becomes 6 shards that survive losing any 2. What that means in machines:
| Nodes | Shards per node | Survives |
|---|---|---|
| 1 | 6 | disk corruption only (2 shards/stripe) |
| 2 | 3 | corruption; not a node loss (3 > 2) — but healing + upload spillover have kept real files alive through single-node wipes |
| 3 | 2 | one node down or destroyed — the sweet spot to start |
| 6+ | 1 | any two nodes at once; capacity weighting starts to bite |
Below 6 nodes, every node carries a slice of every stripe, so the smallest disk paces the cluster — prefer roughly comparable capacities, or accept that the small machine fills first. The full arithmetic lives in Durability & consistency.
Multi-region, multi-provider
Section titled “Multi-region, multi-provider”Nothing to configure: nodes learn their network positions from measured RTTs and placement pulls each stripe’s shards toward distant nodes (how). A 3-node cluster spanning two providers is a real, tested configuration — give metered nodes an egress budget and front distant readers with the stripe cache.
Updating a live cluster
Section titled “Updating a live cluster”# on each node, one at a time:nauka update && systemctl restart naukaChecksum-verified, atomic binary replacement; the cluster keeps quorum
through a rolling restart. nauka update --check in a cron makes a fine
notifier — the engine never self-restarts on its own.
The DNS front door
Section titled “The DNS front door”Every node answers authoritative DNS on :53 (disable with --no-dns
or NAUKA_NO_DNS=true). Delegate any name to a few nodes — NS
records plus glue A records at your registrar, nothing else — and the
cluster becomes its own GeoDNS: each query is answered with the three
closest LIVING members to the asker, straight from the live membership.
A node that joins enters the answers within a minute; one that dies or
is removed leaves them just as fast. Geography comes from a free
monthly database (DB-IP Lite) the nodes fetch themselves; before it is
ready, answers degrade to the first living members — reachable beats
optimal.
; at your registrar, once:cdn.example.com. NS ns1.cdn.example.com.cdn.example.com. NS ns2.cdn.example.com.cdn.example.com. NS ns3.cdn.example.com.ns1.cdn.example.com. A <node on continent 1>ns2.cdn.example.com. A <node on continent 2>ns3.cdn.example.com. A <node on continent 3>Signed links never bind the host, so the same link works whichever node the DNS hands out. The responder is authoritative-only — no recursion, no zone transfers, small answers.
HTTPS, from the cluster itself
Section titled “HTTPS, from the cluster itself”Set NAUKA_HTTPS_DOMAIN=cdn.example.com on a node and it serves the
full API over TLS on :443, with a Let’s Encrypt certificate it
obtains and renews by itself. No reverse proxy, no certbot, no DNS
provider API token: the cluster is the authoritative DNS for the
domain, so the node publishes its ACME DNS-01 proof into the
replicated state, every NS node serves it as a TXT record, and the
CA validates against the cluster’s own answers. Issuance takes
seconds; renewal happens automatically under 30 days of remaining
validity and reloads the listener in place.
Each node orders its own certificate for {domain, n<ip>.domain} —
the per-node alias keeps every SAN set unique, which keeps a large
fleet clear of Let’s Encrypt’s duplicate-certificate limit. Failures
back off exponentially (one minute up to one hour), so a transient
election or an unreachable CA never burns the rate budget.
# /etc/nauka/nauka.env — that is the whole HTTPS setupNAUKA_HTTPS_DOMAIN=cdn.example.comThe systemd unit already grants CAP_NET_BIND_SERVICE, which covers
both :53 and :443. Set the variable on every node and the whole
front door is TLS: the geo-DNS hands the client its nearest nodes, and
whichever one it connects to presents a valid certificate for the
domain.