CLIENT DOCUMENTATION

nfsdiag v0.22.0 — the client namespace and project reference. Written in C99 against libtirpc. MIT license.

This page covers nfsdiag client — testing an NFS server from a client machine — plus the project-wide sections: output formats, fixtures, packaging, security, architecture, changelog. Diagnostics that run on the NFS server itself are documented in the server documentation.

CONTENTS

# CLI reference

nfsdiag client [OPTIONS] <server-ip-or-hostname>
nfsdiag server [OPTIONS]
nfsdiag diff <before.json> <after.json>

Deprecated: nfsdiag [OPTIONS] <host> (without a subcommand) still runs the client diagnostics as an alias for nfsdiag client, printing a warning on stderr. The alias will be removed in 1.0.

nfsdiag client

Diagnostic options

flag arg default description
-e, --export PATH — Test only this export path instead of all discovered exports. Repeatable up to 64 times to test a chosen subset.
-o, --mount-options OPTS — Extra mount options passed verbatim to mount(8) (comma-separated, e.g. soft,timeo=30).
--no-mount — — Run only network and RPC checks. Skip all mount operations and filesystem tests. Does not require root.
--dry-run — — Print what would be done. Skips mounts and all filesystem diagnostics. Safe for quick pre-flight checks.
--read-only — — Do not create or write any test files on the export. Leaves no .nfsdiag-* artefacts.
--uid UID — Simulate access as this UID (repeatable for multiple identities). Requires root. Pair with --gid.
--gid GID — GID paired with the preceding --uid. If given without --uid, applies to the current effective UID.
--groups G1,G2 — Comma-separated supplemental GIDs for UID/GID simulation. Max 64 groups. Requires root.
--krb5 — — Check Kerberos prerequisites: valid ticket via klist, running rpc.gssd, Kerberos configuration file.
--udp — — Also probe RPC NULLPROC over UDP in addition to the default TCP probes.
--ipv4-only — — Force IPv4 for direct TCP port checks. Does not affect mount commands.
--ipv6-only — — Force IPv6 for direct TCP port checks. IPv6 literals in mount sources are automatically wrapped in brackets.
--no-nfs4-discovery — — Disable NFSv4 pseudo-root fallback. By default, if mountd is unavailable, nfsdiag tries to discover exports via the NFSv4 root.
--mount-namespace — — Run mounts inside a private mount namespace so they are invisible to other processes. Root runs attempt this automatically unless disabled.
--no-mount-namespace — — Disable automatic private mount namespace use for live mount diagnostics.
--dangerous-fs-tests — — Enable symlink, hardlink, FIFO, and device-node probes. These are intentionally opt-in for production exports. --deep is an alias.
--allow-risky-mount-options — — Permit risky mount options such as exec, suid, and dev, and skip the default nosuid,nodev,noexec mount hardening.
--profile NAME — Apply a preset: quick, safe, full, performance, security, or readonly.
--hosts-file FILE — Read one host per line from FILE and run diagnostics for each. Lines starting with # are comments. Respects --delay-ms between hosts.
--peer HOST[:PORT] 9100 Correlate with a peer nfsdiag server --listen exporter. The client samples the server's metrics before and after its run and prints a paired: verdict locating the bottleneck (server, network, or client).
--watch SEC — Re-run diagnostics every SEC seconds until interrupted with Ctrl-C. Clears terminal between iterations. All mounts are cleaned up on SIGINT.
--on-fail-exec SCRIPT — Execute SCRIPT through a resolved trusted path with a minimal environment (never sh -c) when any test fails. Receives NFSDIAG_HOST, NFSDIAG_LEVEL, NFSDIAG_FAIL_COUNT, NFSDIAG_WARN_COUNT.
--config FILE — Load options from FILE (key=value, one per line) before parsing remaining CLI arguments. CLI flags override the config file.
--parallel N 1 Test up to N exports concurrently (1-32). Each worker mounts, diagnoses, and unmounts its export in a forked child; results are merged into the normal reports.
--sweep — — After the main tests, re-mount the first working export with several rsize/wsize/nconnect combinations, benchmark each, and recommend the best-performing mount options. Also enabled by --profile performance.
--diff-baseline — — Compare this run's summary with the last saved run for the host (stored under $XDG_DATA_HOME/nfsdiag or ~/.local/share/nfsdiag), report regressions, then update the baseline.

Timeout options

flagargdefaultdescription
--timeout SEC 5 Network and RPC connect timeout in seconds. Applied to TCP reachability checks and clnt_create calls.
--command-timeout SEC 30 Timeout for mount and umount command invocations. Range: 1–3600.
--fs-timeout SEC 30 Timeout for each filesystem test group (write/read benchmark, advisory lock, root_squash). Each group runs with its own independent SIGALRM.
--delay-ms MS 0 Milliseconds to sleep between testing each export. Useful as a rate limiter when auditing servers under production load. Max: 600000.

Benchmark options

flagargdefaultdescription
--bench-bytes BYTES 4194304 Size of the read/write benchmark payload in bytes (4 MiB by default). Range 1 byte to 1 GiB; 0 is rejected — use --bench-iterations 0 to skip the benchmark.
--bench-iterations N 10 Number of create/rename/unlink cycles for the metadata latency test. 0 disables the throughput/latency benchmark.
--bench-type TYPE internal Benchmark engine. internal: built-in smoke test. fio: external fio binary (must be installed and in PATH).
--stale-iterations N 100 Number of iterations in the ESTALE probe loop. Higher values increase the chance of catching transient stale handles; 0 disables the probe.

Output options

flagargdefaultdescription
--json[=PATH] PATH — Emit a hierarchical JSON report. Omit PATH or use - to write to stdout (suppresses diagnostic text). Writing to a file keeps stdout active.
--html[=PATH] PATH — Emit a standalone HTML report with inline CSS. Use - for stdout. Suitable for sharing in support tickets or storing as artefacts.
--output-format FMT text Terminal output format. text: tagged lines (default). table: UTF-8 box-drawing summary table. ndjson: streaming newline-delimited JSON per event. prometheus: OpenMetrics text at end of run. junit: JUnit XML for CI pipelines (fail → failure, warn → skipped).
--listen [ADDR:]PORT bind 127.0.0.1 Serve Prometheus metrics over HTTP (responds to any path, e.g. /metrics). Binds 127.0.0.1 unless ADDR is given — the exporter has no authentication, so exposing it (0.0.0.0:9100, [::]:9100) is an explicit decision. Re-runs diagnostics every --watch SEC (default 60) and serves the latest snapshot between runs.
--output-dir DIR — Write a bundle containing JSON, HTML, evidence text, and SHA256 checksums.
--keep-temp — — Do not remove the temporary workspace after tests. Useful for manual inspection of leftover test files or mount state.
-v, --verbose — — Show all diagnostic steps, including low-level OK and info messages. Without this, only warnings and failures are printed (plus the summary line).
-q, --quiet — — Suppress all human stdout (banner, per-check lines, interpretation, and the summary: line) in every output format. Requested reports (--json/--html, --output-format, --output-dir) are still written, including to stdout when the destination is -.
-V, --version — — Print nfsdiag <version> and exit 0.
--self-test — — Validate local dependencies and pure helper checks, then exit (0 on success, 2 on failure). Used by packaging smoke tests.
-h, --help — — Print the help text and exit 0.
Stdout rule: human stdout (banner, per-check lines, summary:) prints only for the text and table formats. Machine formats (ndjson, junit, prometheus) and --json=-/--html=- emit only the structured document. --quiet removes human stdout in every case while still writing any requested report to its destination.

nfsdiag server

The server namespace runs on the NFS server itself and audits its configuration. Its flags and checks are documented in the server documentation.

# what it checks

network TCP reachability for rpcbind (tcp/111) and NFS (tcp/2049). IPv4 and IPv6 with fallback. Optional UDP probe with --udp. Measures TCP connect latency (min/avg/max) and the path MTU towards the server, warning when the MTU is below 1500. DNS failures are reported with the real resolver error.
rpc Full service map from rpcbind: modern rpcbind v3/v4 DUMP (native IPv6, real ports) with legacy portmapper fallback. NULLPROC probes for NFS v2, v3, v4, v4.1, v4.2; mountd v1, v2, v3; lockd/NLM; statd/NSM. Detects which versions are actually registered and responding. Verifies that dynamically registered mountd/lockd/statd TCP ports are reachable through firewalls, and emits a heuristic fingerprint of the server implementation from its service layout.
client daemons Checks for running nfs-client.target, rpc.gssd, and nfs-idmapd via systemd. Degrades gracefully on non-systemd systems. With --krb5: validates Kerberos ticket, gssd, and krb5.conf.
exports Enumerates exports via mountd (tries v3, v2, v1). Falls back to NFSv4 pseudo-root discovery when mountd is unavailable. Respects repeatable --export PATH flags to test a chosen subset, and --parallel N to test several exports concurrently.
mount Attempts to mount each export with version cascade: v4.2 → v4.1 → v4 → v3. Parses effective mount options from /proc/self/mountinfo and verifies they match expected values. Supports private mount namespace isolation. With --krb5, additionally tests which Kerberos security flavors (sec=krb5/krb5i/krb5p) the server actually accepts at mount time.
filesystem Close-to-open consistency check. Read and traverse permission. Directory listing. Create/write/fsync/read round-trip. Advanced I/O: copy_file_range, fallocate, O_DIRECT. Quota and special file detection. Each group runs with its own SIGALRM timeout.
acl POSIX ACL read/write via getxattr. NFSv4 ACL detection. Generic extended attribute support. SELinux security context retrieval.
permissions UID and GID simulation using setuid / setgid / setgroups in child processes with prctl(PR_SET_PDEATHSIG) termination safety. Multiple identities supported via repeated --uid. Practical root_squash detection: verifies that euid 0 is remapped to a non-root UID on the server.
locking Advisory lock test via fcntl(F_SETLK). NLM and NSM service presence. Lock acquisition and release with timeout protection.
performance Internal read/write throughput smoke test (the read pass drops the client page cache first so it reflects the server). Metadata latency (create/rename/unlink loop). External fio backend (opt-in via --bench-type=fio). Deep latency histograms and RPC operation stats from /proc/self/mountstats. RPC retransmission and auth-refresh delta captured before and after tests. With --sweep: benchmarks rsize/wsize/nconnect combinations and recommends the best-performing mount options.
stale handles ESTALE probe loop (configurable iterations). pNFS layout detection via mountstats. NFSoRDMA transport hints. NFS minor version tracked in JSON/HTML output.
long filenames Creates and accesses files with 255-byte names, names containing spaces, colons, at-signs, and UTF-8 multibyte sequences. Reports which the server rejects and with which errno. Essential for Windows/CIFS interop and legacy VFS kernels.
delegations After mounting, opens and reads a probe file, then parses the per-mount op table in /proc/self/mountstats for DELEGRETURN activity. Reports whether NFSv4 delegations were used, or info if none were observed.
server info Reads /proc/fs/nfsfs/servers for the NFS protocol version, active mount count, and server hostname.
reporting Tagged text, UTF-8 box-drawing table, streaming NDJSON, Prometheus/OpenMetrics, and JUnit XML output. Hierarchical JSON with per-export events. Standalone HTML with Content-Security-Policy header and all server-supplied strings HTML-escaped. Embedded HTTP exporter with --listen PORT, and per-host baseline comparison with --diff-baseline.

# usage recipes

full diagnostic — needs root for mount

sudo nfsdiag client 192.168.1.10

verbose — see every probe step

sudo nfsdiag -v 192.168.1.10

network and RPC only — no mount, no root

nfsdiag client --no-mount 192.168.1.10

test a single export path

sudo nfsdiag client --export /data 192.168.1.10

pass extra mount options

sudo nfsdiag client --mount-options soft,timeo=30,retrans=2 192.168.1.10

simulate a specific identity (UID/GID + supplemental groups)

sudo nfsdiag \
  --uid 1000 --gid 1000 \
  --groups 10,20,30 \
  192.168.1.10

simulate multiple identities in one run

sudo nfsdiag \
  --uid 1000 --gid 1000 \
  --uid 2000 --gid 2000 \
  192.168.1.10

check Kerberos prerequisites

sudo nfsdiag client --krb5 kerberos-nfs.example.com

machine-readable report for CI / scripts

nfsdiag client --json=report.json 192.168.1.10

JSON to stdout — pipe to jq

nfsdiag client --json 192.168.1.10 | jq '.exports[].events'

shareable HTML for a support ticket

sudo nfsdiag client --html=report.html 192.168.1.10

quiet — report file only, no terminal output

sudo nfsdiag client --quiet --json=report.json 192.168.1.10

production audit — read-only, dry run, rate-limited

nfsdiag client --dry-run --read-only --delay-ms 500 prod-nfs.example.com

isolate in a private mount namespace

sudo nfsdiag client --mount-namespace 192.168.1.10

larger benchmark payload and fio backend

sudo nfsdiag \
  --bench-bytes 67108864 \
  --bench-type fio \
  192.168.1.10

force IPv6, also probe UDP

nfsdiag client --ipv6-only --udp 2001:db8::1

keep temp workspace for post-mortem inspection

sudo nfsdiag client --keep-temp 192.168.1.10

continuous monitoring — re-run every 60 s

sudo nfsdiag client --watch 60 192.168.1.10

audit a fleet of servers from a file

sudo nfsdiag \
  --hosts-file /etc/nfs-servers.txt \
  --delay-ms 200 \
  --json=audit.json

stream events — filter only failures with jq

sudo nfsdiag client --output-format=ndjson 192.168.1.10 \
  | jq 'select(.level=="fail")'

Prometheus probe output

sudo nfsdiag client --output-format=prometheus 192.168.1.10

alert via PagerDuty / Slack on failure

sudo nfsdiag \
  --on-fail-exec /usr/local/bin/alert.sh \
  192.168.1.10

run from a container — no install required

docker run --rm --privileged \
  ghcr.io/lsferreira42/nfsdiag client 192.168.1.10

# output formats

Text (default)

Compact on TTY. Warnings and failures always shown. Informational and low-level OK messages only shown with --verbose. Coloured output when stdout is a terminal.

nfsdiag 0.22.0: 192.168.0.21
[OK]   1 export(s) discovered
[WARN] root_squash not detected on /data (uid 0 remains 0)
summary: ok=12 warn=1 fail=0

Table

UTF-8 box-drawing summary table printed at end of run. One row per export with columns: path, NFS version, write MiB/s, read MiB/s, metadata p95 ms, lock status, overall status.

sudo nfsdiag client --output-format=table 192.168.1.10

NDJSON (streaming)

One JSON object per event line, emitted as events are generated. Enables real-time filtering with jq and integration with log pipelines (Loki, Vector, etc.). Events include check_id, category, severity and remediation text.

sudo nfsdiag client --output-format=ndjson 192.168.1.10 \
  | jq 'select(.level=="fail")'

Prometheus / OpenMetrics

Emitted at end of run. Per-export metrics: write/read throughput, metadata latency p95, lock result, delegation state, and per-check pass/fail gauge. Suitable for blackbox_exporter or batch-job scraping.

sudo nfsdiag client --output-format=prometheus 192.168.1.10

JSON

Hierarchical structure: top-level metadata, per-export events (linked by export_idx), per-export NFS version, and a recommendations array. Each report carries schema_version (2.0), timestamp_iso8601, duration_sec, per-run deterministic check_id values, event categories, severity, and remediation text. The output is described by a published JSON Schema (docs/nfsdiag.schema.json) and follows a documented 1.x compatibility policy.

# JSON to file — diagnostic text still on stdout
nfsdiag client --json=report.json 192.168.1.10

# JSON to stdout — diagnostic text suppressed
nfsdiag client --json 192.168.1.10
nfsdiag client --json=- 192.168.1.10

# pipe to jq
nfsdiag client --json 192.168.1.10 | jq '.exports[0].nfs_version'

Evidence bundle

--output-dir writes JSON, HTML, evidence text and SHA256 checksums in one directory. Two JSON reports can be compared with nfsdiag diff.

sudo nfsdiag client --output-dir ./nfsdiag-report 192.168.1.10
nfsdiag diff before.json after.json

HTML

Standalone report with inline CSS — no external resources. Open in any browser. Safe to email or attach to issue trackers. Includes a Content-Security-Policy header; all server-supplied strings are HTML-escaped.

# HTML to file
sudo nfsdiag client --html=report.html 192.168.1.10

# HTML to stdout (suppresses diagnostic text)
sudo nfsdiag client --html=- 192.168.1.10 > report.html

# quiet: HTML file only, no terminal output
sudo nfsdiag client --quiet --html=report.html 192.168.1.10

# exit codes

0All checks passed — no warnings, no failures.
1At least one warning or failure was detected. In automation, warnings return 1 because they usually need attention.
2Usage error or local runtime failure (bad argument, missing dependency, initialization error).

# docker fixtures

14 Docker fixtures reproduce known-bad NFS situations end-to-end. Each spins up a container with a specific failure scenario, runs nfsdiag against it, and validates the output. Regressions in the diagnostic logic show up as test failures.

Warning: Fixture configurations use wildcard clients, insecure, and no_root_squash intentionally to simulate test scenarios. These settings are test-only and must never be used in production.
make test-fixtures                     # run all 14 fixtures
make test-fixture-root-squash          # run a single fixture
make test-fixture-stale-handle         # run another single fixture
make docker-build-all                  # build all images only
make docker-build-slow-performance     # build one image
make test-fixtures-list                # list available fixtures

Some fixtures require root because they perform real NFS mounts from the host. The test runner skips mount-dependent cases gracefully if the host cannot run kernel NFS inside Docker.

# packaging

Pre-built .deb, .rpm, and .apk packages are published automatically for every release: github.com/lsferreira42/nfsdiag/releases/latest ↗

Build packages from source

make deb       # Debian/Ubuntu .deb (requires dpkg-deb)
make rpm       # Fedora/RHEL .rpm (requires rpm-build)
make apk       # Alpine .apk (requires Docker)
make packages  # all three formats at once

Built packages are placed in build/.

Packaging templates live under packaging/: OCI Dockerfile, Homebrew formula, AUR PKGBUILD, and Nix flake. Release artifacts include checksums, SBOM and provenance metadata.

OCI image

A multi-stage Docker image is published to ghcr.io/lsferreira42/nfsdiag on every release as :latest and :vX.Y.Z.

docker run --rm --privileged \
  ghcr.io/lsferreira42/nfsdiag client 192.168.1.10

Install options

sudo make install                  # binary, man page and shell completions to /usr/local
make PREFIX=/opt install           # custom prefix
sudo make uninstall                # remove all installed files

Version bumping

The version lives in VERSION and is mirrored atomically into src/nfsdiag.h and all packaging files by the bump targets.

make bump-version-bugfix   # 0.5.0 → 0.5.1
make bump-version-minor    # 0.5.0 → 0.6.0
make bump-version-major    # 0.5.0 → 1.0.0

GitHub release

make release validates that the working tree is clean and that VERSION matches src/nfsdiag.h, then creates and pushes the annotated v$VERSION tag. It does not build or upload artifacts itself — the tag push triggers the release workflow, which is the single builder/publisher. Run make release-check first to run the build, unit tests, version/schema checks, cppcheck and shellcheck.

make release-check   # gate: build + tests + lint + version/schema
make release         # tag v$VERSION and push (workflow publishes)

The GitHub Actions release workflow runs on every v* tag push. It builds DEB on Ubuntu, RPM in a Fedora container, and APK via Docker, then attaches all three packages to the release. After the release exists, run make update-release-checksums to refresh the Homebrew and AUR source checksums and commit the result.

Manual compile

gcc -O2 -Wall -Wextra -D_GNU_SOURCE -I/usr/include/tirpc \
    src/main.c src/mount.c src/network.c src/report.c \
    src/rpc.c src/stats.c src/tests.c src/validation.c src/util.c \
    -ltirpc -o nfsdiag

# security

nfsdiag is designed to be safe when run as root, including against adversarial NFS servers.

file ops Report files opened with O_WRONLY|O_CREAT|O_TRUNC|O_NOFOLLOW|0600 — prevents symlink attacks when running as root. Test files use hidden .nfsdiag-* names inside a mktemp-created workspace.
processes Child processes resolve helper commands from trusted system directories and execute them with execve, an argv[] array, and a minimal environment — no sh -c with dynamic strings. prctl(PR_SET_PDEATHSIG) + setpgid() prevent orphan children. Inherited file descriptors closed in child processes.
input validation Hostnames, export paths, and mount options are validated before network or mount activity. Exports are mounted with nosuid,nodev,noexec by default; risky mount options and disabling that hardening require --allow-risky-mount-options. Symlink, hardlink, FIFO, and device-node probes require --dangerous-fs-tests. --config files and --on-fail-exec scripts are refused when not owned by root/current user or when group/world-writable. Output captured from external commands is sanitised for terminal escape sequences before display.
XDR / RPC XDR string sizes bounded: export paths at 4096 bytes, group names at 256 bytes. Export and group lists are decoded iteratively (no recursion, so no stack-overflow exposure) with node-count budgets (MAX_XDR_EXPORT_NODES, MAX_XDR_GROUP_NODES) bounding the memory a malicious server can force. All XDR strings are sanitised for control characters before display or HTML inclusion.
random paths Test file paths include cryptographically random bytes from getrandom(). Prevents an attacker in a shared export from pre-creating a symlink at the predicted test path to subvert I/O or permission tests.
capabilities Child processes that simulate UID/GID clear all ambient capabilities via prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL) before calling setuid(), ensuring elevated capabilities are not silently retained.
TMPDIR The TMPDIR environment variable is validated before use. The created workspace is checked with lstat(), must be owned by the current user, and cleanup refuses paths outside the workspace prefix.
HTML output HTML reports include a Content-Security-Policy meta header. opt.host and all server-supplied strings (export paths, group names) are passed through html_escape() at every insertion point, preventing stored XSS if the report is opened in a browser.
memory strdup() and calloc() return values always checked. NULL on OOM is never stored silently.
timeouts SIGALRM-based timeouts protect both filesystem operations and RPC calls (clnt_create, pmap_getmaps). Signal handlers installed without SA_RESTART for responsive interruption.
cleanup atexit(cleanup_all) + SIGINT/SIGTERM handlers always unmount and remove the temp workspace, even on crash or signal. Active mountpoints tracked globally so no mount leaks on unexpected exit.
Production use: by default nfsdiag creates hidden .nfsdiag-* files to test write/read behaviour. Use --read-only to leave the export untouched, or --dry-run to skip mounts entirely. UID/GID simulation requires root because it uses setgid, setgroups, and setuid in child processes.

# architecture

~3,000 lines of C99. Split into 7 focused modules plus a shared header. No runtime dependencies beyond libtirpc.

Modules

nfsdiag.h All shared types, constants, and extern declarations. Single source of truth for defaults and limits.
main.c CLI parsing (getopt_long), orchestration loop, signal and atexit handlers.
network.c TCP port reachability checks for IPv4 and IPv6.
rpc.c RPC service discovery via rpcbind, NULLPROC version probing, export enumeration via mountd.
mount.c Mount/unmount execution, command output capture via poll(), mount namespace setup.
tests.c All filesystem-level diagnostics after mount: I/O, ACLs, locks, identity simulation, benchmarks.
report.c Event and recommendation accumulation, JSON and HTML report generation.
stats.c Client daemon checks, /proc/net/rpc/nfs stats, /proc/self/mountstats parsing.

Execution flow

CLI args → network tests → RPC discovery → export enumeration
  → [for each export] mount (v4.2→4.1→4→3) → filesystem tests → unmount
  → RPC stats delta → text/JSON/HTML report → cleanup

Key constants

constantvalue
NFSDIAG_VERSION"0.22.0"
MAX_XDR_EXPORT_NODES2048
MAX_XDR_GROUP_NODES8192
MAX_HOSTS256
RPCBIND_PORT111
NFS_PORT2049
MAX_EXPORTS512
MAX_EVENTS4096
MAX_RECOMMENDATIONS128
MAX_MOUNTPOINTS128
MAX_IDENTITIES32
MAX_SUPP_GROUPS64
MAX_XDR_EXPORT_PATH4096
MAX_XDR_GROUP_NAME256
CMD_OUTPUT_LIMIT8192
DEFAULT_TIMEOUT_SEC5
DEFAULT_COMMAND_TIMEOUT_SEC30
DEFAULT_FS_TIMEOUT_SEC30
DEFAULT_BENCH_BYTES4 MiB
DEFAULT_BENCH_ITERATIONS10
DEFAULT_STALE_ITERATIONS100

# limitations

NFS problems are environment-dependent. Results can differ based on firewall rules, server export options, NFS version negotiated, kernel client state, UID/GID mapping, root squash, ACLs, SELinux/AppArmor on the server, and server load.

Things the client-side tool cannot guarantee:

Use nfsdiag as a fast diagnostic helper, not as the sole source of truth. Server-side analysis (NFS server logs, exportfs -v, nfsstat -s) remains essential.

# see also

nfsstat(8)· showmount(8)· rpcinfo(8)· mount.nfs(8)· exports(5)· nfs(5)· idmapd(8)· rpc.gssd(8)· rpc.idmapd(8)· mountstats(8)· nfsmount.conf(5)· exportfs(8)· fio(1)

# changelog

v0.15.0

Security pass for the server namespace: exports hardening analysis, identity mapping, Kerberos, ACLs, and a practical root_squash test.

v0.14.0

Foundation of the server namespace: five local checks, structured output, and offline diagnosis of copied filesystem trees.

v0.13.0

Subcommand namespaces and the first server-side check: the CLI splits into client and server, opening the road to full server-side NFS diagnostics.

v0.12.0

Correctness, hardening, and performance pass: accurate latency reporting, a safer embedded exporter, symlink-resistant file reads, and lower memory use.

v0.11.0

Road-to-1.0 hardening: safer diagnostics, a published JSON schema and stability policy, real-parser fuzzing, and a much wider CI and packaging gate.

v0.10.0

Pre-1.0 hardening release: governance files, packaging completeness, release-pipeline automation, machine-output correctness, and a much larger CI gate.

v0.9.0

Feature release: parallel export testing, mount option sweep, Kerberos flavor probing, JUnit output, embedded Prometheus exporter, baseline comparison, deeper network diagnostics, server fingerprinting, and native IPv6 rpcbind — on top of the security hardening and correctness fixes below.

v0.8.0

Skipped — no release carries this version number.

v0.7.0

Maintenance release: fixture, wording, and robustness fixes. No new features.

v0.6.1

Release tooling: make release now publishes every artifact it can build, not just the packages.

v0.6.0

Correctness release: fixes to delegation and server-info detection, per-export status, Prometheus output, and filesystem-test timeouts.

v0.5.0

Major feature release: new output modes, multi-host support, watch mode, on-fail hooks, config file, new filesystem checks, OCI image, shell completions, man page, and security hardening.

v0.4.1

Website and CI/CD infrastructure. No changes to diagnostic behaviour.

v0.3.0

Packaging and release infrastructure. No breaking changes to diagnostic behaviour.

v0.2.0

Security fixes, behavioural fixes, and reliability improvements.

Security & crash fixes

Behavioural fixes

Reliability fixes

Other

v0.1.0

Initial public release.