Hard Links, Symbolic Links, and Inodes
Links become simple once names and objects are separated. A directory entry maps a name to an inode; a hard link adds another mapping to the same inode, while a symbolic link is a distinct object containing a pathname.
Learning objectives
By the end of this lesson
- Explain how directory entries and inodes represent regular files.
- Create and inspect hard links that share one inode and data stream.
- Create relative and absolute symbolic links and resolve their targets.
- Predict behavior when original names, hard links, targets, or symbolic links are removed.
- Apply a symbolic-link release-switch pattern with verification and rollback awareness.
1. Names live in directories; file metadata lives in inodes
A directory stores mappings from names to filesystem objects. For many Unix-style filesystems, a regular file’s inode contains metadata and references to its data. The filename is not stored as the file’s unique identity; it is a directory entry that points to the inode.
flowchart TD D1["directory entry: config"] --> I["inode 4821"] D2["directory entry: config.backup"] --> I I --> B["file data blocks"] S["symlink inode"] --> P["stored path: config"] P -. resolved through directory .-> D1
An inode number alone is not globally unique. The filesystem device and inode together identify an object at a point in time, and inode numbers may be reused after deletion.
2. A hard link is another directory entry for the same inode
ln existing new-name creates a hard link. Both
pathnames then refer to the same inode, metadata, and content.
Neither name is inherently the “original.” Editing through one name
is visible through the other.
lab="$HOME/devops-academy/linux/chapter04/lesson03"
rm -rf -- "$lab"
mkdir -p -- "$lab"
printf 'version=1\n' > "$lab/config"
ln -- "$lab/config" "$lab/config.backup"
ls -li -- "$lab/config" "$lab/config.backup"
stat -c 'path=%n device=%d inode=%i links=%h size=%s' -- \
"$lab/config" "$lab/config.backup"
printf 'feature=true\n' >> "$lab/config.backup"
printf '\nRead through first name:\n'
cat -- "$lab/config"
3. A symbolic link is a separate object containing a pathname
ln -s target link-name creates a symbolic link. The
stored target may be relative or absolute. When a process opens the
link normally, pathname resolution follows the stored text from the
link’s directory for a relative target.
mkdir -p -- "$lab/releases/1.0.0" "$lab/releases/1.1.0"
printf '1.0.0\n' > "$lab/releases/1.0.0/VERSION"
printf '1.1.0\n' > "$lab/releases/1.1.0/VERSION"
# Relative target: portable when the whole parent tree moves together.
ln -s -- releases/1.0.0 "$lab/current"
ls -ld -- "$lab/current"
printf 'stored target: '
readlink -- "$lab/current"
printf 'resolved target: '
realpath -- "$lab/current"
printf 'active version: '
cat -- "$lab/current/VERSION"
Target interpreted from link directory
Often preferred inside a self-contained application tree because moving the parent tree preserves relationships.
Target starts at filesystem root
Unambiguous on one host, but can break after chroot, mount, container, or directory relocation changes.
Stored target cannot be resolved
The link object exists even though normal access through it fails.
Resolution cycles between links
The kernel stops after a bounded number of symbolic-link traversals and returns an error.
4. Inspect the link itself and the resolved target separately
Many commands follow symbolic links by default in some contexts and
inspect the link itself in others. State your intent. GNU
stat inspects the link itself by default and follows it
with -L or --dereference.
printf 'Symbolic-link object:\n'
stat -- "$lab/current"
printf '\nResolved target object:\n'
stat -L -- "$lab/current"
printf '\nCompact comparison:\n'
stat -c 'no-follow: type=%F inode=%i links=%h name=%n -> %N' -- "$lab/current"
stat -Lc 'follow: type=%F inode=%i links=%h name=%n' -- "$lab/current"
readlink prints stored link text.
realpath resolves components and normally requires a
resolvable target. During incident analysis, capture both: the
configured reference and where it resolves now.
5. Unlinking a name does not necessarily remove file data immediately
Removing one hard-link pathname decrements the link count. The inode and data remain while another hard link exists. Even after the final pathname is unlinked, an already-open file can remain accessible to the process holding its file descriptor until that descriptor closes.
printf 'Before unlink:\n'
stat -c 'path=%n inode=%i links=%h' -- "$lab/config" "$lab/config.backup"
rm -- "$lab/config"
printf '\nAfter removing one name:\n'
stat -c 'path=%n inode=%i links=%h' -- "$lab/config.backup"
cat -- "$lab/config.backup"
rm -- "$lab/releases/1.0.0/VERSION"
printf '\nDangling symbolic link evidence:\n'
ls -ld -- "$lab/current"
if ! test -e "$lab/current"; then
printf 'target is not resolvable, but link object still exists\n'
fi
test -L "$lab/current" && printf 'link object exists\n'
A large log can be unlinked yet continue consuming space while a
service keeps it open. Tools such as lsof +L1 can
reveal open files whose link count is zero.
6. Symbolic links as release selectors
A common layout stores immutable release directories and points a
stable name such as current to the active release. A
new link can be constructed and then renamed into place, reducing
the window in which readers could observe an incomplete update.
root="$lab"
next_target='releases/1.1.0'
temporary_link="$root/.current.next"
rm -f -- "$temporary_link"
ln -s -- "$next_target" "$temporary_link"
# Verify before switching.
test "$(cat "$temporary_link/VERSION")" = '1.1.0'
readlink -- "$temporary_link"
# Rename the prepared link over the stable selector.
mv -Tf -- "$temporary_link" "$root/current"
printf 'new stored target: %s\n' "$(readlink "$root/current")"
printf 'new active version: %s\n' "$(cat "$root/current/VERSION")"
The exact atomicity and overwrite behavior depend on platform and filesystem semantics. Keep old release directories until health checks pass so rollback can repoint the selector.
7. Hands-on lab: prove link behavior step by step
set -u
root="$HOME/devops-academy/linux/chapter04/lesson03-proof"
rm -rf -- "$root"
mkdir -p -- "$root/data"
printf 'payload-a\n' > "$root/data/object"
ln -- "$root/data/object" "$root/data/object.hard"
ln -s -- data/object "$root/object.symbolic"
report="$root/link-report.txt"
{
printf '=== initial identities ===\n'
stat -c '%n device=%d inode=%i links=%h type=%F' -- \
"$root/data/object" "$root/data/object.hard" "$root/object.symbolic"
printf '\n=== symlink target ===\n'
readlink -- "$root/object.symbolic"
realpath -- "$root/object.symbolic"
printf '\n=== edit through hard-link name ===\n'
printf 'payload-b\n' >> "$root/data/object.hard"
cat -- "$root/data/object"
printf '\n=== remove first hard-link name ===\n'
rm -- "$root/data/object"
stat -c '%n inode=%i links=%h' -- "$root/data/object.hard"
printf '\n=== symlink after target-name removal ===\n'
test -L "$root/object.symbolic" && printf 'link object=yes\n'
test -e "$root/object.symbolic" || printf 'resolved target=no\n'
} > "$report"
cat -- "$report"
Verification checklist
8. Common link mistakes
Calling one hard link the original
All hard-link names are peers that reference the same inode.
Creating the wrong relative target
A relative symlink target is interpreted from the directory containing the link, not from the shell’s current directory during later access.
Assuming removing a symlink removes its target
rm link removes the link object. It does not
normally remove the resolved target.
Following links unintentionally in automation
Archivers, copy tools, find operations, and security checks have different link-following options. Choose them explicitly.
9. Knowledge check
Question 1. What do two hard-link names share?
Question 2. From where is a relative symbolic-link target interpreted?
Question 3. How can a symbolic link exist while
test -e link reports false?
test -L checks the
link object itself.
10. Summary
Directories map names to filesystem objects. Hard links are multiple directory entries for one inode, so they share content and metadata. Symbolic links are separate objects that store pathnames and can cross filesystems or become dangling. Correct inspection distinguishes the link object from its resolved target, and reliable release switching verifies a prepared link before publishing it.
11. Further reading
- GNU Coreutils manuals for ln, readlink, realpath, stat, mv, rm, and ls.
- Linux manual pages for link, symlink, unlink, rename, inode, path_resolution, and open.
- POSIX specifications for link, symlink, readlink, and pathname resolution.
- Filesystem documentation for hard-link limits, rename semantics, and inode allocation.
- lsof documentation — inspection of open, unlinked files.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.