The idea was simple
All I wanted was for my T620 to mount the QNAP shares, retire the QNAP as the “brain,” and have the T620 do everything … Samba, SFTP, the lot … while the QNAP just sat there as dumb storage in the background. Simple networking. I have never been more wrong about something taking one evening.
Boot failure #1: the NFS ordering cycle
First attempt, I set up NFS mounts from the QNAP onto the T620, then re-exported those same paths back out via NFS so other machines could reach them through the T620. Seemed logical … one machine, one point of access.
Rebooted. Box wouldn’t come up. Emergency mode, root mounted read-only,
the whole nightmare. Turns out re-exporting an NFS-mounted path via NFS
again is a genuine, documented bug in nfs-utils/systemd … not a Fedora
thing, not something I did wrong, just a real contradiction in how
nfs-server-generator builds its ordering dependencies. The mount wants
to start before the server, the server wants to start before the mount,
and systemd just gives up.
Fixed it by SSHing in, remounting root read-write, ripping out the bad
config. Rebooted again. Same thing happened again a day later because
the underlying fstab options (x-systemd.automount mixed with re-export)
were still fighting each other.
The actual fix: stop trying to be clever. QNAP mounts get pulled onto
the T620 via plain NFS with soft,timeo=30,retrans=3 … no automount, no
hard, nothing fancy. Anything that needs to go back out to other
machines goes out via Samba, never NFS-over-NFS again. Sounds obvious
now. Took two boot failures to get there.
sudo tee -a /etc/fstab << 'EOF'
192.168.0.xxx:/Public /mnt/data/Public nfs _netdev,nfsvers=3,soft,timeo=30,retrans=3 0 0
EOF
sudo systemctl daemon-reload
sudo mount -a
The permissions rabbit hole
Once the mounts were stable, I hit an access problem that made no sense
on paper. Some files in one of my shares just would not open … ACL said
I had full access, getfacl showed rwx right there in black and white,
and Samba still handed back access denied.
Turns out the QNAP’s NFSv3 server was enforcing raw UID matching
underneath the ACL, and completely ignoring what the ACL said for any UID
that wasn’t the file’s original owner. My Linux UID (1000) didn’t match
the QNAP-side UID (502) that actually owned those files, and no amount of
setfacl fixed it because NFSv3 doesn’t do username mapping … just raw
numbers, and QTS wasn’t respecting the ACL grant for a non-owning UID.
The fix: create a dummy local account with that exact UID (502), and
tell Samba to force user = qnap-relationships on that specific share.
Now Samba always accesses those files as the UID the QNAP actually
trusts, no matter who’s really connected. Not elegant, but it works, and
now I know to check ls -la for a raw number instead of a name … that’s
the tell that a UID isn’t mapping.
sudo useradd -u 502 -M -s /sbin/nologin -g users qnap-relationships
In smb.conf, under the affected share:
[RELATIONSHIPS]
force user = qnap-relationships
sudo systemctl reload smb
The HDD that ate my shares
Added a second local drive for shared storage. Mounted it straight onto
/mnt/data … the same parent directory the QNAP shares were nested
under. Instant chaos. Every share came back blank, Samba started
throwing canonicalize_connect_path failed, and for about twenty
horrible minutes I thought I’d lost data.
Nothing was lost. The new disk was just shadowing the QNAP submounts … mount something on top of a directory, and whatever was already mounted underneath temporarily disappears from view. Classic Linux mount behaviour, obvious in hindsight, invisible in the moment when you’re staring at empty folders at 11pm.
The fix: local disks get their own completely separate mountpoint.
Always. /mnt/data2, not anywhere near /mnt/data. Never again.
sudo mkdir -p /mnt/data2
sudo tee -a /etc/fstab << 'EOF'
UUID=xxxxxxxxxxxxxxxxxxxxxxx /mnt/data2 xfs defaults,nouuid 0 0
EOF
sudo mount -a
(nouuid also fixes a related “duplicate UUID” mount refusal if the
same disk ever gets reconnected via a different cable or port.)
SELinux, twice
Got bit by SELinux context mismatches more than once. First time, a
directory needed to serve both Samba and SFTP, and the default
samba_share_t context only satisfied one of those … SFTP kept getting
silently denied until I relabeled it to public_content_rw_t, which
happily serves both. Second time, same story with the Hugo web root for
this very blog … nginx got a flat 403 until I ran the same relabel
pattern.
Now it’s muscle memory: anything new that needs to be readable across
multiple services gets checked with ls -Z before I even bother
debugging further.
sudo semanage fcontext -a -t public_content_rw_t "/mnt/data2/Public(/.*)?"
sudo restorecon -Rv /mnt/data2/Public
SFTP jails and the “forgot to persist it” trap
Built proper SFTP chroot jails so each user gets dropped into just their own home folder plus a shared Public folder, nothing else visible. Worked great … until a reboot, at which point every user’s SFTP access to Public just vanished with no error, nothing in the logs pointing at why.
Turned out the bind mounts that made the jails work were only ever done
live with mount --bind, never actually written anywhere permanent.
A clean reboot means a clean slate, and nothing was there to redo the
binding.
The fix, eventually: stopped hand-writing fstab lines per user
entirely … at four users it was already unmanageable, and I was thinking
about a hundred. Wrote one small systemd service that scans
/srv/sftp/* at boot and binds whatever it finds, automatically. Add a
new user, their jail just works next boot. Zero fstab edits, ever again.
sudo tee /usr/local/bin/sftp-bind-jails.sh << 'EOF'
#!/bin/bash
HOMES_ROOT="/mnt/data/homes"
PUBLIC_ROOT="/mnt/data2/Public"
SFTP_ROOT="/srv/sftp"
for jail in "$SFTP_ROOT"/*/; do
user=$(basename "$jail")
mkdir -p "${jail}home" "${jail}public"
mountpoint -q "${jail}home" || mount --bind "${HOMES_ROOT}/${user}" "${jail}home"
mountpoint -q "${jail}public" || mount --bind "$PUBLIC_ROOT" "${jail}public"
done
EOF
sudo chmod +x /usr/local/bin/sftp-bind-jails.sh
sudo tee /etc/systemd/system/sftp-bind-jails.service << 'EOF'
[Unit]
Description=Bind-mount all my SFTP chroot jails
After=mnt-data-homes.mount mnt-data2.mount
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/bin/sftp-bind-jails.sh
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now sftp-bind-jails.service
What I’d tell past-me
- If you’re mounting something via NFS, check whether you’re about to re-export it. If yes, stop, use Samba instead.
- A local disk needs its own mountpoint. Not near anything else.
getfaclshowing the right answer doesn’t mean the underlying protocol is going to respect it. Check the raw UID.- Anything serving through more than one protocol needs its SELinux context checked before you assume it’s a permissions bug elsewhere.
- Never trust a live
mount --bindto survive a reboot. If it matters, it goes in a config file or a service, not just a command you ran once.
None of this was smart engineering. It was mostly stubbornness and a lot
of journalctl at hours I’m not proud of. But the box works now, and I
understand every single piece of why, which is worth more than if it had
just worked first try.