NFS (Network File System) is the protocol Unix and Linux machines use to mount a directory that lives on another server and use it as if it were a local disk. It runs on ONC RPC, normally over TCP port 2049. NFSv3 trusts the client to say which user is asking; NFSv4 adds a stateful protocol, built-in locking and mandatory support for Kerberos, which can authenticate (krb5), integrity protect (krb5i) or encrypt (krb5p) every request.
That split is the whole security story. Most NFS servers still default to AUTH_SYS, where the client states a numeric user ID and the server believes it, in cleartext. Strong options exist: RPCSEC_GSS with Kerberos since 1997 and RPC-with-TLS since September 2022. This post covers the wire protocol, every version, each security flavour, the weakness classes as concepts, and the commands an administrator uses to check their own exports and mounts. Protocol facts come from the RFCs on rfc-editor.org; Linux configuration comes from the nfs-utils manual pages and the Linux kernel documentation.
Key takeaways
- NFS is an ONC RPC program (program number 100003). Every request carries an RPC header with a credential, and the credential flavour decides how much security you get. RFC 5531 states plainly that AUTH_SYS "is known to be insecure" and should not be used for services that let clients modify data.
- NFSv3 (RFC 1813, June 1995) is a stateless protocol with separate side protocols for mounting (MOUNT, program 100005), locking (NLM) and status monitoring. Those side protocols use no authentication.
- NFSv4.0 (RFC 7530, March 2015, which obsoletes RFC 3530) folds mounting and locking into one protocol on port 2049, adds the COMPOUND procedure, ACLs and delegations, and makes Kerberos V5 through RPCSEC_GSS mandatory to implement. Mandatory to implement is not mandatory to use.
- NFSv4.1 (RFC 8881, August 2020, obsoleting RFC 5661) adds sessions, callbacks over the client's own connection and optional pNFS. NFSv4.2 (RFC 7862, November 2016) adds server side copy, sparse files and Labeled NFS.
- The Kerberos flavours map to three RPCSEC_GSS services: krb5 authenticates each request, krb5i adds an integrity check over each message, krb5p also encrypts arguments and results. Only krb5p hides file contents.
- RPC-with-TLS (RFC 9289) wraps RPC in TLS 1.3 on the same port, enforced on Linux with
xprtsec=tlsorxprtsec=mtls. Without client certificates it encrypts traffic but still lets a client claim any user ID under AUTH_SYS. - On Linux,
root_squashis the default and maps only UID 0. It does nothing for any other user ID a client chooses to send. Exports to*or to wide subnets,no_root_squash,insecureand subdirectory exports withoutsubtree_checkare the usual configuration gaps.
What NFS is for: the layer and the standards
NFS solves one problem: give many client machines shared access to files that live on one server, with normal file semantics (open, read, write, lock, rename, permissions) instead of a copy and upload step. The client kernel mounts the remote export into its own directory tree, and applications use ordinary system calls without knowing the data is remote. The Linux nfs(5) manual page describes it as "an Internet Standard protocol created by Sun Microsystems in 1984", developed for file sharing on a local area network.
NFS is an application layer protocol, but it does not talk to the transport directly. It is a program that runs on ONC RPC (Open Network Computing Remote Procedure Call, also called Sun RPC), which in turn encodes every argument and result with XDR, the External Data Representation. ONC RPC runs over TCP, UDP or RDMA. That layering explains most of NFS security: authentication, integrity and encryption all live in the RPC layer, not in NFS itself. RFC 8881 says it directly: "NFS has always depended on RPC for authentication."
The defining documents
| Document | Title | Date | Status | Notes |
|---|---|---|---|---|
| RFC 1094 | NFS: Network File System Protocol Specification (NFSv2) | March 1989 | Informational | Written by Sun; the original public description |
| RFC 1813 | NFS Version 3 Protocol Specification | June 1995 | Informational | The IESG note says the IETF is "not involved in creating or maintaining this specification" |
| RFC 7530 | Network File System (NFS) Version 4 Protocol | March 2015 | Proposed Standard | NFSv4.0; obsoletes RFC 3530 (April 2003), which obsoleted RFC 3010 (December 2000); updated by RFC 7931 and RFC 8587 |
| RFC 8881 | NFS Version 4 Minor Version 1 Protocol | August 2020 | Proposed Standard | NFSv4.1; obsoletes RFC 5661 (January 2010) |
| RFC 7862 | NFS Version 4 Minor Version 2 Protocol | November 2016 | Proposed Standard | NFSv4.2; updated by RFC 8178 |
| RFC 5531 | RPC: Remote Procedure Call Protocol Specification Version 2 | May 2009 | Draft Standard | ONC RPC; obsoletes RFC 1831; updated by RFC 9289 |
| RFC 2203 | RPCSEC_GSS Protocol Specification | September 1997 | Proposed Standard | The GSS-API security flavour for RPC; updated by RFC 5403 (RPCSEC_GSS version 2, February 2009) |
| RFC 9289 | Towards Remote Procedure Call Encryption by Default | September 2022 | Proposed Standard | RPC-with-TLS; updates RFC 5531 |
Two points from this table matter in practice. NFSv3 was never an IETF standards track protocol; it is Sun's informational description of what it shipped. And RFC 8881 says NFSv4.1 "has no dependencies on NFS version 4 minor version 0, and is considered a separate protocol", which is why servers can enable and disable 4.0, 4.1 and 4.2 independently.
Ports and IANA registrations
The IANA service name and port number registry lists these entries:
| Service | Port | Transport | Registered for |
|---|---|---|---|
nfs | 2049 | TCP, UDP, SCTP (SCTP per RFC 5665) | Network File System |
sunrpc | 111 | TCP, UDP | Sun Remote Procedure Call (rpcbind, the portmapper) |
mountd | 20048 | TCP, UDP | NFS mount protocol, registered 9 August 2010 |
nfsrdma | 20049 | TCP | NFS over RDMA, entry updated 5 September 2017 with reference RFC 8267 |
The registry also flags a historical conflict on 2049 with another service name. RFC 7530 says the registered port 2049 "SHOULD be the default configuration" and that using it means "the NFS client will not need to use the RPC binding protocols", which lets NFSv4 cross firewalls with a single port. NFSv3 is harder to firewall: nfs(5) notes that while NFS usually uses 2049, "auxiliary services such as the NLM service can choose any unused port number at random", and clients find them through rpcbind on port 111.
How NFS works on the wire
The ONC RPC envelope
Every NFS request is an RPC call message, and every response is an RPC reply. RFC 5531 defines the structure in XDR. A call carries a transaction ID, the RPC version (always 2), the program number, the program version, the procedure number, a credential and a verifier, then the procedure's arguments.
| Field | Size | Meaning |
|---|---|---|
| Record mark (TCP only) | 4 bytes | Top bit marks the last fragment; the other 31 bits give the fragment length |
xid | 4 bytes | Transaction ID that matches a reply to its call |
mtype | 4 bytes | 0 for CALL, 1 for REPLY |
rpcvers | 4 bytes | Must be 2 |
prog | 4 bytes | 100003 for NFS, 100005 for MOUNT, 100021 for NLM |
vers | 4 bytes | 3 or 4 for NFS |
proc | 4 bytes | Procedure number; NFSv4 has only NULL (0) and COMPOUND (1) |
cred | flavour plus up to 400 bytes | Authentication credential |
verf | flavour plus up to 400 bytes | Verifier that validates the credential |
| Arguments | variable | XDR encoded procedure arguments |
The credential flavour numbers come from RFC 5531 and RFC 9289: AUTH_NONE is 0, AUTH_SYS is 1, AUTH_SHORT is 2, AUTH_DH is 3, RPCSEC_GSS is 6, and AUTH_TLS is 7. An AUTH_SYS credential is short and revealing. RFC 5531 Appendix A defines it as a stamp, the client machine name, the caller's effective user ID, effective group ID, and up to 16 supplementary group IDs, with an AUTH_NONE verifier. There is nothing in it that a server can check.
An NFSv3 mount and read
NFSv3 spreads one logical job across several RPC programs.
Client Server
| |
| rpcbind (port 111): where is MOUNT v3 on TCP? |
|---------------------------------------------------------->|
|<----------------------------------------- port number ----|
| MOUNT MNT "/export/data" (program 100005) |
|---------------------------------------------------------->|
|<------- root filehandle + list of accepted auth flavours -|
| NFS FSINFO, GETATTR (program 100003, port 2049) |
|---------------------------------------------------------->|
| NFS LOOKUP dir_fh "report.csv" cred: AUTH_SYS uid=1001 |
|---------------------------------------------------------->|
|<-------------------------------- file_fh + attributes ----|
| NFS ACCESS / READ file_fh offset count |
|---------------------------------------------------------->|
|<-------------------------------------------- data, eof ---|
| NLM LOCK (program 100021, port found via rpcbind) |
|---------------------------------------------------------->|
| NFS WRITE (UNSTABLE) ... then COMMIT |
|---------------------------------------------------------->|
| MOUNT UMNT |
|---------------------------------------------------------->|
The MOUNT reply (mountres3_ok in RFC 1813) returns the root filehandle and a list of authentication flavours the server accepts for that export. RFC 7530 notes that for NFSv3 "the security negotiation is done via the MOUNT protocol", which is itself unprotected. After that, everything is addressed by filehandle: an opaque value of up to 64 bytes (NFS3_FHSIZE) that the server creates and the client hands back on every call. The NFSv3 server keeps no per-client state for reads and writes, which RFC 1813 presents as a feature for crash recovery.
An NFSv4.1 mount, session and read
NFSv4 replaces the separate programs with two procedures, NULL and COMPOUND. A COMPOUND carries a list of operations that the server runs in order until one fails. RFC 7530 gives the example of a client that can "read data from a file in one request by combining LOOKUP, OPEN, and READ operations in a single COMPOUND RPC". NFSv4.1 adds a session in front of everything else.
Client Server
| |
| TCP connect to port 2049 |
| [optional] NULL with AUTH_TLS (RFC 9289 probe) |
|---------------------------------------------------------->|
|<------------------------- verifier "STARTTLS" ------------|
| [optional] TLS 1.3 handshake on the same connection |
|<=========================================================>|
| [if Kerberos] RPCSEC_GSS_INIT with Kerberos AP-REQ token |
|---------------------------------------------------------->|
|<----------------------- GSS context handle + seq_window --|
| COMPOUND: EXCHANGE_ID (choose state protection) |
|---------------------------------------------------------->|
|<---------------------------------------------- client ID -|
| COMPOUND: CREATE_SESSION |
|---------------------------------------------------------->|
|<------------------------------- session ID, slot table ---|
| COMPOUND: SEQUENCE, PUTROOTFH, SECINFO_NO_NAME |
|---------------------------------------------------------->|
|<-------------------- security triples allowed here -------|
| COMPOUND: SEQUENCE, PUTROOTFH, LOOKUP "export", |
| GETFH, GETATTR |
|---------------------------------------------------------->|
| COMPOUND: SEQUENCE, RECLAIM_COMPLETE |
| COMPOUND: SEQUENCE, PUTFH, OPEN "report.csv", GETFH |
|---------------------------------------------------------->|
|<------------------------------- stateid (+ delegation) ---|
| COMPOUND: SEQUENCE, PUTFH, READ stateid offset count |
| COMPOUND: SEQUENCE, PUTFH, CLOSE stateid |
| |
|<- callbacks (CB_RECALL etc.) over the client's connection-|
| |
| COMPOUND: DESTROY_SESSION, then DESTROY_CLIENTID |
|---------------------------------------------------------->|
Three ideas carry the design.
- Pseudo file system. NFSv4 has no MOUNT protocol. The server exposes a root filehandle, and RFC 7530 says it "provides multiple file systems by gluing them together with pseudo-file systems". On Linux the root is marked with
fsid=rootorfsid=0in exports(5). - State. OPEN, LOCK and delegations create state on the server, identified by stateids and held under a lease that the client must keep renewing. nfs(5) explains that if a client reboots, "the server frees all open and lock state associated with that client's lease".
- Sessions (4.1 and later). RFC 8881 says clients and servers "MUST support and MUST use the session feature". Every COMPOUND starts with SEQUENCE, naming a slot and a sequence number, so the server's bounded reply cache can give exactly once semantics: a retried request gets the cached reply instead of running twice.
Security negotiation in NFSv4
Each export can demand a different flavour. A client using the wrong one gets NFS4ERR_WRONGSEC and calls SECINFO (SECINFO_NO_NAME in 4.1) to learn the allowed security triples. nfs(5) notes the Linux client renegotiates when it crosses from the pseudo file system into a real export, which "often have more restrictive security settings". As the weakness section shows, that negotiation itself needs integrity protection.
Versions and history
NFSv2 (RFC 1094, March 1989)
The first published version. Fixed 32 byte filehandles, 32 bit file sizes, stateless server, usually over UDP. Linux nfs(5) lists only versions 3, 4.0, 4.1 and 4.2 as supported by the current client, so NFSv2 survives only on very old equipment.
NFSv3 (RFC 1813, June 1995)
RFC 1813 section 1.7 lists the changes from version 2. Filehandles became variable length up to 64 bytes. File sizes and file IDs widened to 8 byte unsigned integers. Writes gained a stability indicator, so a client can send unstable writes and later call COMMIT to flush them to stable storage. New procedures arrived: ACCESS for an explicit permission check on the server, READDIRPLUS to return filehandles and attributes with directory entries, FSSTAT, FSINFO, PATHCONF and MKNOD. Locking stayed in the separate Network Lock Manager protocol, described in the same RFC's appendix.
For security, RFC 1813 described AUTH_UNIX (today's AUTH_SYS) plus DES based AUTH_DES and AUTH_KERB, long obsolete; RFC 5531 calls AUTH_DH "obsolete and insecure". RPCSEC_GSS can be used with NFSv3, but nfs(5) warns it "does not protect their sideband protocols", so MOUNT, NLM and the status monitor stay unauthenticated. NFSv3 remains widely deployed because it is simple and supported almost everywhere.
NFSv4.0 (RFC 3010, RFC 3530, then RFC 7530)
NFSv4 was first published as RFC 3010 in December 2000, replaced by RFC 3530 in April 2003, and re-issued as RFC 7530 in March 2015. RFC 7530's abstract summarises the change: it integrates "support for file locking and the MOUNT protocol" and adds "strong security (and its negotiation), COMPOUND operations, client caching, and internationalization". File and directory names became UTF-8. Owners and groups became strings in user@dns_domain form instead of raw numbers, with ACLs that are richer than POSIX mode bits. Delegations let a server hand a client temporary authority over a file so it can cache aggressively.
RFC 7530 makes Kerberos V5 through RPCSEC_GSS mandatory to implement, keeps AUTH_SYS optional, and drops the LIPKEY and SPKM-3 mechanisms RFC 3530 had required. NFSv4.0 callbacks still need the server to connect back to the client, which is why nfs(5) documents a clientaddr mount option.
NFSv4.1 (RFC 5661, then RFC 8881)
RFC 8881 section 1.9 lists the major differences from 4.0: the sessions model, parallel access to data (pNFS), RECLAIM_COMPLETE for cleaner lock recovery, directory delegations and notifications, attributes set atomically during exclusive create, and better Windows ACL compatibility. The sessions section explains why sessions exist. Earlier versions suffered from a lack of exactly once semantics, "no support for sending callbacks through firewalls", limited trunking over multiple paths, and "requiring machine credentials for fully secure operation". In 4.1 the client creates the connections the server uses for callbacks, so nfs(5) notes that 4.1 and 4.2 "do not require the server to connect to the client".
pNFS is optional. The metadata server hands the client a layout describing where the data lives, and the client then reads and writes directly to storage devices. RFC 8881 lists three layout types (files, objects and block volume) and specifies the files layout itself; the others are defined in separate documents. It also states that pNFS implementations "MUST NOT remove NFSv4.1's access controls".
NFSv4.2 (RFC 7862, November 2016)
RFC 7862 lists the additions: server side clone and copy (inside one server, or directly between two servers), application I/O advice through IO_ADVISE, sparse file support with READ_PLUS, SEEK and DEALLOCATE, space reservation with ALLOCATE, application data blocks with WRITE_SAME, Labeled NFS for mandatory access control labels such as SELinux contexts, and LAYOUTERROR and LAYOUTSTATS for pNFS. The Linux client "tries version 4.2 first, then negotiates down until it finds a version supported by the server", according to nfs(5).
What is still deployed
In practice you will meet NFSv3 on older storage arrays, hypervisor datastores and anywhere simplicity wins, and NFSv4.1 or 4.2 on current Linux servers and clients. NFSv4.0 is still enabled by default on many servers even though 4.1 is the better choice. RPC-with-TLS is the newest piece; on Linux it depends on the tlshd handshake agent described below and on a kernel that supports it.
Security flavours: AUTH_SYS, RPCSEC_GSS with Kerberos, and RPC-with-TLS
This is the heart of NFS security. The table below sets the four practical options side by side.
| Option | Who is authenticated | Integrity of each message | Confidentiality | Where it is defined |
|---|---|---|---|---|
sec=sys (AUTH_SYS) | Nobody. The client asserts a UID, GID and up to 16 groups | None | None | RFC 5531 Appendix A |
sec=krb5 | Each user, by Kerberos, and the server to the user | RPC header only | None | RFC 2203, rpc_gss_svc_none |
sec=krb5i | Each user and the server | Whole message, arguments and results | None | RFC 2203, rpc_gss_svc_integrity |
sec=krb5p | Each user and the server | Whole message | Arguments and results encrypted | RFC 2203, rpc_gss_svc_privacy |
xprtsec=tls | The server, by its certificate | Whole connection | Whole connection, TLS 1.3 | RFC 9289 |
xprtsec=mtls | Server and client machine, by certificates | Whole connection | Whole connection, TLS 1.3 | RFC 9289 |
AUTH_SYS: identity by assertion
Under AUTH_SYS the server makes access decisions on numbers the client sends. RFC 7530 section 19 describes the historical model: "the client was the entire machine, or at least the source IP address", and the server "relied on the NFS client to make the proper authentication of the end-user". It then calls that model "certainly not a safe model".
The only traditional guard is the reserved port check. nfs(5) explains that an NFS server "assumes that if a connection comes from a privileged port, the UID and GID numbers in the NFS requests on this connection have been verified by the client's kernel". Only root can bind a port below 1024. The manual is candid: "This is an easy system to spoof, but on a trusted physical network between trusted hosts, it is entirely adequate." The key words are trusted network and trusted hosts. Anyone with root on a permitted client, or any device that can use a permitted client address, can claim any user ID except where squashing applies.
RPCSEC_GSS and the Kerberos pseudo-flavours
RPCSEC_GSS (RFC 2203) lets RPC use any GSS-API mechanism. In NFS that means Kerberos V5 (RFC 4121 mechanism). RFC 7530 maps the three Linux names to RPCSEC_GSS services and pseudo-flavour numbers:
| Pseudo-flavour number | Name | RPCSEC_GSS service | What it adds |
|---|---|---|---|
| 390003 | krb5 | rpc_gss_svc_none | Per-request user authentication; the RPC header is integrity checked |
| 390004 | krb5i | rpc_gss_svc_integrity | A checksum over arguments and results as well |
| 390005 | krb5p | rpc_gss_svc_privacy | Encryption of arguments and results, which includes integrity |
All three share the Kerberos mechanism OID 1.2.840.113554.1.2.2. RFC 8881 explains the layering: "if privacy is selected, integrity, authentication, and identification are enabled", and every request and response header is integrity protected even in plain krb5. RFC 8881 also says NFSv4.1 servers "MUST support RPCSEC_GSS's privacy service", while clients "SHOULD support" it.
A context starts with RPCSEC_GSS_INIT, carrying a Kerberos token the client obtained from the KDC. The server replies with a context handle and a seq_window. After that, each data request carries a sequence number. RFC 2203 describes the replay protection: the server keeps a window of recently seen sequence numbers, and any request below the window, or one already seen inside it, "is silently discarded".
On Linux the client side of this is the rpc.gssd daemon. Its manual page explains that user credentials come from kinit or PAM, and that the machine credential comes from a service principal in /etc/krb5.keytab. The server side uses either the legacy rpc.svcgssd upcall or gss-proxy; the Linux kernel documentation notes the legacy upcall "can handle tokens that are no bigger than 2KiB", which breaks with large Kerberos tickets, and recommends the gss-proxy mechanism. For how tickets, the KDC and service principals work, see our post on how Kerberos authentication works (RFC 4120).
Kerberos algorithm strength is not chosen by NFS. RFC 7530 says the algorithms "are not directly exposed to or selectable by the client or server", so security depends on the Kerberos realm's configuration, and it points to RFC 6649 for why weak algorithms should be disabled.
RPC-with-TLS (RFC 9289)
RFC 9289 adds encryption at the transport. It defines a new flavour, AUTH_TLS (value 7). The client sends a NULL procedure with AUTH_TLS on the normal port. A server that supports TLS answers with an AUTH_NONE verifier whose body is the eight ASCII characters "STARTTLS". The client then sends a TLS ClientHello on the same connection. The RFC requires TLS 1.3 or later ("Implementations MUST NOT negotiate TLS versions prior to 1.3"), requires servers to keep using the same ports, and registers the ALPN identifier "sunrpc".
Peers can authenticate with X.509 certificates. RFC 9289 forbids wildcard names in RPC-with-TLS certificate DNS identities and requires the client to check that the server certificate matches the expected name or address. Because client addresses are often dynamic, servers identify clients by certificate rather than by IP.
On Linux, the kernel does not perform the handshake itself. The kernel TLS handshake documentation states that "there is no TLS handshake implementation in the Linux kernel" and that a user space handshake agent receives the socket, completes the handshake and hands it back. exports(5) names that agent: the administrator "must install and configure tlshd". Servers then restrict exports with xprtsec=, and clients request TLS with the xprtsec= mount option described in nfs(5).
Security properties and weakness classes
What NFS guarantees, by configuration
| Property | AUTH_SYS | krb5 | krb5i | krb5p | AUTH_SYS + TLS (no client cert) | AUTH_SYS + mutual TLS |
|---|---|---|---|---|---|---|
| User authentication | No | Yes | Yes | Yes | No | No; client machine authenticated |
| Message integrity | No | Header only | Yes | Yes | Yes | Yes |
| Confidentiality of data | No | No | No | Yes | Yes | Yes |
| Replay protection | No | Sequence window | Sequence window | Sequence window | TLS record protection | TLS record protection |
| NFSv3 side protocols covered | No | No | No | No | Depends on each side protocol | Depends on each side protocol |
Availability is not guaranteed by any flavour. RFC 2203 says directly that it "does not address attacks where an attacker can block or steal messages without being detected by the server".
Trusting client-asserted user IDs
This is the defining NFS weakness, and it is a design choice, not a bug. With AUTH_SYS the server's permission checks are only as good as the client's honesty. Root on any allowed client can become any non-root user simply by sending a different UID, because nothing in the credential is verifiable. RFC 5531 says AUTH_SYS "SHOULD NOT be used for services that permit clients to modify data", and RFC 9289 section 6.3 adds that even with TLS, an unauthenticated client "can still misrepresent user identity without server detection". The fixes are Kerberos (the server learns the user from a ticket, not from the client's say-so) or, at minimum, mutual TLS so that only known client machines can send AUTH_SYS requests at all.
Root squashing gaps
exports(5) describes root squashing as mapping UID 0 to an anonymous ID, by default 65534. It is the default, and it is narrow. The manual notes it "does not apply to any other uids or gids that might be equally sensitive, such as user bin or group staff". A client asserting the UID of a service account, a database owner or another administrator gets that user's access. no_root_squash removes even the root mapping; exports(5) describes it as "mainly useful for diskless clients". all_squash maps every user to the anonymous ID, which is safer for public read only data but unusable for home directories.
Over-broad exports
Under AUTH_SYS the export list, keyed on client name or address, is the main access control. exports(5) supports hosts, networks, wildcards, netgroups and a bare * that "will match all clients". Wildcards "also match the dots in a domain name", so they cover every subdomain, and the manual warns they can "work by accident when reverse DNS lookups fail". An export reachable from more machines than intended is an export to whoever controls those machines.
Filehandle guessing and subdirectory exports
Filehandles are capabilities: whoever holds a valid one can use it. exports(5) warns that when you export a subdirectory rather than a whole file system, "it may be possible for a malicious user to access files on the filesystem outside of the exported subdirectory, by guessing filehandles", and that differing security options on nested exports can be bypassed the same way. The defences are to export only whole file systems, to use subtree_check where you must export a subdirectory, and, on recent nfs-utils, sign_fh, which adds a hash to filehandles to "guard against filehandle guessing attacks".
Cleartext data and metadata
Without krb5p or TLS, file contents, file names, directory listings and user IDs cross the network in the clear. nfs(5) puts it plainly: "Typically, file data and user ID values appear unencrypted". krb5i stops modification but not reading. Even with krb5p, RFC 2203 notes that the RPC program, version and procedure numbers stay in the clear, because only arguments and results are encrypted.
Security negotiation downgrade and redirection
RFC 7530 and RFC 8881 both name two operations that need integrity protection even when users choose weaker flavours. Without integrity, an on-path party could alter SECINFO results "such that the client might select a weaker algorithm". And by forging an NFS4ERR_MOVED error and then altering the fs_locations attribute, an attacker could "cause the client to migrate its traffic to a server controlled by the attacker". RFC 9289 has the same shape of problem for TLS: the AUTH_TLS probe is cleartext, so an on-path attacker can make TLS appear unavailable (a STRIPTLS attack). The RFC's mitigation is a client policy that requires TLS on every connection, or DNSSEC TLSA records.
State hijacking
NFSv4 state belongs to a client ID. RFC 8881 describes what can go wrong without protection: someone who can forge requests could create a session for another client's ID and release that client's locks. NFSv4.1 lets the client choose SP4_MACH_CRED or SP4_SSV state protection in EXCHANGE_ID so that only the principal that created the client ID can manage its sessions. nfs(5) adds that when a Linux client has no keytab, it "uses AUTH_SYS and UID 0 for lease management", which means this protection is only available when Kerberos is set up on the client.
Unauthenticated side protocols in NFSv3
nfs(5) says NFSv3's mounting, locking and status protocols "use no authentication". Even an NFSv3 deployment with krb5p on the data path leaves MOUNT and NLM open. The insecure_locks (or no_auth_nlm) export option goes further and stops checking credentials on lock requests at all.
pNFS and inter-server copy
With pNFS, clients talk directly to storage devices. RFC 8881 section 12.9 warns that some storage protocols cannot provide confidentiality, recommends physical isolation or forgoing pNFS where that matters, and warns that access checks done only in the client let a misbehaving client reach data it should not. NFSv4.2 inter-server copy adds a server-to-server trust relationship; RFC 7862 section 4.9 notes that "a server may, depending on configuration, also allow COPY_NOTIFY requests that use only AUTH_SYS".
Implementation bugs
NFS servers and clients parse complex XDR structures from the network, usually in kernel code. RFC 9289 section 6.3 notes that mutual TLS protects XDR decoders "from deserializing untrusted data, a common coding vulnerability". NFS client code also hides in bootloaders, firmware and user space libraries.
Secure configuration, with defender-side checks
Every command below comes from the nfs-utils manual pages or the Linux kernel documentation linked in the sources. Only run these against systems you own or are authorised to administer.
Check what the server is exporting, and with which options
exportfs -v lists the current exports with every option, including defaults you never wrote down. exportfs(8) says the defaults are sync,ro,root_squash,wdelay.
# On the NFS server: current exports with all options
sudo exportfs -v
# The same list in /etc/exports format, useful for diffing against the source file
sudo exportfs -s
# The kernel's own view of exports and clients (nfsd(7))
sudo cat /proc/fs/nfsd/exports
Read the output for these strings: * or a wide network as the client, no_root_squash, insecure, rw where ro would do, sec=sys alone, no_subtree_check on a subdirectory export, insecure_locks or no_auth_nlm, and crossmnt on a parent whose children you did not intend to share.
Check what a server advertises to the network
showmount -e asks a server's MOUNT service for its export list, which is what any client on the network can see for an NFSv3 capable server.
# From an administrative host, against your own server
showmount -e nfs1.example.internal
# Which clients the server believes have mounted what (showmount(8) warns this is not reliable)
showmount -a nfs1.example.internal
showmount(8) notes that NFSv4 only servers "may not expose their MNT service to clients", so an empty answer there is expected.
Write a safe export
A minimal, Kerberos only export to one subnet, read write, with root squashed and subtree checking for a subdirectory export:
# /etc/exports or /etc/exports.d/projects.exports
/srv/nfs 10.20.30.0/24(ro,fsid=0,sec=krb5p)
/srv/nfs/projects 10.20.30.0/24(rw,sec=krb5p,root_squash,subtree_check)
exports(5) explains that sec= takes a colon separated list, that "order counts" for negotiation, and that the default is sys. If you must keep AUTH_SYS for some clients during a migration, you can use multiple sec= blocks so that only Kerberos users get write access; the manual says only ro, rw, no_root_squash, root_squash and all_squash can vary per flavour. Apply changes with:
sudo exportfs -ra
-r re-exports everything and removes entries that were deleted from the files, per exportfs(8).
Require TLS on an export
If you run AUTH_SYS on a network you do not fully trust, exports(5) recommends RPC-with-TLS "particularly useful when using non-cryptographic security flavors such as sec=sys". With tlshd installed and configured on the server:
/srv/nfs/builds 10.20.40.0/24(rw,sec=sys,xprtsec=mtls,root_squash)
xprtsec=mtls admits only clients that present an X.509 certificate. Without an xprtsec= option on a TLS capable server, exports(5) says the default is xprtsec=none:tls:mtls, which allows clients with no TLS at all.
Mount with the security you intend
On the client, state the version and flavour explicitly instead of letting negotiation pick:
# Kerberos with encryption, NFSv4.2
sudo mount -t nfs -o nfsvers=4.2,sec=krb5p nfs1.example.internal:/projects /mnt/projects
# AUTH_SYS, but only over mutually authenticated TLS
sudo mount -t nfs -o nfsvers=4.2,sec=sys,xprtsec=mtls nfs1.example.internal:/builds /mnt/builds
nfs(5) says that if sec= is not given, "the client attempts to find a security flavor that both the client and the server supports", and that if xprtsec=tls or mtls is set and TLS fails, "the mount attempt fails". That failure is what you want: it is the client policy RFC 9289 recommends against STRIPTLS.
Verify what each mount actually negotiated
# Per mount: version, security flavour, transport and options
nfsstat -m
nfsstat(8) says the flags in this output "are the same as the flags give to the mount command", so look for vers=4.2, sec=krb5p and the transport options on every NFS mount. The same information is in /proc/mounts.
Disable versions and transports you do not need
nfs.conf(5) lists the [nfsd] keys udp, tcp, vers3, vers4, vers4.0, vers4.1 and vers4.2. On an NFSv4.1 and later only server:
# /etc/nfs.conf
[nfsd]
udp=n
vers3=n
vers4.0=n
vers4.1=y
vers4.2=y
rpc.nfsd(8) offers the same through -N (do not offer a version) and -V (offer a version), and says the server does not listen on UDP by default. Turning off NFSv3 removes MOUNT, NLM and the rpcbind dependency for NFS clients. Turning off 4.0 removes server-to-client callback connections and gives every client sessions.
Check counters and the Kerberos plumbing
# Server side statistics, NFSv4 only
nfsstat -s -4
# RPC layer statistics only
nfsstat -r
# Kernel: has the server been switched to the gss-proxy upcall? (kernel docs)
cat /proc/net/rpc/use-gss-proxy
The kernel documentation explains that this file contains 1 once gss-proxy has registered, that a read blocks until it has, and that the choice must be made before nfsd starts. If the command hangs, the server is not yet using gss-proxy.
Network exposure and logging
Keep 2049, 111 and the NFSv3 helper ports inside the networks that need them. nfs(5) describes fixing helper ports so a firewall can allow specific ones, and NFSv4.1 and later need only 2049. For logging, exportfs and rpc.mountd accept debug categories through the [exportfs] and [mountd] sections of nfs.conf(5); nfs.conf(5) says that setting debug = auth for mountd is equivalent to its --log-auth option.
How do you enforce sec=krb5p across an estate?
Enforcing krb5p is mostly about everything around the export line.
- Every client needs a keytab. rpc.gssd(8) looks in
/etc/krb5.keytabfor a host or nfs principal. Without one, the Linux client uses AUTH_SYS for lease management and NFSv4.1 state protection is unavailable. - Every user and service needs a ticket, through
kinit, PAM at login, or a keytab for batch jobs. - ID mapping must agree. RFC 7530 asks that
user@domainnames be translated "in a secure manner that preserves the integrity of the translation", for example LDAP over an integrity protected channel. - Exports list only krb5p, not
sec=krb5p:sys, or negotiation can settle on the weaker flavour. - Clients mount with sec=krb5p explicitly.
- Expect a cost. nfs(5) says to "expect some performance impact when using integrity checking or encryption". Measure it before ruling it out.
- Verify on every client with
nfsstat -m.
NFS vs SMB vs object storage for shared data
| NFSv4.2 with Kerberos | NFSv3 with AUTH_SYS | SMB 3.1.1 | Object storage over an HTTP API | |
|---|---|---|---|---|
| Typical use | Linux home directories, shared project data, compute clusters | Legacy arrays, hypervisor datastores, simple build caches | Windows and mixed estates, user shares | Application data, backups, data lakes, artifacts |
| Access model | Mounted POSIX-like file system with locks and ACLs | Mounted file system, separate lock protocol | Mounted share with rich locking and caching | Whole object PUT and GET by key, no in-place partial writes or POSIX locks |
| Authentication | Per user, Kerberos | Client asserted UID | Kerberos or NTLM | Per request signatures or tokens |
| Integrity and encryption | krb5i, krb5p; or TLS 1.3 | None, unless RPC-with-TLS | Signing; AES encryption from 3.0 | TLS on every request is the norm |
| Ports | TCP 2049 | 2049, 111 and helper ports | TCP 445; QUIC on UDP 443 optional | HTTPS 443 |
| Internet exposure | Not recommended | Never | Not over TCP 445 | Designed for it, with authentication |
| Operational cost | Kerberos realm, keytabs, ID mapping | Low, until something goes wrong | Low inside Active Directory | Applications must be written for it |
A rough rule: NFSv4.2 with krb5p (or mutual TLS where Kerberos is impossible) for Linux file sharing; SMB 3.1.1 with signing and encryption for Windows and mixed estates (see our SMB protocol explainer); object storage when the application can work with whole objects and needs to be reached from many networks; and SFTP for moving files to and from outside parties (see FTP vs SFTP vs FTPS). If Kerberos itself is the open question, our comparison of Kerberos vs NTLM covers what each gives an application, and the TCP vs UDP post explains what a network observer can still see when payloads are encrypted.
Which NFS code is inside your software
The kernel NFS client and server are patched through your distribution. The rest is easy to lose track of: nfs-utils, rpcbind and libtirpc packages in container base images, user space NFS libraries in backup tools and storage operators, and NFS code in bootloaders and firmware.
Safeguard's software composition analysis, generally available, builds a full transitive dependency inventory for repositories and container images, so a user space NFS library or an nfs-utils package in a base image shows up by name and version. SBOM generation and analysis, also generally available, records it in SPDX or CycloneDX. Vulnerability management with EPSS and CISA KEV enrichment and reachability analysis, both generally available, tell you which findings are being exploited and whether your code calls the vulnerable path. Container visibility and scanning, generally available as part of ESSCM, covers the images your workloads mount NFS from. Safeguard does not inspect NFS traffic, read your exports file or tell you which security flavour a mount negotiated; the commands above do that.
Common mistakes
- Treating the export list as authentication. Under AUTH_SYS, an IP address or host name is the only gate, and anyone with root on an allowed client can be any user.
- Assuming root_squash protects everything. It maps UID 0 only. Service accounts, administrators and every other user ID are still accepted as sent.
- Using no_root_squash for convenience. It hands root on every allowed client root on the export. Keep it for the narrow cases exports(5) describes, such as diskless clients.
- Exporting to
*or a wide wildcard. exports(5) wildcards match across subdomains, and a bare*matches every client that can reach the server. - Adding
insecureto fix a mount error. It removes the reserved port check. nfs(5) recommends allowing non-privileged ports only when both sides use strong authentication such as Kerberos. - Listing
sec=krb5p:sys. Negotiation can settle onsys. If migration needs both, givesysread only access with per-flavour options. - Thinking krb5 or krb5i encrypt data. Only krb5p encrypts. krb5i proves nothing was changed; anyone on the path can still read it.
- Enabling TLS without client certificates and calling it done.
xprtsec=tlshides traffic but, as RFC 9289 says, an unauthenticated client "can still misrepresent user identity". - Leaving NFSv3 enabled on an NFSv4 server. It brings back MOUNT, NLM and rpcbind, which use no authentication, and a second path to the same data.
- Exporting subdirectories without subtree_check. Filehandle guessing can reach files outside the exported subtree.
- Letting async creep in. exports(5) says
asynclets the server "violate the NFS protocol" and can lose or corrupt data on a crash. It is an integrity risk, not just a performance setting.
FAQ
What port does NFS use?
TCP 2049 for NFS itself, registered with IANA as nfs. NFSv3 also needs rpcbind on port 111 and the MOUNT, NLM and status services, which may use random ports unless you fix them; IANA registers 20048 for mountd. NFSv4 needs only 2049. NFS over RDMA uses 20049.
Is NFS encrypted?
Not by default. Data is encrypted only with sec=krb5p (Kerberos privacy) or with RPC-with-TLS (xprtsec=tls or xprtsec=mtls), which uses TLS 1.3 on the same port. AUTH_SYS, krb5 and krb5i all send file contents in the clear.
What is the difference between krb5, krb5i and krb5p?
All three authenticate each user with Kerberos. krb5 integrity checks only the RPC header. krb5i adds an integrity check over the whole message, so changes are detected. krb5p also encrypts the arguments and results, so file data cannot be read on the network.
NFSv3 vs NFSv4: what is the difference?
NFSv3 is stateless and uses separate, unauthenticated protocols for mounting and locking, with several ports. NFSv4 is stateful, has mounting and locking built in, runs on port 2049 only, uses COMPOUND requests and string based owners, and must implement Kerberos. NFSv4.1 adds sessions and pNFS; NFSv4.2 adds server side copy and security labels.
How do I check NFS exports on a Linux server?
Run exportfs -v on the server to see every export with its effective options, or exportfs -s for /etc/exports format. From another host, showmount -e servername shows what the MOUNT service advertises. Only run these against servers you administer.
How do I check which NFS version and security a mount is using?
Run nfsstat -m on the client. Each NFS mount is listed with its flags, including vers= and sec=. The same details appear in /proc/mounts.
Can NFS be used over the internet?
It is not designed for it. Even with Kerberos, exposing 2049 widens the attack surface of kernel code. If remote clients need NFS, use a VPN, or RPC-with-TLS with mutual authentication plus Kerberos, and restrict access to known client certificates.
NFS vs SMB: which should I use?
Use NFSv4.2 with Kerberos for Linux and Unix clients, and SMB 3.1.1 for Windows and mixed estates. Both can authenticate users with Kerberos and encrypt traffic. NFS with AUTH_SYS is the weakest option of either family, because it has no user authentication at all.
What is RPC-with-TLS for NFS?
RFC 9289, published September 2022, lets any ONC RPC protocol, including NFS, upgrade a connection to TLS 1.3 after a cleartext probe on the same port. With client certificates it also authenticates the client machine. On Linux it needs the tlshd handshake agent and the xprtsec= export and mount options.
Fix it without waiting for upstream
NFS vulnerabilities usually live in implementations: the kernel NFS client and server, nfs-utils and rpcbind, the libtirpc library, and user space NFS client libraries that your applications, backup tools and container images pull in. Kernel fixes come from your distribution, but the libraries and packages in the software you build are dependencies you can upgrade yourself.
Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.
1. Autonomous remediation: pull requests, not tickets
When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.
2. Gold Open Source: the safe version in seconds, free
gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.
3. Gold Registry: start from components that are already fixed
registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.
When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.
| Your situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | The Gold scan GitHub Action in CI, plus Safeguard policies and gates |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch (early access) |
| You want to start clean on new projects | Pull hardened components and images from Gold Registry |
One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.
See how Safeguard would help
These demos show dependency, image and SBOM workflows, not NFS traffic monitoring or server configuration. You can download Safeguard and follow the CLI docs to try them on your own projects.
This demo sets up the Safeguard runner on your own machine, approves it with its device code and then scans from the terminal. Pointed at a container image that ships nfs-utils, rpcbind or libtirpc, or at a backup tool that bundles an NFS client library, the same scan lists those packages with their versions and known vulnerabilities.
Click through it at your own pace, or open it full screen.
This demo is a real CLI session: install, open the interactive terminal, ask it to fix the lodash CVE in a repository, and watch it read the finding and advisory, ask before each command, move lodash to 4.17.21, run npm install and npm test with approval, and print a summary without pushing anything. The same approve each step flow applies when the dependency to bump is an NFS client library pinned in your manifest or a base image tag in your Dockerfile.
Click through it at your own pace, or open it full screen.
This demo connects Claude to the Safeguard MCP server with OAuth and lets you choose which tools Claude can use. Once connected, you can ask in plain language which of your projects or images include a particular nfs-utils, libtirpc or NFS library version, and get the answer from your own scan data.
Click through it at your own pace, or open it full screen.
Quick check
An export uses root_squash and is limited to one subnet with sec=sys. Can a user with root on one of those clients read another user's files?
Yes. root_squash only maps UID 0. Under AUTH_SYS the client states the user ID in every request and the server cannot verify it, so root on an allowed client can send requests with any other user's UID and get that user's access. Kerberos (sec=krb5 or stronger) fixes this because the server learns the user from a ticket, not from the client.
A server exports with sec=krb5i. Can someone on the network see the contents of files being read?
Yes. krb5i authenticates each user and adds an integrity check so changes are detected, but it does not encrypt anything. File data, names and attributes are still visible on the wire. To hide them, use sec=krb5p, or protect the connection with RPC-with-TLS.
Sources
- RFC 1094: NFS: Network File System Protocol Specification, March 1989
- RFC 1813: NFS Version 3 Protocol Specification, June 1995
- RFC 7530: Network File System (NFS) Version 4 Protocol, March 2015
- RFC 8881: Network File System (NFS) Version 4 Minor Version 1 Protocol, August 2020
- RFC 7862: Network File System (NFS) Version 4 Minor Version 2 Protocol, November 2016
- RFC 5531: RPC: Remote Procedure Call Protocol Specification Version 2, May 2009
- RFC 2203: RPCSEC_GSS Protocol Specification, September 1997
- RFC 5403: RPCSEC_GSS Version 2, February 2009
- RFC 9289: Towards Remote Procedure Call Encryption by Default, September 2022
- RFC 7204: Requirements for Labeled NFS, April 2014
- RFC Editor metadata for RFC 7530, 8881, 7862, 5531, 2203, 3530 and 5661 (obsoletes and updates)
- exports(5): NFS server export table (nfs-utils)
- nfs(5): fstab format and options for the nfs file systems
- exportfs(8): maintain table of exported NFS file systems
- showmount(8): show mount information for an NFS server
- nfsstat(8): list NFS statistics
- nfsd(7): special filesystem for controlling Linux NFS server
- rpc.nfsd(8): NFS server process
- nfs.conf(5): general configuration for NFS daemons and tools
- rpc.gssd(8): RPCSEC_GSS daemon
- Linux kernel documentation: rpcsec_gss support for kernel RPC servers
- Linux kernel documentation: In-kernel TLS handshake
- Linux kernel documentation: NFS index
- Linux NFS project wiki
- IANA Service Name and Transport Protocol Port Number Registry
Last reviewed: 23 October 2026. NFS server and client defaults change between kernel and nfs-utils releases; confirm against the manual pages for the versions you run.