2026-07-23
You know the drill. A service running as www-data can't open /srv/apps/tenant-42/uploads/inbox/foo.pdf. You start ls-ing each directory: ls -ld /srv, ls -ld /srv/apps, ls -ld /srv/apps/tenant-42... six commands later you spot a stray drwx------ in the middle. Or you don't, because there's a symlink pointing to a bind mount that lives on an NFS export with squashed UIDs, and now you're chasing your tail.
namei is the util-linux tool built for exactly this. One command, one column-per-hop breakdown, symlinks and mountpoints expanded inline. Been shipping in every distro since the '90s.
$ namei -lx /srv/apps/tenant-42/uploads/inbox/foo.pdf
f: /srv/apps/tenant-42/uploads/inbox/foo.pdf
Dr-xr-xr-x root root /
drwxr-xr-x root root srv
drwxr-xr-x root root apps
drwx------ deployer deployer tenant-42 <-- gotcha
drwxr-xr-x deployer www-data uploads
drwxrwx--- deployer www-data inbox
-rw-r----- deployer www-data foo.pdf
tenant-42 is mode 700 owned by deployer, so www-data can't traverse into it — the leaf's permissions are irrelevant. The chase is over in one screen.
The flags that matter:
-l — long listing with mode/owner/group per component. This is the flag you always want; it's basically -m -o -v bundled.-x — mark mountpoints with a capital D instead of lowercase d. Priceless when the "missing" permission is actually a container bind mount over a hardened directory, and you'd otherwise never notice the layer change.-n — don't follow symlinks. Useful when the symlink target itself is the thing you're investigating.The symlink case, where namei earns its keep:
$ namei -l /var/www/html/uploads/report.csv
f: /var/www/html/uploads/report.csv
dr-xr-xr-x root root /
drwxr-xr-x root root var
drwxr-xr-x root root www
drwxr-xr-x root root html
lrwxrwxrwx root root uploads -> /mnt/data/uploads
dr-xr-xr-x root root /
drwxr-xr-x root root mnt
drwx------ root root data <-- symlink target dies here
drwxrwxr-x root www uploads
-rw-rw-r-- root www report.csv
Each symlink gets its own indented subwalk. Try producing that with ls and readlink in a loop and see how long it takes you — and how many corner cases (loops, relative link targets, mid-path links) you'd have to handle.
Why not just stat or ls -ld in a loop? You can, and you probably have. But you have to spell out every component, remember to chase the symlink targets manually, and mentally align seven different outputs. namei tokenizes the path itself, walks each component with lstat(2), and hands you one aligned table. It's the diff between "I'll figure this out" and "here's your bug."
The killer combo: sudo -u www-data namei -lx /srv/apps/tenant-42/uploads/inbox/foo.pdf. Now lstat happens as the actual failing user, and any traversal denials surface right where they'd fail at runtime. If your daemon runs as UID 33 and hits an NFS mount that squashes it to nobody, you'll see the exact hop where the perms lie — without having to su into a service account or spelunk through logs.
namei -lx collapses "who denied me at what layer?" from a six-command scavenger hunt into a one-command truth table.
