Linux Basic — Hard Links and Symlinks
Hard Links and Symlinks
Linux lets you create two kinds of links between filenames and files: hard links and symbolic links (symlinks). They look similar but behave very differently under the hood.
Hard Links
A hard link is a second directory entry pointing to the same inode. Both names are equal — neither is more “original” than the other.
# Create a hard link
ln source.txt hardlink.txt
# Verify: both files share the same inode number
ls -li source.txt hardlink.txt
# 2621441 -rw-r--r-- 2 ubuntu ubuntu 42 Apr 1 10:00 hardlink.txt
# 2621441 -rw-r--r-- 2 ubuntu ubuntu 42 Apr 1 10:00 source.txt
# ^^^^^^^ same inode!
# Link count = 2 (two directory entries point to this inode)
# Editing either file changes both (same underlying data)
echo "appended" >> source.txt
cat hardlink.txt
# appended
# Deleting one does NOT remove the data — inode persists until link count = 0
rm source.txt
cat hardlink.txt # Still works!
ls -li hardlink.txt
# 2621441 -rw-r--r-- 1 ubuntu ubuntu 51 Apr 1 10:00 hardlink.txt
# Link count is now 1
Hard Link Limitations
# 1. Cannot hard link across different filesystems
ln /tmp/file.txt /home/ubuntu/file.txt # Works (same filesystem)
ln /etc/hostname /mnt/usb/hostname # ERROR: Invalid cross-device link
# 2. Cannot hard link a directory (prevents filesystem loops)
ln /home/ubuntu /home/ubuntu-link # ERROR: hard link not allowed for directory
Symbolic Links (Symlinks)
A symlink is a special file that stores a path string pointing to a target. It is like a shortcut or alias.
# Create a symlink
ln -s /path/to/target linkname
# Example: symlink to a config file
ln -s /etc/nginx/sites-available/mysite /etc/nginx/sites-enabled/mysite
# Example: symlink to a directory
ln -s /opt/myapp-v2.1 /opt/myapp-current
# Inspect a symlink
ls -l /opt/myapp-current
# lrwxrwxrwx 1 root root 15 Apr 1 10:00 /opt/myapp-current -> /opt/myapp-v2.1
# ^
# └── l means symlink
# ls -la shows the target with an arrow
Symlinks and Inodes
ls -li /opt/myapp-current /opt/myapp-v2.1 2>/dev/null
# The symlink has its OWN inode number (it is its own file)
# The symlink's content is the path string "/opt/myapp-v2.1"
# Following the symlink reads/writes the target's data
stat /opt/myapp-current
# File: /opt/myapp-current -> /opt/myapp-v2.1
# Size: 15 ← length of the path string
# ...
Comparing Hard Links vs Symlinks
| Feature | Hard Link | Symbolic Link |
|---|---|---|
| Same inode | ✅ Yes | ❌ No (own inode) |
| Cross filesystem | ❌ No | ✅ Yes |
| Link directories | ❌ No | ✅ Yes |
| Survives target deletion | ✅ Yes (data stays) | ❌ No (becomes dangling) |
Shows with ls -l |
Looks like normal file | Shows -> arrow |
| Relative path support | N/A | ✅ Yes (relative or absolute) |
| Use case | Backup, log rotation | Deployment aliases, config enables |
Dangling Symlinks
A symlink becomes dangling when its target no longer exists:
# Create a symlink and then remove the target
ln -s /tmp/missing.txt /tmp/mylink.txt
rm /tmp/missing.txt # (or it never existed)
# Dangling symlink — ls shows it in RED in most terminals
ls -la /tmp/mylink.txt
# lrwxrwxrwx 1 ubuntu ubuntu 15 Apr 1 10:00 /tmp/mylink.txt -> /tmp/missing.txt
# Reading a dangling symlink fails
cat /tmp/mylink.txt
# cat: /tmp/mylink.txt: No such file or directory
# Find all dangling symlinks in a directory
find /tmp -xtype l
# /tmp/mylink.txt
# -xtype l = "type is symlink but the target does NOT exist or is a different type"
# Compare: -type l finds ALL symlinks (broken or not)
find /tmp -type l
Resolving Symlinks
# readlink — print the target of a symlink
readlink /usr/bin/python3
# python3.10
# readlink -f — resolve the FULL canonical path (follow all symlinks)
readlink -f /usr/bin/python3
# /usr/bin/python3.10
readlink -f /bin
# /usr/bin (because /bin -> usr/bin on modern Ubuntu)
# realpath — same as readlink -f, more readable name
realpath /usr/bin/python3
# /usr/bin/python3.10
Updating a Symlink
Use ln -sfn to atomically replace a symlink:
# -s: create symlink
# -f: force (overwrite if exists)
# -n: treat existing symlink as a file (not the target directory)
ln -sfn /opt/myapp-v2.1 /opt/myapp-current # Atomically updates the alias
This pattern is the core of zero-downtime deployments — the application always reads /opt/myapp-current, and you just flip which version it points to.
The Versioned Deployment Pattern
# Deploy structure:
# /opt/
# ├── myapp-v1.0/ (old version)
# ├── myapp-v2.0/ (current version)
# ├── myapp-v2.1/ (new version, just uploaded)
# └── myapp-current -> myapp-v2.0 (active symlink)
# Deploy new version atomically
sudo ln -sfn /opt/myapp-v2.1 /opt/myapp-current
# Rollback is instant
sudo ln -sfn /opt/myapp-v2.0 /opt/myapp-current
# The running app reads /opt/myapp-current — it picks up the new version
# immediately without restart (for interpreted languages and config changes)
# Common in:
# - nginx configs (sites-available → sites-enabled)
# - Ruby on Rails Capistrano deployments
# - Python virtualenv management
# - Java JAR versioning
Practical Examples
# Enable an nginx site
sudo ln -s /etc/nginx/sites-available/mysite /etc/nginx/sites-enabled/mysite
# Disable it (remove the symlink only — config file stays)
sudo rm /etc/nginx/sites-enabled/mysite
# Create a convenient alias for a deep path
ln -s /var/log/nginx /home/ubuntu/logs
# Find all symlinks in /usr/bin
find /usr/bin -type l | head -10
# /usr/bin/python3 -> python3.10
# /usr/bin/awk -> gawk
# ...
# Find dangling symlinks anywhere in /opt
find /opt -xtype l 2>/dev/null
Key Takeaways
ln source linkcreates a hard link — same inode, cannot cross filesystems or link directoriesln -s target linkcreates a symlink — a file containing a path string, can cross filesystems and link directories- When a symlink’s target is removed, the symlink becomes dangling (broken) —
find . -xtype lfinds them readlink -fandrealpathresolve symlinks to their canonical absolute pathln -sfn /new-target /symlinkatomically updates a symlink — used in zero-downtime deployments- Nginx uses symlinks to enable/disable sites:
sites-available/→sites-enabled/
| ← Back to File Types and Inodes | Next → Disk Usage: df and du |