Deploy a cluster
A Nauka cluster is formed by running the same command on every machine. There is no leader to designate, no seed list to maintain, no configuration file to write. The only thing you distribute is the cluster key.
The whole procedure
Section titled “The whole procedure”-
Generate the cluster key — once, on your workstation.
Terminal window nauka keygen --out ./nauka-keysThis writes
cluster-ca.key(an Ed25519 CA, mode 0600 — the secret) andcluster-ca.pem. Owning this directory is membership of the cluster.keygenrefuses to overwrite an existing key, so it cannot silently orphan a running cluster. -
Copy it to every machine.
Terminal window scp -r nauka-keys vps1:/etc/nauka-keysscp -r nauka-keys vps2:/etc/nauka-keysscp -r nauka-keys vps3:/etc/nauka-keysNothing else is distributed. Node identities, the Raft node ids, and the DHT rendezvous key are all derived from this one directory.
-
Open the ports. See the section below — this is where deployments fail.
-
Run the same command on every machine.
Terminal window nauka --data-dir /var/lib/nauka --keys /etc/nauka-keys serveEach node derives its identity from the key, detects its public address through the DHT, looks for the cluster, and either joins it or — if no cluster exists yet — stands as a genesis candidate and founds it. Startup order does not matter, and no node is privileged.
-
Check that the cluster formed. See Verifying it works.
Firewall: the step that catches everyone out
Section titled “Firewall: the step that catches everyone out”sudo ufw allow 7311/udpsudo ufw allow 7312/udpsudo ufw allow 8080/tcpsudo firewall-cmd --permanent --add-port=7311/udpsudo firewall-cmd --permanent --add-port=7312/udpsudo firewall-cmd --permanent --add-port=8080/tcpsudo firewall-cmd --reloadudp dport { 7311, 7312 } accepttcp dport 8080 acceptIf you changed --listen, adjust both numbers: the consensus plane always
sits at port + 1, and it refuses every non-Raft request, so a collision can
never quietly turn it into a second data plane.
There is no NAT traversal yet. Every node needs a public address or a forwarded port. On a multi-homed host, or behind a NAT where auto-detection picks the wrong address, set it explicitly:
nauka --keys /etc/nauka-keys serve --advertise 203.0.113.10:7311Several nodes on one host
Section titled “Several nodes on one host”Space the listen ports by at least 2, since each node claims port and
port + 1:
nauka --data-dir ./n1 --keys ./nauka-keys serve --listen 0.0.0.0:7311 --http 0.0.0.0:8080nauka --data-dir ./n2 --keys ./nauka-keys serve --listen 0.0.0.0:7313 --http 0.0.0.0:8081nauka --data-dir ./n3 --keys ./nauka-keys serve --listen 0.0.0.0:7315 --http 0.0.0.0:8082Use 7311, 7313, 7315, never 7311, 7312, 7313 — the second node would bind
the first one’s consensus port. This failure used to be silent; cluster-init
now pre-flights both planes and checks which node id answers.
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 (4 data + 2 parity) and survives losing any 2 of them. What that buys you depends entirely on how those 6 shards are spread:
| Nodes | Shards per node, per stripe | Survives losing one machine? |
|---|---|---|
| 1 | 6 | No — that machine is the cluster |
| 2 | 3 | No — 3 lost shards exceeds m = 2 |
| 3 | 2 | Yes — exactly m = 2 lost, still recoverable |
| 4–5 | 2 | Yes |
| 6 or more | 1 | Yes, and survives losing two machines |
Three nodes is the practical minimum. At three, placement enforces a
strict 2/2/2 split of every stripe regardless of declared capacities —
durability outranks capacity balancing, so a large disk is never allowed to
concentrate more than m shards of one stripe and become a single point of
failure. Raft agrees on the same number: 3 voters tolerate 1 failure, 2
voters tolerate none.
Below three nodes, Nauka still works and still detects corruption — you just have no machine-loss tolerance, which is the point of running it at all.
Running it as a service
Section titled “Running it as a service”The .deb and .rpm packages install a hardened systemd unit at
/usr/lib/systemd/system/nauka.service, running as an unprivileged nauka
user with ProtectSystem=strict and write access to nothing but
/var/lib/nauka.
-
Install the cluster key into
/etc/nauka/, which the package created with mode 0750, owned byroot:nauka:Terminal window # first node of a new clustersudo nauka --keys /etc/nauka keygen --out /etc/nauka# or, joining an existing cluster: copy that cluster's key oversudo scp cluster-ca.key cluster-ca.pem root@node2:/etc/nauka/sudo chown root:nauka /etc/nauka/cluster-ca.*sudo chmod 640 /etc/nauka/cluster-ca.* -
Adjust the configuration, if the defaults are not right. The unit reads
/etc/nauka/nauka.env, which the package seeds fromnauka.env.example:/etc/nauka/nauka.env NAUKA_ARGS=--listen 0.0.0.0:7311 --http 0.0.0.0:8080#NAUKA_DATA_DIR=/var/lib/nauka#NAUKA_KEYS_DIR=/etc/naukaNAUKA_ARGSis appended tonauka serveverbatim — that is where--advertise,--capacity,--no-discoverand--scrub-intervalgo. -
Start it:
Terminal window sudo systemctl enable --now naukasudo systemctl status nauka
The unit restarts the node automatically on failure. Rebuilding shards after a restart is normal and healthy — that is the scrubber catching up, not an error.
Verifying it works
Section titled “Verifying it works”Ask a node for the cluster state
Section titled “Ask a node for the cluster state”nauka --keys /etc/nauka-keys cluster-metrics --peer 10.0.0.1:7311node 1 — leader: Some(1), applied log: Some(LogId { index: 42, ... }) member 1 @ 10.0.0.1:7311 — capacity 480.0 GB member 2 @ 10.0.0.2:7311 — capacity 480.0 GB member 3 @ 10.0.0.3:7311 — capacity 960.0 GBThree things must be true: a leader is elected (leader: Some(...), not
None), every machine you started appears in the member list, and the
applied log index moves forward between two invocations. A leader: None
that persists is almost always the missing port + 1 UDP rule.
Open the web interface
Section titled “Open the web interface”Point a browser at http://<any-node>:8080. Any node is a complete entry
point — upload, download and listing return the same result everywhere. The
Cluster view shows live membership and per-node fill.
Read the logs
Section titled “Read the logs”journalctl -u nauka -fOn a healthy first startup you should see, in roughly this order:
identity: node-id 5140831482304991211 (fingerprint 47533dcbe0b1a2f4)public IP detected through the DHT: 203.0.113.10 — advertised address 203.0.113.10:7311 (port 7311 and port 7312 must be reachable over UDP)no cluster on the DHT — standing as genesis candidategenesis: candidacy uncontested — cluster foundedHTTP API on http://0.0.0.0:8080web UI served from the binaryseeds published on the DHT: 1 address(es)On the nodes that join afterwards:
cluster discovered on the DHT: [203.0.113.10:7311] — joining…join succeeded — voting member of the clusterThen, on every background pass:
| Log line | What it means |
|---|---|
scrub: X checked, Y healed, Z unrecoverable | Y > 0 is a real repair. A Z that persists means too many nodes are down to rebuild those stripes. |
gc: N shards released | Rebalancing after a membership or capacity change. Only ever released once every current owner has proved it holds its copy. |
audit: 6/6 possessions proved, 0 missing | Sampled proof-of-possession against peers. Persistent missing is an alert; any FAILED is serious. |
peer <id> unreachable | Usually a firewall rule, a dead node, or an --advertise address the others cannot route to. |
Prove it heals
Section titled “Prove it heals”The demonstration is worth running once on a test cluster:
# upload something, note the hashcurl -X POST --data-binary @video.mp4 "http://10.0.0.1:8080/api/upload?name=video.mp4"
# destroy one node's entire shard storessh vps2 'rm -rf /var/lib/nauka/shards'
# wait a scrub interval (30 s by default), then download it anywaycurl -o out.mp4 http://10.0.0.3:8080/f/<hash>sha256sum video.mp4 out.mp4 # identicalThe audit report goes from 6/6 proved to 3/6 proved, 3 missing and back
to 6/6 once the scrubber has regenerated everything from parity.
Growing and shrinking
Section titled “Growing and shrinking”In discovery mode, joining is automatic: start a new node with the cluster key and it becomes a learner, then a voter. Rebalancing follows over the next scrub and GC cycles, and only the shards that must migrate move at all.
To remove a node, drain it first — it keeps serving reads while the others re-replicate its share:
nauka --keys /etc/nauka-keys cluster-remove 5140831482304991211 --peers 10.0.0.1:7311# wait for the scrub reports to settle, then shut the machine downDo not wipe more than m nodes at a time. With 4+2, that means one at a time
on a 3-node cluster, and you should wait for scrub to report a clean pass
between each.
Where to go next
Section titled “Where to go next”- Operations — the full CLI reference, diagnostics, backup and restore, and the known limitations of v1.
- Cluster — how placement, healing, attestation and topology-aware spreading actually work.
- HTTP API — upload, download, deletion, expiry and banning.
- End-to-end encryption — encrypting on the client, so the nodes store bytes they cannot read.