ln Command in Linux: Complete Guide to Creating Hard and Symbolic Links and Parameters

ln command in Linux and it perimeters

Introduction

ln confused me for a long time, mostly because “link” sounds like it should mean the same thing every time, but hard links and symbolic links are actually built on completely different mechanisms with different guarantees and different failure modes. Once I understood the distinction properly — using stat and inode numbers to actually see what was happening under the hood — the command stopped feeling mysterious. This guide covers both link types, when to use each, and the gotchas I’ve personally run into.

What Is the ln Command?

ln creates links between files. It’s a GNU coreutils utility:

$ ln --version
ln (GNU coreutils) 9.4
Copyright (C) 2023 Free Software Foundation, Inc.

Basic Syntax

ln [OPTION]... TARGET LINK_NAME
ln [OPTION]... TARGET
ln [OPTION]... TARGET... DIRECTORY
ln [OPTION]... -t DIRECTORY TARGET...

Hard Links vs Symbolic Links — The Core Concept

This distinction is the entire point of the command, so I want to be precise about it.

A hard link is a second directory entry pointing at the same inode as an existing file. There’s no meaningful difference between the “original” and the “link” afterward — they’re both just names pointing at identical data, permissions, and metadata. I demonstrated this earlier with stat:

$ ln src.txt hardlink.txt
$ ls -li src.txt hardlink.txt
573462 -rwxr-xr-x 2 root root 6 Jul 31 01:36 hardlink.txt
573462 -rwxr-xr-x 2 root root 6 Jul 31 01:36 src.txt

Same inode number (573462) for both. Editing either name edits the same underlying data. Deleting one just decrements the link count (visible as 2 in the permission field above); the data survives as long as at least one name remains.

Hard links have real restrictions: they can’t span filesystems (an inode is only meaningful within the filesystem that allocated it), and on most systems you can’t hard-link a directory (to prevent creating cycles in the directory tree, which would break countless tools that walk directories assuming no loops).

A symbolic link (symlink) is a completely different kind of object — it’s its own inode, of type “symlink,” whose content is simply a text string holding a path. When the kernel resolves a symlink, it reads that path and looks up whatever it points to, transparently, at access time. I created one in the same test session:

$ ln -s src.txt symlink.txt
$ ls -li symlink.txt
573467 lrwxrwxrwx 1 root root 7 Jul 31 01:36 symlink.txt -> src.txt

Notice the different inode number (573467, distinct from src.txt’s 573462), the leading l in the permission string, the size (7 bytes — the length of the string src.txt), and the -> src.txt arrow showing what it points to. Symlinks can point to anything, including nonexistent targets, directories, and paths on other filesystems. If the target is removed, the symlink becomes “dangling” — it still exists as a file, but resolving it fails.

Full List of Parameters

OptionLong formDescription
-s--symbolicCreate a symbolic link instead of a hard link
-f--forceRemove existing destination files first
-i--interactivePrompt whether to remove destinations
-n--no-dereferenceTreat LINK_NAME as a normal file if it’s a symlink to a directory
-bMake a backup of each existing destination file
--backup[=CONTROL]Same as -b, with control over the backup method
-r--relativeCreate symbolic links relative to link location
-t--target-directory=DIRECTORYSpecify the DIRECTORY in which to create the links
-T--no-target-directoryTreat LINK_NAME as a normal file always
-v--verbosePrint name of each linked file
--helpDisplay help and exit
--versionOutput version information and exit

Practical Examples with Output

Creating a hard link:

$ echo "hello" > src.txt
$ ln src.txt hardlink.txt
$ cat hardlink.txt
hello

Creating a symbolic link:

$ ln -s src.txt symlink.txt
$ cat symlink.txt
hello
$ ls -l symlink.txt
lrwxrwxrwx 1 root root 7 Jul 31 01:36 symlink.txt -> src.txt

Creating a relative symlink regardless of current directory:

$ ln -sr /home/claude/test/demo/src.txt /home/claude/test/demo/a/rel_link.txt
$ ls -l /home/claude/test/demo/a/rel_link.txt
lrwxrwxrwx 1 root root 9 ... rel_link.txt -> ../src.txt

-r computed the relative path (../src.txt) automatically, which is what you generally want for links inside a project directory that might be moved or copied as a whole — an absolute-path symlink would break the moment the whole tree is relocated, while a relative one keeps working.

Symlinking a directory (something hard links can’t do):

$ ln -s /var/www/releases/v2 /var/www/current

Forcing overwrite of an existing link:

$ ln -sf src.txt symlink.txt

Linking multiple targets into a directory:

$ ln -s /usr/bin/python3 /usr/bin/python3.12 mybin/

Common Use Cases

Shell Scripting and Automation

The classic atomic-deploy pattern using a relative symlink swap:

#!/usr/bin/env bash
set -euo pipefail

RELEASE_DIR="/opt/myapp/releases/$(date +%Y%m%d_%H%M%S)"
mkdir -p "$RELEASE_DIR"
# ... deploy new code into $RELEASE_DIR ...

ln -sfn "$RELEASE_DIR" /opt/myapp/current

I use -sfn deliberately here: -s for a symlink, -f to overwrite the existing link, and -n so that if /opt/myapp/current is itself a symlink to a directory, ln replaces the symlink itself rather than (incorrectly) creating a new link inside the directory it points to.

Space-efficient backup rotation using hard links (the core trick behind classic rsync-based incremental backup scripts):

rsync -a --link-dest="/backups/$(date -d yesterday +%F)" /data/ "/backups/$(date +%F)/"

Unchanged files between backups get hard-linked rather than copied, so each daily snapshot looks complete but only consumes disk space for files that actually changed.

Real-World System Administration Workflows

Comparing ln to Related Commands

Troubleshooting Common ln Issues

“ln: failed to create hard link: Invalid cross-device link”: you’re trying to hard link across filesystems — this is impossible by design since inodes aren’t unique across filesystems; use a symlink instead.

“ln: ‘X’: hard link not allowed for directory”: most Linux filesystems block hard links to directories entirely, for the cycle-prevention reason mentioned earlier; use a symlink for directories.

Symlink shows as broken/red in ls: the target no longer exists at the recorded path. Check with readlink -f linkname to see the fully resolved path and confirm it’s missing, then either recreate the target or repoint the symlink.

Relative symlink breaks after moving a directory: this happens when the symlink’s relative path assumed a fixed position relative to its target; use ln -sr when creating it so the tool computes the correct relative path from the link’s actual location, and be aware moving just the symlink (not both link and target together) can still break it.

Performance Considerations

Hard links have zero storage or performance overhead beyond a directory entry — reading through a hard link is exactly as fast as reading the original, because it is the original file, just under another name. Symlinks add one extra path resolution step per access (the kernel has to read the link’s target and re-resolve), which is negligible for a single link but can add measurable overhead in deeply chained symlinks resolved millions of times (e.g., dynamic library loading paths on a busy server), which is one reason distros try to keep .so symlink chains short.

Security Implications

Symlinks introduce a classic attack class called symlink attacks (or TOCTOU — time-of-check to time-of-use — race conditions): a privileged script that checks a file’s properties and then opens it, if the path is attacker-writable in between, can be tricked into following a symlink swapped in at the last moment, pointing at a sensitive target. This is why well-written system scripts running as root avoid predictable temp filenames and use safe APIs (mkstemp, O_NOFOLLOW) rather than naive path-based checks. As a practical rule, never let a root-run script write to a world-writable directory using a fixed filename it merely “hopes” isn’t already a symlink.

Best Practices

Compatibility Across Distributions

GNU ln behaves identically across Debian/Ubuntu, RHEL/Fedora, Arch, and openSUSE. BusyBox ln (Alpine, minimal containers) supports -s, -f, and -v but often lacks -r (relative) and -T/-n refinements — verify with ln --help before relying on those in a minimal image.

Advanced Scenarios I’ve Run Into

Chained symlinks and resolution depth: symlinks can point to other symlinks, and the kernel follows the chain until it reaches a real file or hits a configured maximum depth (ELOOP, typically 40 on Linux) and refuses further resolution — a safeguard against symlink cycles like a -> b -> a. readlink -f path (or realpath path) fully resolves any chain down to the final real path, which is invaluable for debugging deeply nested symlink setups like dynamic library versioning:

$ readlink -f /usr/lib/x86_64-linux-gnu/libssl.so
/usr/lib/x86_64-linux-gnu/libssl.so.3

Finding all symlinks pointing at a specific target, useful before removing or relocating a file that other links might depend on:

$ find / -xdev -lname '*src.txt' 2>/dev/null

Finding all hard links to a given file — since hard links share an inode but have no directory record of “siblings,” you have to search by inode number across the same filesystem:

$ inode=$(stat -c %i src.txt)
$ find / -xdev -inum "$inode" 2>/dev/null

Symlinking an entire directory tree of binaries into a personal bin directory without duplicating them, a common dotfiles pattern:

$ for f in /opt/toolkit/bin/*; do
    ln -sf "$f" "$HOME/bin/$(basename "$f")"
done

Hard Links and Backup Deduplication in More Detail

The rsync --link-dest pattern deserves a closer look because it’s genuinely one of the cleverer uses of hard links in everyday system administration. Each nightly backup run creates what looks like a completely independent full snapshot directory, but files unchanged since the previous snapshot are hard-linked rather than copied — meaning a week of daily “full” backups of a mostly-static system might consume barely more disk space than a single copy, while still letting you rm -rf any single day’s snapshot independently without affecting the others (since removing one hard link just decrements the shared inode’s link count, exactly as described earlier in this guide). Tools like rsnapshot, borgbackup (though Borg uses its own deduplication rather than hard links), and countless custom shell scripts build on this exact mechanism.

Summary

The single idea that makes ln click is that hard links and symbolic links solve the “multiple names for one thing” problem at two completely different layers: hard links operate at the inode level (same file, multiple directory entries, same filesystem only), while symlinks operate at the path level (a tiny separate file holding a string, resolved fresh on each access, able to point anywhere including across filesystems or at directories). Once that distinction is second nature, choosing between ln and ln -s for any given task becomes obvious.

References

Exit mobile version