Namei: Tracing Path Traversal Hierarchies, Auditing Inode Permission Ladders, and Diagnosing Filesystem Access Denials in Production
How can a file that is completely open to the world remain stubbornly unreachable? The answer lies in a blind spot shared by almost every developer and system administrator. When something goes wrong on a Linux system, our muscle memory tells us to inspect the problematic file directly using standard tools like ls -la. But looking at a file in isolation is like inspecting the lock on an apartment door while ignoring the fact that the buildingβs front security gate downstairs is chained shut. In modern operating systems, files do not exist in a vacuum; they sit at the end of a long chain of parent directories, and an access block anywhere along that chain brings everything to an abrupt halt.
To diagnose these baffling failures, Linux includes a compact, surgical diagnostic utility named namei(1)βshort for "name-to-inode". Rather than examining just the destination file, namei steps through every single doorway the operating system must open to reach it, laying bare every directory, permission bit, user ownership, and symbolic link along the path.
The single most effective way to use the command during an outage is with the -mol flags:
namei -mol /var/log/nginx/access.log
When run, it outputs the complete traversal hierarchy in a neat, chronological ladder:
f: /var/log/nginx/access.log
Drwxr-xr-x root root /
drwxr-xr-x root root var
drwxr-xr-x root root log
drwxr-x--- root adm nginx
-rw-r----- www-data adm access.log
With one command, you see the entire journey from the root directory down to the target file. If a web server running as an unprivileged user cannot write to its own access log, this view immediately exposes why: the nginx directory restricts traversal to members of the adm group, blocking any outside process before it can ever touch access.log.
1. The Kernel Mechanics of POSIX Path Resolution
To understand why a world-readable file can remain blocked, one must understand how the Linux Virtual Filesystem (VFS) navigates storage. When an application asks the kernel to read, modify, or run a programβusing system calls such as openat(2), stat(2), or execve(2)βthe operating system does not leap directly to the destination. Instead, it performs a methodical, step-by-step walk down the directory hierarchy known as link_path_walk(), as documented in the Linux Programmer's Manual on path resolution.
mode: drwxr-xr-x
inode: 2"] --> Var["var/
mode: drwxr-xr-x
inode: 131073"] Var --> WWW["www/
mode: drwxr-x---
inode: 262145
FAILURE POINT (Traversal blocked for worker)"] WWW -. Traversal Blocked .-> Releases["releases/
mode: drwxr-xr-x
Never evaluated"] Releases -.-> Current["current (symlink)
mode: lrwxrwxrwx
Never evaluated"] Current -.-> App["app.js (target)
mode: -rwxrwxrwx
Never evaluated"]
The Invariable Rule of the Ancestral Ladder
Path resolution follows a strict, non-negotiable security invariant: to access, read, write, or execute any file or directory, a process must possess searchβmeaning execute (+x)βpermissions on every single ancestor directory in the entire chain.
In the Unix permission model, permissions on directories behave differently from permissions on regular files:
- The read (
r) bit allows a program to list the names of files inside a directory (via directory entry caches known as dentries). - The execute (
x) bit governs whether a program is permitted to step through the directory to reach items nested inside it.
If even one ancestral folderβwhether it is /home/deployer or /var/wwwβwithholds the execute bit from the process's user or group, the kernel terminates path resolution immediately with an EACCES (Permission denied) error. The kernel never inspects the destination file's permissions, because it was never able to reach it.
Symbolic Links and Recursion Safeguards
When the kernel encounters a symbolic link (a shortcut pointing to another location), it pauses its current journey, reads the destination path, and begins resolving that new path. Because shortcuts can point to further shortcuts or use relative parent steps (../), this introduces the danger of infinite loops.
To prevent malicious or accidental loops from locking up the system, the Linux kernel enforces an internal safety ceiling. As defined in symlink(7), the system sets a limit called MAXSYMLINKS (traditionally 40 consecutive symlink resolutions). If a path exceeds this limit, traversal halts with an ELOOP error ("Too many levels of symbolic links").
Manually untangling complex symlinks and multi-tiered directory permissions with standard commands requires dozens of tedious checks. The namei command emulates the kernel's path-resolution engine directly in user-space, outputting every mode bit, owner, symlink hop, and filesystem boundary in a single visual report.
2. What It Does in Plain English
The namei tool takes one or more file paths and walks them from the root directory down to the final target. At each step along the route, it inspects and prints the exact file type, permission bits, user ownership, and group ownership. Instead of requiring you to run ls -ld across half a dozen folders while mentally reconstructing relative links, namei draws the full ancestral ladder on your screen in a fraction of a second.
3. Core Flags and Quick Start
The utility includes several helpful flags to tailor its output during high-pressure troubleshooting:
| Flag | Long Option | Description |
|---|---|---|
-m |
--modes |
Displays the standard POSIX permission mode bits (e.g. drwxr-xr-x) for every path component |
-o |
--owners |
Appends user (UID) and group (GID) ownership attribution for each directory and file |
-l |
--long |
Formats output using a long listing format, explicitly displaying symlink targets |
-x |
--mountpoints |
Highlights mount point boundaries by marking them with an uppercase D instead of lowercase d |
-v |
--vertical |
Aligns mode bits and ownership attributes vertically for improved readability across deep paths |
-n |
--nosymlinks |
Halts traversal upon encountering a symbolic link without following it to its target |
Interpreting Output Characters
When reading namei reports, the leading character identifies the type of filesystem component:
| Indicator | Component Type | Meaning |
|---|---|---|
f: |
Path Target | The full target pathname being resolved |
d |
Directory | Standard directory |
D |
Mount Point | Directory acting as a distinct filesystem mount point (shown with -x or -l) |
l |
Symbolic Link | Symlink pointing to another location (followed by -> and target) |
- |
Regular File | Standard data file or executable |
s |
Socket | Unix domain communication socket |
b / c |
Device Node | Block device (such as a disk) or character device (such as a terminal) |
? |
Resolution Error | Inaccessible directory or missing path component |
4. Five Real-World Production Use Cases
Use Case 1: Triaging Web Server HTTP 403 Errors Across Ancestor Permission Ladders
The Scenario
A microservices backend running PHP-FPM under the unprivileged service user www-data fails immediately after an automated deployment. Web requests return 403 Forbidden, and the web server logs report: FastCGI sent in stderr: "Primary script unknown" while reading response header from upstream. The entrypoint script /home/deployer/apps/production/public/index.php has open 0644 (-rw-r--r--) permissions, and the PHP-FPM service is confirmed active.
Command Execution
namei -m -o /home/deployer/apps/production/public/index.php
Terminal Output
f: /home/deployer/apps/production/public/index.php
Drwxr-xr-x root root /
drwxr-xr-x root root home
drwx------ deployer deployer deployer
drwxr-xr-x deployer deployer apps
drwxr-xr-x deployer deployer production
drwxr-xr-x deployer deployer public
-rw-r--r-- deployer deployer index.php
Line-by-Line Technical Dissection
Drwxr-xr-x root root /anddrwxr-xr-x root root home: The root and/homedirectories grant world read and execute (r-x), allowing any system account to pass through them.drwx------ deployer deployer deployer: The failure point. The directory/home/deployeris set to octal0700. Only the userdeployeris allowed to enter or traverse this folder. Becausewww-datamatches neither the UID nor the GID ofdeployer, the kernel blocks the process here.drwxr-xr-x ... index.php: All subsequent child directories and the final file have open permissions, but they cannot be reached because the path ladder was severed at/home/deployer.
What the Administrator Does Next
The administrator must grant traversal (+x) on the blocking directory, or move the application to a standard location such as /srv or /var/www. To restore service immediately while keeping directory contents private:
# Grant traversal execution (+x) to others without allowing them to list files:
sudo chmod o+x /home/deployer
# Alternatively, add www-data to the deployer group and assign group traversal:
sudo usermod -aG deployer www-data
sudo chmod g+x /home/deployer
Use Case 2: Unraveling Multi-Hop Symlink Chains in Zero-Downtime Releases
The Scenario
A Node.js application relies on symlink-based zero-downtime deployments, where live web traffic points to /srv/app/live/dist/server.js. Following an automated rollback, the application supervisor crashes on launch with ENOENT: no such file or directory, open '/srv/app/live/dist/server.js'. Running ls /srv/app/live shows that the link exists, yet the runtime insists the file is missing.
Command Execution
namei -m -o -l /srv/app/live/dist/server.js
Terminal Output
f: /srv/app/live/dist/server.js
Drwxr-xr-x root root /
drwxr-xr-x root root srv
drwxr-xr-x nodeapp nodeapp app
lrwxrwxrwx nodeapp nodeapp live -> /srv/app/releases/current
drwxr-xr-x root root /
drwxr-xr-x root root srv
drwxr-xr-x nodeapp nodeapp app
drwxr-xr-x nodeapp nodeapp releases
lrwxrwxrwx nodeapp nodeapp current -> ../builds/2026-08-19_v2.4.0
drwxr-xr-x nodeapp nodeapp dist
-rw-r--r-- nodeapp nodeapp server.js
? ../builds/2026-08-19_v2.4.0: No such file or directory
Line-by-Line Technical Dissection
lrwxrwxrwx nodeapp nodeapp live -> /srv/app/releases/current: The initiallivelink successfully points to/srv/app/releases/current.lrwxrwxrwx nodeapp nodeapp current -> ../builds/2026-08-19_v2.4.0: The secondary link uses a relative path offset (../builds/...).? ../builds/2026-08-19_v2.4.0: No such file or directory: The failure point. The relative link evaluated from/srv/app/releases/expects a directory at/srv/app/builds/2026-08-19_v2.4.0. The rollback script cleaned up older build artifacts too aggressively, leaving a broken intermediate shortcut in the chain.
What the Administrator Does Next
The administrator repairs the broken link by pointing current to an existing release artifact:
# Verify available release directories
ls -d /srv/app/releases/*
# Atomically point the symlink to a valid release build
ln -sfn /srv/app/releases/2026-08-20_v2.3.9 /srv/app/releases/current
Use Case 3: Auditing UID/GID Ownership Drift Across Deep Multi-Tenant NFS Mounts
The Scenario
A Kubernetes worker node mounts a shared network storage volume over NFSv4 at /mnt/nfs_shares/analytics. A scheduled batch processing pod running under security UID 10005 fails when attempting to write checkpoint data to /mnt/nfs_shares/analytics/tenants/eu_west/q3_batch/checkpoints/data.parquet. The remote storage is shared across multiple domains, leading to identity mapping mismatches.
Command Execution
namei -x -m -o /mnt/nfs_shares/analytics/tenants/eu_west/q3_batch/checkpoints/data.parquet
Terminal Output
f: /mnt/nfs_shares/analytics/tenants/eu_west/q3_batch/checkpoints/data.parquet
Drwxr-xr-x root root /
drwxr-xr-x root root mnt
drwxr-xr-x root root nfs_shares
Drwxr-xr-x 10005 analytics analytics
drwxr-xr-x 10005 analytics tenants
drwxr-s--- nobody nogroup eu_west
drwxr-xr-x 10005 analytics q3_batch
drwxr-xr-x 10005 analytics checkpoints
-rw-r--r-- 10005 analytics data.parquet
Line-by-Line Technical Dissection
Drwxr-xr-x 10005 analytics analytics: The uppercaseDmarks the boundary where the local filesystem hands execution over to the network NFS driver.drwxr-s--- nobody nogroup eu_west: The failure point. Theeu_westsubdirectory has reverted tonobody:nogroupwith mode0750(drwxr-s---). This occurs when the NFS identity mapper (rpc.idmapd) fails to match domain usernames, defaulting unmapped objects to the anonymous user.- Because the directory restricts access exclusively to user
nobody, the pod's user (10005) cannot traverse intoq3_batch, making all nested folders unreachable despite having the correct ownership lower down.
What the Administrator Does Next
The administrator corrects the identity configuration and fixes ownership across the share:
# Correct ownership recursively across the tenant directory
sudo chown -R 10005:analytics /mnt/nfs_shares/analytics/tenants/eu_west
# Ensure correct group traversal and setgid bit propagation
sudo chmod 2775 /mnt/nfs_shares/analytics/tenants/eu_west
# Flush and refresh the NFS identity mapping cache
sudo nfsidmap -c
Use Case 4: Diagnosing Traversal Denials for Sandboxed systemd Services and Containers
The Scenario
A Prometheus metrics exporter managed by systemd fails to read custom application metrics written to /var/log/app-telemetry/metrics/system.prom. The systemd service unit isolates the process using modern security hardening:
[Service]
User=node-exporter
DynamicUser=yes
ProtectSystem=strict
The system log records open "/var/log/app-telemetry/metrics/system.prom": Permission denied, even though the metrics file itself has standard world-readable permissions (0644).
Command Execution
namei -m -o /var/log/app-telemetry/metrics/system.prom
Terminal Output
f: /var/log/app-telemetry/metrics/system.prom
Drwxr-xr-x root root /
drwxr-xr-x root root var
drwxr-xr-x root root log
drwxr-x--- appuser appgrp app-telemetry
drwxr-xr-x appuser appgrp metrics
-rw-r--r-- appuser appgrp system.prom
Line-by-Line Technical Dissection
drwxr-x--- appuser appgrp app-telemetry: The logging directory was created with mode0750(rwxr-x---), restricting entry exclusively toappuserand members ofappgrp.- As detailed in the
systemd.exec(5)documentation,DynamicUser=yesassigns an ephemeral UID from the range61184β65519whenever the service starts. - Because this temporary user does not belong to
appgrp, the kernel refuses to let it enter/var/log/app-telemetry.
What the Administrator Does Next
Rather than making the entire logging directory public, the administrator applies a targeted POSIX Access Control List (ACL) to grant search/traversal access specifically to the service user, following best practices from the ArchWiki File Permissions Guide:
# Grant traversal access on the ancestor directory to the service user
sudo setfacl -m u:node-exporter:--x /var/log/app-telemetry
# Verify that the ACL bit (+) now permits path traversal
namei -m /var/log/app-telemetry/metrics/system.prom
Use Case 5: Automated Pre-Deployment Path Validation Within CI/CD Release Pipelines
The Scenario
An automated blue/green deployment script unpacks release bundles on production servers. In roughly 5% of runs, background umask inconsistencies during build compilation leave parent directories with restrictive permissions (0700), causing immediate outages when live traffic switches over. The engineering team requires an automated pre-flight check that validates the path structure before activating new releases.
Command Execution & Validation Script
The engineer adds an automated path verification step using namei into the release script:
#!/usr/bin/env bash
set -euo pipefail
TARGET_PATH="/srv/http/production/releases/rev-8f3b2a/public/index.html"
EXPECTED_USER="http-worker"
echo "Auditing POSIX path resolution ladder for: ${TARGET_PATH}"
# Execute namei with mode and owner flags
OUTPUT=$(namei -mol "${TARGET_PATH}")
echo "${OUTPUT}"
# Check for resolution failure indicators ('?' denotes inaccessible or missing nodes)
if echo "${OUTPUT}" | grep -q '^[[:space:]]*?'; then
echo "CRITICAL ERROR: Broken symlink or missing path component detected in ladder." >&2
exit 1
fi
# Ensure every directory in the path ladder has world or group execute permissions
# Flag any directory matching 'd[r-][w-]-' where both group and other execute are missing
if echo "${OUTPUT}" | grep -E '^[[:space:]]*d[rwx-]{2}-[rwx-]{2}-'; then
echo "SECURITY AUDIT FAILED: Ancestor directory found lacking traversal (+x) bits." >&2
exit 1
fi
echo "Path verification passed: All ancestral directories permit traversal."
Terminal Output (Caught During Pipeline Execution)
Auditing POSIX path resolution ladder for: /srv/http/production/releases/rev-8f3b2a/public/index.html
f: /srv/http/production/releases/rev-8f3b2a/public/index.html
Drwxr-xr-x root root /
drwxr-xr-x root root srv
drwxr-xr-x root root http
drwxr-xr-x root root production
drwxr-xr-x deploy deploy releases
drwx------ deploy deploy rev-8f3b2a
drwxr-xr-x deploy deploy public
-rw-r--r-- deploy deploy index.html
SECURITY AUDIT FAILED: Ancestor directory found lacking traversal (+x) bits.
Line-by-Line Technical Dissection
- The script parses the output generated by
namei -mol. - The entry
drwx------ deploy deploy rev-8f3b2atriggers the pattern check^[[:space:]]*d[rwx-]{2}-[rwx-]{2}-, detecting that both the group and world execute bits are missing (-). - The script flags the failure and halts the release process before symlinks are switched, completely preventing production downtime.
What the Administrator Does Next
The deployment playbook includes a directory-only normalization step to guarantee consistent traversal permissions across all generated release directories:
# Normalize directory permissions recursively without changing file permissions
find /srv/http/production/releases/rev-8f3b2a -type d -exec chmod 755 {} +
5. What Can Go Wrong: Pitfalls and Caveats
While namei provides clarity during an investigation, there are three important operational scenarios where user-space path inspection can diverge from runtime reality.
Pitfall 1: Execution Context and Group Privileges
namei evaluates paths from the perspective of the user executing the command, not the background application. If you run sudo namei /path/to/file, the tool runs with root administrative capabilities (CAP_DAC_OVERRIDE and CAP_DAC_READ_SEARCH). As root, the kernel bypasses normal permission checks, meaning namei will traverse directories that would block an unprivileged web process like www-data or nobody.
namei -mo, rather than relying solely on whether namei itself completes without displaying a ? error.Pitfall 2: Mount Namespaces and Container Isolation
Modern container engines and systemd services rely on mount namespaces (CLONE_NEWNS) and filesystem isolation options (such as PrivateTmp=yes or ProtectHome=yes).
If you run namei on the main host system against /tmp/app.sock, you are inspecting the host's filesystem view. An application inside a container or sandboxed service may see an entirely different directory mounted at /tmp. To audit path resolution accurately inside isolated environments, execute namei within the target process namespace:
# Run namei directly inside the mount namespace of a running process PID
sudo nsenter --target <PID> --mount -- namei -mol /path/to/target/file
Pitfall 3: Mandatory Access Control (SELinux / AppArmor) Masking
namei inspects standard POSIX Discretionary Access Control (DAC) attributes. It does not evaluate Linux Security Module (LSM) security policies, such as SELinux types or AppArmor profiles. If namei -mol shows correct 0755 permissions and matching ownership, yet an application still receives Permission denied, the block is frequently caused by a security policy rule.
# Search SELinux audit logs for security profile denials
sudo ausearch -m avc -ts recent
6. Today's Takeaway
Open a terminal on your computer right now and run namei -mol /etc/ssl/certs or point it at your project directory with namei -moxl <path>. In five minutes, you will see the exact ancestral permission ladder your operating system climbs, reveal any hidden mount points along the way, and gain an intuitive understanding of how Linux resolves paths from the root inode downβarming you with the fastest diagnostic tool against Permission denied errors.