Linux has two kinds of link and they are not variations on the same idea — they work at different levels of the filesystem. Understanding the difference takes one paragraph about inodes, and after that everything else follows.
Filenames are not files
A file’s actual data and metadata live in an inode, identified by a number. A directory entry is just a name pointing at an inode number. The name is not the file; it is a label attached to it.
Once you accept that, both link types make sense:
- A hard link is another name pointing at the same inode. Two names, one file, equal standing — neither is the “original”.
- A symbolic link is a small file of its own whose contents are a path. Following it means resolving that path, whatever happens to be there.
$ ls -li
1310721 -rw-r--r-- 2 kevin kevin 42 Aug 23 10:14 notes.txt
1310721 -rw-r--r-- 2 kevin kevin 42 Aug 23 10:14 hardlink.txt
1310745 lrwxrwxrwx 1 kevin kevin 9 Aug 23 10:15 symlink.txt -> notes.txt-i shows inode numbers. Note that notes.txt and hardlink.txt share inode 1310721 and both show a link count of 2. The symlink has its own inode, a size of 9 bytes (the length of the path it stores), and an l file type.
Creating them
ln target linkname # hard link
ln -s target linkname # symbolic linkThe argument order is the thing that trips everyone up. It is the same order as cp: what exists first, what you are creating second. Getting it backwards produces a link named after your target pointing at nothing.
ln -s /opt/app/releases/v2.1 /opt/app/current # correct
ln -s /opt/app/current /opt/app/releases/v2.1 # backwards, and now confusingOmit the second argument and the link is created in the current directory using the target’s basename — handy once you trust yourself.
Which to use
| Hard link | Symbolic link | |
|---|---|---|
| Points to | An inode | A path, as text |
| Across filesystems | No | Yes |
| To a directory | No (except . and ..) | Yes |
| Survives the target being renamed | Yes | No — becomes broken |
| Survives the target being deleted | Yes — data persists | No — becomes broken |
| Can point at nothing | No | Yes (a dangling link) |
Visible in ls -l | Only as a link count | Yes, with -> |
| Extra disk used | None | One small inode |
In practice, use symbolic links. They can cross filesystems, work on directories, and are visible — anyone reading ls -l can see what is going on. Hard links are for specific jobs: deduplicating identical files, and backup schemes that snapshot by linking unchanged files rather than copying them.
The relative path trap
A symlink stores exactly the text you typed. If that text is a relative path, it is resolved relative to the symlink’s own directory, not to where you were standing when you created it.
$ cd /home/kevin
$ ln -s notes.txt /tmp/mylink
$ ls -l /tmp/mylink
lrwxrwxrwx 1 kevin kevin 9 Aug 23 10:20 /tmp/mylink -> notes.txt
$ cat /tmp/mylink
cat: /tmp/mylink: No such file or directoryThe link points at /tmp/notes.txt, which does not exist. Nothing warned you, because ln -s does not check that the target exists — that is a feature, since it lets you create a link before its target.
Use absolute paths unless you specifically want a relative one, or let ln work it out for you:
ln -s /home/kevin/notes.txt /tmp/mylink # absolute: unambiguous
ln -sr /home/kevin/notes.txt /tmp/mylink # -r computes a correct relative pathRelative links are the right choice when the whole tree might move together — inside a project directory, or a structure that gets copied elsewhere.
Updating a symlink
Pointing an existing link somewhere new has one sharp edge that has broken a lot of deployments.
ln -sf /opt/app/releases/v2.2 /opt/app/currentIf current is an existing symlink to a directory, -f follows it and creates the new link inside that directory — you end up with /opt/app/releases/v2.1/v2.2 and the deployment silently pointing at the old release.
The fix is -n, which treats the destination as a file rather than descending into it:
ln -sfn /opt/app/releases/v2.2 /opt/app/currentln -sfn is the invocation to memorise for anything that repoints a directory symlink. For a zero-downtime swap, create the new link under a temporary name and mv -T it into place — a rename is atomic, whereas delete-then-create leaves a window with no link at all.
Inspecting and finding links
readlink mylink # what the link literally contains
readlink -f mylink # the fully resolved final destination
stat notes.txt # includes the inode and link count
ls -li # inode numbers alongside the listing
find /path -type l # every symlink
find /path -xtype l # broken symlinks only
find / -samefile /path/to/file # all hard links to one file
find /path -type l -exec ls -l {} + # symlinks with their targetsfind -xtype l is worth running occasionally on anything you maintain — broken symlinks accumulate quietly and only announce themselves when something tries to follow one. See find for the rest.
Where you will actually meet them
- Deployments.
current -> releases/v2.2, repointed atomically. Rolling back is oneln -sfn. - Dotfiles. Keep them in a git repository and symlink them into
$HOME, so your configuration is version-controlled. - Alternatives.
/usr/bin/python3and/usr/bin/editorare symlinks the system manages for you. - Shared libraries.
libfoo.so -> libfoo.so.1.2.3, so the linker finds the current version. - Backups. Snapshot tools hard-link unchanged files between runs, so ten daily snapshots of mostly-identical data cost roughly one copy.
- systemd.
systemctl enableworks by creating a symlink in a.wantsdirectory — see systemctl.
Gotchas
The trailing slash
rm mylink removes the link. rm mylink/ is an error, and some commands treat mylink/ as “the directory it points to” — which for rsync --delete is the difference between replacing a link and emptying a directory. Leave the slash off when you mean the link itself.
Copying links
cp follows symlinks by default and copies the contents. cp -a (or -P) preserves them as links. rsync -a preserves symlinks but not hard-link relationships unless you add -H. tar stores symlinks as symlinks unless you pass -h.
Each of these defaults is defensible and none of them agree, so check before archiving anything where it matters.
Permissions on a symlink are meaningless
A symlink always shows lrwxrwxrwx. Access is governed entirely by the target’s permissions — see file permissions. chmod on a symlink changes the target, not the link.
Hard links and disk usage
Two hard-linked names occupy one file’s worth of space, but tools disagree on how to report that. du counts it once within a single traversal and twice if you scan the two directories separately. Deleting one name frees nothing; the data goes only when the link count reaches zero. This is also why a large file can appear to survive deletion — see disk space.
You cannot hard-link a directory
The kernel forbids it, because a cycle in the directory tree would make it impossible to walk safely. Symlink directories instead. For something that behaves like a directory in two places at once, a bind mount is the real answer.
Quick reference
ln -s target link # symlink (target first, like cp)
ln -sr target link # symlink with a computed relative path
ln target link # hard link
ln -sfn newtarget link # repoint a directory symlink safely
readlink -f link # resolve to the final path
ls -li # inode numbers and link counts
stat file # detail, including link count
find . -type l # all symlinks
find . -xtype l # broken symlinks
find / -samefile file # every hard link to a fileRelated
- find — locating symlinks, and the broken ones in particular.
- File permissions — why a symlink’s own permissions never matter.
- Disk space — inodes, link counts, and files that will not free their space.
- tar — how archives handle links, and when
-hchanges the answer. mount --bind— when you genuinely need a directory in two places.
