Trace Symlinks and Permissions with namei
A deploy script says "permission denied" on a path that looks fine when you ls it, and the real problem is three directories up. namei walks a pathname one component at a time, symlink by symlink, so the broken link or the locked-down directory stops hiding. Five minutes, an ordinary user account, and nothing here writes to your filesystem or needs sudo.
The route
Jump straight to the step you need, or tick off Done means at the end.
1. Confirm the installed command
Check which version is actually running before you compare output with another machine. The command and its man page can come from different package revisions, so this is worth recording first:
namei --version
man namei
On the machine used for this guide, the executable reports util-linux 2.41.3. The installed man page identifies its source as util-linux 2.39.3. The pathname resolution model and options used here are documented by that man page, but formatting can vary between util-linux releases.
2. Build a harmless path to inspect
Create a small directory tree containing a directory link and a file link. Keep the temporary directory name in a shell variable so cleanup is precise:
namei_demo=$(mktemp -d /tmp/namei-demo.XXXXXX)
mkdir -p "$namei_demo/real/logs"
printf 'sample\n' > "$namei_demo/real/logs/app.log"
ln -s real "$namei_demo/current"
ln -s logs/app.log "$namei_demo/current-log"
printf '%s\n' "$namei_demo"
The first link makes current/logs/app.log pass through real. The second link points directly to a relative target. Relative symlink targets are interpreted from the directory containing the link, not from your current working directory; that distinction explains many apparently nonsensical failures.
3. Follow every component
Ask namei to resolve the file through the directory link, while displaying modes and owners:
namei -l "$namei_demo/current/logs/app.log"
Output starts with an f: line for the pathname being resolved. Each following line represents a component. A leading d identifies a directory, l a symbolic link, and - a regular file. With -l, the output also includes mode bits, owner and group, with columns aligned for reading.
f: /tmp/namei-demo.XXXXXX/current/logs/app.log
drwxr-xr-x root root /
drwxrwxrwt root root tmp
drwx------ alice alice namei-demo.XXXXXX
lrwxrwxrwx alice alice current -> real
drwxr-xr-x alice alice real
drwxr-xr-x alice alice logs
-rw-r--r-- alice alice app.log
The exact temporary directory name and ownership depend on your account. The useful line is usually the first unexpected one: a link whose target is not what you expected, a directory without search permission, or a final component that is not the type you assumed.
4. Inspect a link without following it
Use -n when you need to examine the link itself rather than its destination:
namei -n -l "$namei_demo/current-log"
--nosymlinks stops link traversal. This is useful when checking deployment output: it tells you that current-log is a link and shows its stored target, without treating the target's components as the path you asked about. A missing destination is therefore not hidden by a successful-looking resolution.
Without -n, the ordinary command follows links and reports the endpoint. If a link target is missing, namei prints the component it cannot resolve and returns a failure status. Do not confuse that diagnostic with a permission denial: check the component named in the output before changing ownership or modes.
5. Add context when diagnosing access
Reach for the focused display options once the basic trace has already pointed at a suspicious component:
namei -m "$namei_demo/current/logs/app.log"
namei -o "$namei_demo/current/logs/app.log"
namei -x "$namei_demo/current/logs/app.log"
namei -Z "$namei_demo/current/logs/app.log"
-mshows mode bits for every component.-oshows owner and group instead.-xmarks mountpoint directories withDinstead ofd, useful when the trap is a filesystem boundary rather than a permission.-Zasks for the security context, showing?where context support is unavailable. It is not a substitute for checking the policy that governs access, and no option here changes that policy.
-l is shorthand for -m -o -v: modes, owners and vertical alignment. If you only need one of those details, using the narrower option makes command output easier to scan and easier to capture in a ticket.
6. Verify and clean up
Confirm that the endpoint is the file you created, then remove only the temporary tree:
test -f "$namei_demo/real/logs/app.log" && printf 'endpoint exists\n'
rm -rf -- "$namei_demo"
unset namei_demo
The final command is the only destructive example. It removes the temporary directory created by the guide, not a system path. If you used a different value for the variable, stop and inspect it with printf '%s\n' "$namei_demo" before running the removal. If you are investigating a real path, omit this cleanup block.
Common traps
- A pathname is more than its final filename. Access normally requires search permission on every directory component, so inspect the whole trace.
- The
f:marker is not a file-type claim. It means the pathname currently being resolved, and the final type can be a directory, device node, socket or FIFO. - A leading
?marks an error of some kind. Read the message beside the component; do not infer that every error is a mode-bit problem. - Do not add
sudoreflexively. It can make a root-owned path look accessible while hiding the permissions your service account actually has. Use the real account's view when diagnosing a service. - Use one pathname per test while learning the output. The command accepts multiple pathnames, but separate runs make it harder to attribute a failure to the wrong path.
Done means
- Traced: you can identify every directory, link and endpoint in a pathname trace.
- Fluent: you know when to use
-n,-m,-o,-x,-Zand-l. - Diagnosed properly: you checked the component named by an error instead of changing permissions blindly.
- Cleaned up: your temporary test tree is removed, or you deliberately kept it for further inspection.