Skip to content

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.

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.

Terminal window
nauka token
# nauka1_yHc20GCphnJVTMJN8rib2DxUQuoDws13A_0gHnxOPA8

The 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).

Terminal window
nauka keygen --out ./nauka-keys # refuses to overwrite
# copy to each machine, then on every node:
nauka --keys /etc/nauka-keys serve --advertise <ip>:7311

Same trust model, two spellings. Either way:

PortProtocolWhatExposure
7311UDPQUIC data plane (--listen)open between nodes and to clients of the native protocol
7312UDPconsensus plane (always listen + 1)open between nodes
8080TCPHTTP API (--http)public; reads and writes are space-scoped (signed links, space keys)
443TCPHTTPS API (only with NAUKA_HTTPS_DOMAIN)public; same router as 8080, TLS terminated by the node itself
53UDP+TCPauthoritative geo-DNS (on by default)public if you delegate a name; binds the advertised IP only
9100TCPPrometheus (--metrics)loopback by default; widen only onto a private scrape network
8333TCPS3 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.

nauka init (and node add, for the machines it provisions) installs the same layout the .deb/.rpm packages use:

PathContents
/etc/nauka/nauka.envNAUKA_TOKEN=… and NAUKA_ARGS=--advertise <ip>:7311 [--join] — the one file to edit, then systemctl restart nauka
/var/lib/naukashards, manifests, Raft log, node.key — owned by the dedicated nauka user
/etc/systemd/system/nauka.servicehardened 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).

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.

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:

Terminal window
nauka --data-dir ./n1 serve --listen 0.0.0.0:7311 --http 0.0.0.0:8081
nauka --data-dir ./n2 serve --listen 0.0.0.0:7313 --http 0.0.0.0:8082

7311, 7313, 7315 — never 7311, 7312, 7313, where the second node would collide with the first one’s consensus port.

With the default 4+2 profile every stripe becomes 6 shards that survive losing any 2. What that means in machines:

NodesShards per nodeSurvives
16disk corruption only (2 shards/stripe)
23corruption; not a node loss (3 > 2) — but healing + upload spillover have kept real files alive through single-node wipes
32one node down or destroyed — the sweet spot to start
6+1any 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.

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.

Terminal window
# on each node, one at a time:
nauka update && systemctl restart nauka

Checksum-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.

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.

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 setup
NAUKA_HTTPS_DOMAIN=cdn.example.com

The 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.