Linux Error Guide: 'Too many levels of symbolic links' — break the symlink loop
Fix 'Too many levels of symbolic links' (ELOOP): find and break circular or self-referencing symlinks, spot bind-mount loops, and safely repair broken link chains on Linux.
- #linux
- #troubleshooting
- #errors
- #filesystem
Stuck on this Linux Admins error? Get the free incident triage checklist
A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.
Overview
Accessing a file or directory fails, and the tool reports that it followed too many symbolic links while resolving the path:
ls: cannot access '/opt/app/current': Too many levels of symbolic links
The raw errno behind it is ELOOP, and you will also see it phrased by the C library as:
bash: cd: /opt/app/current: Too many levels of symbolic links
Linux caps how many symlinks it will follow when resolving a single path (typically 40). When a symlink points — directly or through a chain — back to itself, the kernel hits that limit and returns ELOOP rather than looping forever. The error is protecting you from an infinite resolution loop; the fix is to find and break the loop.
Symptoms
ls,cd,cat,stat, or an application fails on one specific path with “Too many levels of symbolic links”.- The path involves a symlink (often a
current/latestpointer in a deploy layout) that was just changed. readlink -f <path>also errors instead of printing a resolved target.ls -lon the link shows it pointing at itself or at another link that points back.- Only the looping path is affected; unrelated files on the same filesystem work fine.
Common Root Causes
- A self-referencing symlink —
ln -s current currentor a relative target that resolves to the link itself. - A circular chain —
a -> b,b -> c,c -> a, common after a scripted deploy re-points links in the wrong order. - A bad relative target — a symlink using
.or a relative path that, from its own directory, points back at itself. - A botched atomic-deploy swap where the
currentsymlink was repointed to a directory that itself contains a link back tocurrent. - Bind mounts or overlay setups that create a directory that mounts onto a path inside itself, producing a loop during resolution.
- A restored/rsync’d tree that copied symlinks with absolute targets that no longer match the new layout.
Diagnostic Workflow
Inspect the link itself without following it (ls/readlink on the link, not through it):
ls -l /opt/app/current # shows "current -> current" or the next hop
readlink /opt/app/current # one level: the literal target
readlink -f /opt/app/current # full resolution (will error on a true loop)
Walk the chain hop by hop to locate where it closes:
namei -l /opt/app/current # prints every component and each symlink target
namei output for a loop makes the cycle obvious:
f: /opt/app/current
l current -> releases/active
l active -> ../current
Find self-referencing or clearly broken links under a tree:
find /opt/app -maxdepth 2 -type l -exec sh -c 'readlink -f "$1" >/dev/null 2>&1 || echo "BROKEN/LOOP: $1 -> $(readlink "$1")"' _ {} \;
If you suspect a mount loop rather than a plain symlink, check the mount table:
findmnt -R /opt/app
mount | grep -i /opt/app
Example Root Cause Analysis
A deploy script used the standard current -> releases/<timestamp> pattern for atomic releases. After a release, the app started returning 500s and cd /srv/app/current failed with Too many levels of symbolic links. Inspecting the link:
ls -l /srv/app/current
lrwxrwxrwx 1 deploy deploy 7 Jul 6 02:14 current -> current
The deploy step meant to run ln -sfn releases/20260706021400 current from inside /srv/app, but a variable expanded empty, so the command became ln -sfn current current — the link now pointed at itself. Because ln -sfn replaces the existing link in place, the loop was created atomically with no error at deploy time. The fix was to re-point it correctly and force-replace the loop:
cd /srv/app
ln -sfn releases/20260706021400 current
readlink -f current
/srv/app/releases/20260706021400
The lasting fix was to make the deploy script fail fast when the release variable is empty (set -u plus a guard) so ln can never be handed a target equal to the link name.
Prevention Best Practices
- In deploy scripts, use
set -euo pipefailand validate that the symlink target variable is non-empty before callingln. - Always create/replace pointer symlinks with
ln -sfn <target> <link>and never let the target equal the link name. - Prefer absolute or clearly-scoped relative targets, and verify with
readlink -fimmediately after creating a link. - Add a post-deploy health check that runs
readlink -f current(ortest -d current) and rolls back if resolution fails. - Keep atomic-swap logic in one reviewed helper rather than inline
lncalls scattered across scripts. - When copying trees with rsync/tar, decide deliberately between preserving symlinks (
-l) and dereferencing them (-L) so targets stay valid in the new location.
Quick Command Reference
# Inspect the link without following it
ls -l <path>
readlink <path> # one hop
readlink -f <path> # full resolution (errors on a loop)
# Trace every component and target
namei -l <path>
# Find broken or looping symlinks under a tree
find <dir> -type l -exec sh -c \
'readlink -f "$1" >/dev/null 2>&1 || echo "BAD: $1 -> $(readlink "$1")"' _ {} \;
# Rule out a mount loop
findmnt -R <path>
# Correctly (re)point a deploy symlink
ln -sfn <real-target> <link>
Conclusion
“Too many levels of symbolic links” (ELOOP) means the kernel gave up resolving a path because a symlink pointed back into its own chain — a safety limit, not a disk fault. Inspect the link itself with ls -l and readlink (never through it), then use namei -l to see exactly where the cycle closes, and re-point the offending link with ln -sfn. Most real-world cases come from deploy scripts that fed ln an empty or self-referential target, so the durable fix is a fail-fast script that validates its target and a post-deploy check that confirms readlink -f resolves before traffic is sent.
Fixed it? Get 500 Linux Admins & DevOps AI prompts — free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.
Did this fix your issue?
Get 500 Battle-Tested DevOps AI Prompts — Free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.