← Back to Wiki
Networking / Storage

Your "10 Gigabit" NFS Mount May Be Running Over the 1 Gigabit Link

You add a 10GbE path between a Proxmox VE guest and a Synology NAS. You point the mount at the new address. Routing confirms the new interface. mount shows exactly what it should. Throughput does not move one megabyte per second. NFSv4 recognised the server it was already talking to and quietly kept using the old connection.

Share on X

What server trunking is

NFSv4.1 and later can use several network paths to the same server. To do that the client works out whether two addresses are really the same server. It asks with an EXCHANGE_ID call. If the server answers with an identity the client already has a session with, the client treats the new address as another path to a known server. Then it reuses the existing transport instead of building a new one.

That is a sensible feature. It is also why your new fast path carries nothing. You mounted a different IP. The client asked who lives there. The answer was "the server you are already connected to". So it carried on over the old, slow interface.

Why every obvious check passes

This is the part that eats an afternoon. All of these look correct while the problem is happening:

$ mount | grep movies
10.0.1.31:/volume1/movies on /mnt/movies type nfs4 (rw,...)   # the new address ✓

$ ip route get 10.0.1.31
10.0.1.31 dev ens20 src 10.0.1.44                              # the new interface ✓

$ ping -c3 10.0.1.31
3 packets transmitted, 3 received                              # reachable ✓

Throughput sat unchanged at 105 MB/s. That is suspiciously close to a saturated 1 GbE link. It was the only clue anything was wrong.

The check that actually answers it: interface counters

Ask the interface how many bytes it moved. It cannot be talked into a wrong answer:

# before
$ cat /sys/class/net/ens20/statistics/rx_bytes
0

# read ~4 GB off the mount
$ dd if=/mnt/movies/some.large.file of=/dev/null bs=1M count=4000

# after
$ cat /sys/class/net/ens20/statistics/rx_bytes
0                       # <- the "10G" interface received nothing at all

Zero. The traffic went out the old interface the whole time. Throughput numbers and mount output are not evidence about which link carries traffic. Counters are. After the fix the same test showed about 4,028 MB across that interface for a 4 GB read. That is what actually using the path looks like.

nfsstat -m and ss -tnp | grep :2049 are good supporting checks. The second one shows the local address of the real TCP connection, which is the thing in dispute.

NFS client mounts 10G addr vers=4 NAS both addresses same server id existing 1G connection, all traffic stays here new 10G path, rx_bytes: 0 EXCHANGE_ID → "you already have a session with me" → client reuses the old transport
Trunking detection in one picture. The mount is correct, the route is correct, and the packets never touch the new link.

The fix: pin it to NFSv3, deliberately

NFSv3 has no trunking detection. It has no concept of server identity across addresses. The address you ask for is the address it uses. For a fixed storage path where you have already decided which link the traffic takes, that predictability is the feature:

# /etc/fstab
10.0.1.31:/volume1/movies  /mnt/movies  nfs  vers=3,nconnect=8,_netdev  0  0

Write that choice down where the next person finds it. vers=3 looks like somebody was lazy or never upgraded. It is not an oversight. It is the mechanism. If you move back to v4 later, re-verify with interface counters. Not with mount output, and not with a throughput number.

To stay on v4 you have two honest options. Make the two addresses genuinely distinct servers from the client's point of view. Or accept that path selection is the client's call, not yours. There is no "please use this NIC" flag that beats trunking.

While you're here: one connection isn't enough from a VM

Once traffic was genuinely on the fast link, a single NFS connection from the guest still only hit 299 MB/s. The hypervisor itself managed 579 to 633 MB/s over the same link. That gap is virtio single-queue overhead, not the network. One queue, one CPU doing the work.

nconnect tells the client to open several TCP connections for the same mount. That spreads the work:

ConfigurationThroughput
1G path (before)105 MB/s
10G, single connection299 MB/s
10G, nconnect=8553 MB/s
10G, nconnect=8, live library under load397 MB/s

Nearly double, from one mount option. nconnect needs a modern kernel on both ends. It is per server-address, so mounts to the same address share the connections. Raising the guest NIC's queue count with queues=8 on a virtio NIC attacks the same overhead from the other side. It stacks with nconnect, at the cost of a guest restart.

Prevention

The one-line version: NFSv4 recognises a server it already has a session with and keeps using the old connection, whatever address you mounted. A new fast link can sit at zero bytes while everything looks correct. Check /sys/class/net/<iface>/statistics/rx_bytes. Pin storage mounts to vers=3 when you need to pick the path yourself. Add nconnect before you conclude the link is the limit.