# Deployment How nfsd runs in production. ## Topology ```mermaid flowchart LR C1[NFS client] --> S[nfsd] C2[NFS client] --> S S --> Disk[(export tree)] S --> State[(state dir)] ``` One nfsd process serves one exported directory tree to any number of NFSv4.2 clients over the single TCP port 2049. There is no portmapper, no mountd and no separate locking protocol: a client mounts `nfs://host:2049/` directly and everything rides the one connection or its successors. ## Requirements - a Linux host, on amd64, arm64, loong64 or riscv64; the binary is static, no runtime libraries - port 2049 free; it is not a privileged port, so the service does not need root - the exported directory must exist before start; the service identity needs read access to it, and write access where clients may write - a writable state directory, when persistence is on, writable by the service identity alone (mode 0700) - write access to the export tree requires one of: the service runs as root, or the service holds `CAP_CHOWN` and `CAP_MKNOD`, or the operator accepts the identity behaviour described under Privileges ## Build ```sh just build ``` The binaries land in `bin/nfsd` and `bin/nfs`. ## Run ```sh bin/nfsd -config /etc/nfsd/nfsd.toml ``` The configuration file is described in [CONFIGURATION.md](CONFIGURATION.md); it carries the listen address, the export, the state directory, the connection cap, the TLS key pair and the root squash switch. The flags override the file. ## Service unit ```ini [Unit] Description=NFSv4.2 server for one export After=network-online.target Wants=network-online.target [Service] Type=notify User=nfsd Group=nfsd ExecStart=/usr/local/bin/nfsd -config /etc/nfsd/nfsd.toml StateDirectory=nfsd AmbientCapabilities=CAP_CHOWN CAP_MKNOD CapabilityBoundingSet=CAP_CHOWN CAP_MKNOD NoNewPrivileges=yes ProtectSystem=strict ReadWritePaths=/srv/export Restart=on-failure [Install] WantedBy=multi-user.target ``` `Type=notify` is real readiness: the server writes `READY=1` to `$NOTIFY_SOCKET` once the listener is up, and systemd considers the unit started at that moment, not at the fork. `StateDirectory=nfsd` creates `/var/lib/nfsd` owned by the service identity; point the configuration's `state-dir` at it and handles and opens survive a restart inside the grace window. ### Privileges The port needs no privilege, so the unit runs under a dedicated identity and names exactly two capabilities: - `CAP_CHOWN` lets the server hand a freshly created object to the identity the client presented. Without it the object keeps the service identity; the server still answers the client's claim as the owner attribute, but the on disk owner is the service one. This is a deliberate operator decision, documented here and not hidden: serving untrusted clients without `CAP_CHOWN` changes whose identity new files carry on disk. - `CAP_MKNOD` serves the special objects a CREATE may carry, character and block devices among them. Without it those creations fail. Neither capability lets the service read a file it could not already reach. ### Root squash Serve untrusted clients with `root-squash = true` in the export: a client claiming uid 0 acts as nobody (65534), loses the superuser grant, and its objects carry nobody. The default is `false`, which keeps the trust AUTH_SYS hands to the claim; an operator who controls every client may keep it. ## Production configuration ```toml listen = ":2049" state-dir = "/var/lib/nfsd" max-connections = 256 log-ops = false [tls] cert = "/etc/nfsd/cert.pem" key = "/etc/nfsd/key.pem" [[export]] path = "/srv/export" root-squash = true ``` The TLS key pair enables RPC-with-TLS of RFC 9289; a client that skips the upgrade is refused. The certificate comes from the operator's PKI; no credential belongs in this repository or its configuration examples. ## Firewall One port in, no outbound requirement beyond what the clients reach the back channel on: the server calls the client back on the client's connection, so no inbound port per client is needed. ```sh firewall-cmd --permanent --add-port=2049/tcp && firewall-cmd --reload ``` ## Upgrade ```sh just build install -m 755 bin/nfsd /usr/local/bin/nfsd systemctl restart nfsd ``` With `state-dir` set the restart is a recovery, not a loss: the new process loads the handle map and the opens, and clients inside the grace window reclaim with CLAIM_PREVIOUS. Without it, clients re establish their sessions and re open; their mounted trees keep working through the file handles the backend re registers. ## Rollback Reinstall the previous binary and restart; the state directory format has one version so far, so a downgrade reloads the same state. Not rehearsed against a released predecessor yet: rehearse before relying on it. ## Monitoring - the unit's ready state: `systemctl is-active nfsd` - the operation log, when `log-ops` is on: every operation, its status and its duration on standard error, journald's `journalctl -u nfsd` picks it up - the connection cap answering refusals shows up as clients reconnecting; a steady refusal rate means the cap or the client count is wrong - a healthy idle server logs nothing and holds no CPU: check `systemctl status nfsd` for a flat memory figure and `ss -tnp sport = 2049` for the connected clients