Skip to content
Pages in Guides

Run a central

What a central is and what it cannot do, installing one on a NAS, a Docker host or a VPS, its first accounts and its certificate.

Guide Updated View as Markdown

A central is a Noust whose job is to manage your other servers. It opens an SSH tunnel to each of them and drives them through their own API, from one console. It can be:

  • a container on a NAS or any Docker host (the recommended home for it: it only dials out, so nothing is opened on your router), or
  • a VPS, either one that only manages the fleet or a server that also deploys its own applications.

A central in a container is a hub (central.role = hub): it deploys nothing itself. Applications, sites, certificates, databases and the rest are refused there with a message, and the console hides them. Everything else (the console, tokens, two-factor, the fleet) works as on any Noust.

The console's own fleet pages (a central's, never a plain server's: All servers in the scope selector) show every server it manages side by side - whether it answers, what it runs, what needs attention on each - and the selector switches the whole console to one of them; see console.md for its pages.

Fleet

What the central cannot do matters as much. It reaches each server as noust-tunnel, an account noust fleet authorize creates for it there: a system account with no home, nologin as its shell and * as its password (no password ever matches it). Its keys live in a file of root's, /etc/ssh/noust/noust-tunnel.keys, which it cannot edit, and sshd itself restricts it with a block Noust writes to /etc/ssh/sshd_config.d/00-noust-tunnel.conf (or, when sshd includes no such directory, at the end of /etc/ssh/sshd_config, between marker lines):

Match User noust-tunnel
    AuthorizedKeysFile /etc/ssh/noust/%u.keys
    PubkeyAuthentication yes
    AuthenticationMethods publickey
    DisableForwarding no
    AllowTcpForwarding local
    AllowStreamLocalForwarding no
    PermitOpen 127.0.0.1:<console port>
    PermitListen none
    GatewayPorts no
    PermitTunnel no
    PermitTTY no
    X11Forwarding no
    AllowAgentForwarding no
    PermitUserRC no
    ForceCommand /usr/bin/false

The key line says the same thing again, so a line copied without its options is still contained:

restrict,port-forwarding,permitopen="127.0.0.1:<console port>",permitlisten="127.0.0.1:1",command="/usr/bin/false" ssh-ed25519 AAAA... noust-central:<central>

The central's key can open ssh -L to the console's port on loopback and nothing else: no terminal, no command, no agent, no remote forward, no Unix-socket forward, no other port. Before sshd is reloaded the change is checked with sshd -t, and afterwards sshd -T -C user=noust-tunnel,... must show every one of these settings in force; if another file or Match block wins, the change is put back and authorize says which setting differs. This works on servers with PermitRootLogin no, and even with DisableForwarding yes for everyone else. It needs OpenSSH 7.8 or later on the server (Debian 12, Ubuntu 22.04 and later, Fedora and openSUSE Leap 15 all ship newer).

Servers enrolled by 3.0. Up to 3.0 the key went into root's authorized_keys, and this page said it could only forward one port. That was not true: permitlisten limits TCP listeners only, so the same key could ask for a remote forward to a path (ssh -R /etc/nologin:...) and sshd created that Unix socket as root, anywhere - including /etc/nologin, which stops every other account from logging in. Move each such server to the tunnel account (see Moving a 3.0 server to the tunnel account). noust node list flags the ones still reached as root.

The central never gets a shell on a server, each server decides how far the central may go there (the access ceiling), and each server can revoke it on its own.

The central's tunnels also pin their algorithms: ed25519 host keys only (the one the join code carries), sntrup761x25519-sha512@openssh.com (post-quantum hybrid) or curve25519-sha256 for the key exchange, chacha20-poly1305@openssh.com or aes256-gcm@openssh.com as ciphers, and encrypt-then-MAC MACs only. The central asks its own ssh -Q once and drops what an older client lacks.

The rest of this guide is on pages of its own:

Install on a UGREEN NAS (UGOS Pro)

  1. Open App Center and install Docker if it is not there.
  2. Open Docker > Project > Create. Name it noust, keep the suggested path, and paste packaging/container/compose.yaml as the compose file. Press Deploy.
  3. Open Container > noust > Log and copy the line Access Token: noust_.... It is shown on the first start only.
  4. Browse to https://<the NAS address>:8443. The certificate is self-signed, so the browser warns once; the log shows its SHA-256 fingerprint to compare with the one the browser shows. Paste the token.

The same with SSH on the NAS, in a folder holding compose.yaml:

Shell
sudo docker compose up -d
sudo docker logs noust          # the token and the certificate's fingerprint

To keep the data in a folder of the NAS instead of a Docker volume (easier to back up with the NAS's own tools), create it for the container's user first and switch the volumes: line in compose.yaml:

Shell
sudo mkdir -p /volume1/docker/noust
sudo chown 10001:10001 /volume1/docker/noust

Install on any Docker host

Shell
docker volume create noust-data
docker run -d --name noust --restart unless-stopped \
  -p 8443:8443 -v noust-data:/data \
  --read-only --tmpfs /tmp:size=16m --cap-drop ALL \
  --security-opt no-new-privileges:true \
  ghcr.io/perkybeet/noust:latest
docker logs noust

The image runs as uid 10001 with a read-only root filesystem and no capabilities; everything it keeps is under /data.

Variable Default What it does
NOUST_ALLOW_IP loopback, 10/8, 172.16/12, 192.168/16, fc00::/7 Who may open the console, space- or comma-separated. Wins over web.ip_whitelist.
NOUST_TLS_CERT, NOUST_TLS_KEY a self-signed pair minted at the first start Your own certificate, both on the volume. The recommended way: see The console's certificate.
NOUST_TLS_NAMES none Names and addresses to add to the self-signed certificate, space- or comma-separated (central.example.com 192.168.1.50): a container only sees its own addresses, not the NAS's.
NOUST_DATA_DIR /data Where everything lives.
NOUST_CENTRAL_ROLE hub Set by the image; a container cannot deploy.

On a VPS

On a VPS, the central is a normal Noust installed from the packages; there is no container and no noust central run.

  • A VPS that also deploys applications stays central.role = server (the default). It manages the fleet from the same console it already has: nothing to change.

  • A VPS that only manages the fleet: install Noust, then

    Shell
    noust config set central.role hub
    noust web enable --host 0.0.0.0 --port 8443 --self-signed --allow-ip <your address or VPN range>

    or keep the console on loopback behind your own reverse proxy (noust web enable and --trusted-proxy). A central reachable from the internet is the one thing to avoid: prefer a VPN or an SSH tunnel to it (ssh -L 8443:127.0.0.1:8443 root@central). A hub has no nginx sites of its own, so noust console expose (a name, a certificate and an allow-list through Noust's nginx, docs/console.md) is for a central that also deploys, central.role = server; a hub serves its own TLS, as above.

noust central run works on a VPS too (as root, or with NOUST_DATA_DIR pointing at a directory its user owns), but noust web enable is the supported way to run a console as a service there.

First sign-in and accounts

  1. Sign in with the token from the first start. A central with no accounts opens Create the first account: an administrator, and (recommended) a security officer for another person. Or from the command line: docker exec -it noust noust user create alice --role admin.
  2. Sign in as that account and enrol its second factor, an authenticator app or a passkey. Every account must have one, and a central refuses to add its first server without it. From then on the access token is emergency access only.
  3. Lost the token? docker exec -it noust noust web token --new issues another and retires the old one.

The central's accounts are the ones that matter for the fleet: operators reach every server through the central, and each server receives the operator's name with every call (X-Noust-Actor) and what that person may do (X-Noust-Actor-Permissions; roles are editable, so a role's name does not mean the same on two servers). The server grants those permissions within its ceiling for the central (below), and never what no central may have: its accounts, security settings and audit trail. A server before 4.1 reads the name of the widest role of 3.1 the person holds all of (X-Noust-Actor-Role) and decides by its own table. A server's own accounts, tokens and second factor are never the central's to change.

docker exec -it noust noust central status shows the role, where the data is, the certificate's fingerprint, who may connect, whether the secrets are sealed, how many servers there are and whether two-factor is on.

The console's certificate

The central holds the keys of the whole fleet, so its console is served over TLS with a certificate your browser really trusts. The way to get that is your own: a certificate from your organisation's CA, or from Let's Encrypt for a name of yours, with NOUST_TLS_CERT and NOUST_TLS_KEY (or --tls-cert/--tls-key outside the container). Chrome and Firefox also refuse passkeys on a certificate they do not trust, so this is the setup they need.

Without one the first start mints a self-signed certificate: ECDSA P-256, valid 825 days, with subjectAltName for the host name, the machine's FQDN, localhost, the addresses of its interfaces and whatever NOUST_TLS_NAMES adds (every current browser ignores the common name, so a certificate without names could not be trusted by any means). Its fingerprint and expiry are in noust central status and in the first lines of docker logs. A pair from an earlier version (RSA, no names) is replaced once, on the first start of 3.1, and the browser asks about the new fingerprint once. A self-signed certificate that expires is replaced at the next start.

The console accepts TLS 1.2 at the least, and of the TLS 1.2 suites only ECDHE key exchange with AES-GCM or ChaCha20-Poly1305. web.allowed_hosts (a list of names, *.example.com allowed) makes it answer only the names you give, plus loopback and the host of web.public_url; empty answers any host. Neither a NAS in a cupboard nor a container on a home network is where a company's central should live: put it on the company's infrastructure, in a management network, with NOUST_ALLOW_IP naming who may open it.

Something on this page is wrong or unclear? Report it on GitHub.

Help, contributing and the project’s policies are under Project.

Search the documentation

Type to search every page.

↑ ↓ to move, Enter to open, Esc to close