Slow NAS Transfers — SMB, GVFS & a USB 2.0 Trap

Slow NAS Transfers — SMB, GVFS & a USB 2.0 Trap

Search terms: slow NAS · slow SMB · slow CIFS · 25 MB/s · 40 MB/s · GVFS · gvfsd-fuse · smb-share:server= · USB 2.0 · 480 Mbps · ASM225CM · UGREEN · nobarrier · rsync says 100% but still running

Solved 2026-08-08. Transfers to the UGREEN NAS were running 25-30 MB/s on a gigabit LAN. Two separate causes, neither of them the network. Ended at 74.6 MB/s — a 2.7× improvement.

Stage Write speed
Starting point (GVFS mount, drive on USB 2.0) ~25-30 MB/s
After switching to a kernel CIFS mount 40.9 MB/s
After moving the NAS’s drive to a USB 3.0 port 74.6 MB/s

Read this first: two traps that fake your numbers

Both of these produced plausible, completely wrong measurements during this investigation. Neither is obvious.

Trap 1 — write caching makes copies look instant

rsync -ah --progress <3.4GB file> /mnt/nas/test/
   3.34G 100%   1.98GB/s   0:00:01

1.98 GB/s on a gigabit link is impossible (~110 MB/s ceiling). rsync measured the speed of writing into the page cache — RAM — not to the NAS. It printed 100% when it had handed off every byte, then sat there while the kernel flushed those pages over the network. That gap between “100%” and the prompt returning is the real transfer.

See what’s actually pending:

watch -n1 'grep -E "^(Dirty|Writeback):" /proc/meminfo'

Dirty = written but not yet sent. Writeback = in flight. The transfer is genuinely done when both reach zero. During a 3.4 GB copy this box showed 1.4 GB sitting in Writeback long after rsync claimed completion.

Benchmark writes with conv=fdatasync — it forces everything to the server before dd stops the clock:

dd if=<big-local-file> of=/mnt/nas/test/benchmark.tmp bs=1M count=1024 conv=fdatasync

Trap 2 — the NAS caches reads in its own RAM

Reading the same 1 GB region twice gave 19.3 MB/s, then 72.2 MB/s. The second read was served from the NAS’s memory, not its disks. That nearly produced a claimed “3.7× improvement” that did not exist.

Always benchmark a region you have never read before. Use different offsets for each test:

dd if=/mnt/nas/bigfile.img of=/dev/null bs=1M count=1024 skip=6144   # test A
dd if=/mnt/nas/bigfile.img of=/dev/null bs=1M count=1024 skip=7168   # test B

Paired cold reads gave the honest answer: 21.3 vs 38.3 MB/s.

The accidental cached read turned out to be useful anyway — it measured the NAS’s protocol/CPU path with storage removed, and 72 MB/s later matched the final disk-backed result almost exactly.


Cause 1 — GVFS instead of a kernel mount

Mounting a share by clicking it in the file manager gives you a GVFS/FUSE mount, in userspace. Every block round-trips through gvfsd-fuse. Check with:

ls /run/user/1000/gvfs/          # gvfs mounts (slow)
mount -t cifs                    # kernel mounts (fast)

If the first has entries and the second is empty, that’s the problem. Measured cost here: 21.3 vs 38.3 MB/s cold read — roughly half the throughput.

Fix: mount with the kernel CIFS client

sudo apt install cifs-utils

Credentials in a file, created with restrictive permissions before anything is written to it, and edited in an editor so the password never enters shell history:

install -m 600 /dev/null ~/.smbcredentials && nano ~/.smbcredentials
username=YOUR_USER
password=YOUR_PASSWORD
domain=workgroup
sudo mkdir -p /mnt/nas
sudo mount -t cifs "//10.0.0.15/<share>" /mnt/nas \
  -o credentials=/home/aztechguy/.smbcredentials,uid=1000,gid=1000,iocharset=utf8,vers=3.0

uid=1000,gid=1000 makes files yours instead of root’s. Persistent version for /etc/fstabnofail and _netdev matter, or an unreachable NAS can hang boot:

//10.0.0.15/<share> /mnt/nas cifs credentials=/home/aztechguy/.smbcredentials,uid=1000,gid=1000,iocharset=utf8,vers=3.0,nofail,_netdev,x-systemd.automount 0 0

rsync note: rsync speaks only user@host:/path (SSH) or rsync://host/module. A bare 10.0.0.15/share/folder is treated as a local relative path. To an SMB-only NAS you must rsync to the mounted path.


Cause 2 — the NAS’s own drive was on USB 2.0

The bigger one, and invisible from the client. Find where the share actually lives:

grep -A4 "<share-name>" /etc/samba/smb.conf
[1.44.1-64570]
	path = /mnt/@usb/sda1        <- USB-attached, not an internal array

All prior analysis of the internal disks, RAID layout and free space was aimed at drives that were never in the path. Check the share’s path before profiling storage.

Then check the link speed of that device, on the NAS:

for d in $(readlink -f /sys/block/sda | tr '/' '\n' | grep -E "^[0-9]+-[0-9.]+$"); do
  echo "$d: $(cat /sys/bus/usb/devices/$d/speed 2>/dev/null) Mbps"
done
1-1:   480 Mbps          <- a USB 2.0 HUB
1-1.1: 480 Mbps          <- the drive behind it

480 Mbps = USB 2.0 ≈ 40-45 MB/s real. Measured 38.3 read / 40.9 write — exactly at the ceiling. A 12TB 7200rpm IronWolf capable of 200+ MB/s, throttled to a fifth by its connection.

List every USB controller to see whether a faster one exists:

for d in /sys/bus/usb/devices/usb*; do
  echo "$(basename $d): $(cat $d/speed 2>/dev/null) Mbps  $(cat $d/product 2>/dev/null)"
done
usb1: 480 Mbps  xHCI Host Controller
usb2: 5000 Mbps xHCI Host Controller    <- free, unused

Fix: move the drive, safely

Unmount before unplugging — pulling a mounted drive is how filesystems get corrupted.

sudo fuser -vm /mnt/@usb/sda1        # who is holding it?

Read the access column: c = current directory only, f = open file. Only c means nothing is being read or written and it is safe to clear. Unmount the share on the client first, then restart Samba to clear leftover children:

sudo systemctl restart smbd.service && sleep 3 && sudo umount /mnt/@usb/sda1 && sync

Then move the cable to a blue/SS port, directly, not through a hub. Verify:

2-1:   5000 Mbps
2-1.3: 5000 Mbps

Result: 40.9 → 74.6 MB/s.

Three ways this move silently fails:

  • a USB 2.0 cable in a USB 3.0 port negotiates 480 and nothing changes — use the enclosure’s own cable;
  • going through a USB 2.0 hub caps everything downstream regardless of the port;
  • the device may re-enumerate under a different name, breaking a share defined by device path (path = /mnt/@usb/sda1). Check the share still resolves afterwards.

What was ruled out (and how)

Four plausible hypotheses died on evidence. Recording them so they aren’t re-chased.

Hypothesis Killed by
Laptop’s USB-C port was USB 2.0 Kernel logged new SuperSpeed USB device on both ports — always USB 3.0
Dock contention (DisplayLink video vs NIC) Moved to the built-in PCIe NIC at gigabit; speed unchanged
SMB signing burning NAS CPU testparm -sserver signing = No, already off
NAS CPU too weak During transfer: 50% idle, smbd at 29% — but 37% iowait

Moving the dock to a different port cost a monitor (the original port carried DisplayPort Alt Mode, the new one didn’t) and gained nothing. The kernel had already recorded both port speeds in the journal — checking journalctl -b 0 | grep "new SuperSpeed" would have answered it in ten seconds without touching hardware.


Also found: nobarrier on the USB volume

/dev/sda1 on /mnt/@usb/sda1 type ext4 (rw,nosuid,nodev,noatime,nodelalloc,nobarrier)

nobarrier disables ext4 write barriers — the mechanism that forces journal writes to commit in order. Faster, but on power loss or a bus disconnect the journal can end up inconsistent in exactly the way journaling exists to prevent. It matters more than usual on a USB-attached drive, which can drop off the bus in ways an internal SATA disk won’t.

Almost certainly a deliberate vendor choice, and it likely resets on reboot or firmware update, so it isn’t really a tunable. Worth knowing before putting anything important on that volume, and an argument for the NAS being on a UPS. Compare with the 509-day-old ext4 orphan-inode error cleared from the local 2TB NVMe on 2026-08-02 — same failure mode, different drive.


Where the ceiling is now

74.6 MB/s is about 68% of practical gigabit (~110 MB/s). The next limit looks like the NAS itself: the RAM-served read measured 72.2 MB/s with storage removed from the path, and the final disk-backed write landed at 74.6. Those matching numbers suggest ~72-75 MB/s is what that ARM CPU pushes through SMB regardless of what’s underneath.

The remaining lever is the NAS’s indexing services — index_serv, media_serv, thumb_core, thumb_serv, with media_worker observed at 83% CPU during a transfer. Exclude the share from indexing in the NAS web UI (rather than systemctl stop, which won’t survive a reboot) and re-run the benchmark to see if it’s worth anything.


Method notes worth keeping

  • Check the share’s path before profiling storage. Hours went into internal disks that were never in the data path.
  • Benchmark cold, and benchmark writes with fdatasync. Both caches will lie to you.
  • The logs often already answer it. The USB port question was recorded in journalctl before the hardware was ever touched.
  • Related: Diagnosing Freezes, Hangs & Unclean Shutdowns — same isolate-by-controlled-test method, including the identical “vary the shape of the load” lesson that solved the NVMe ASPM storm.