ln system

copy-paste is not a file management strategy.

The ln man page is 100 lines long. You need one flag and a basic understanding of how files actually work.

You have a config file. It needs to exist in two places. So you copied it. Now you have two copies. You edited one. The other is stale. You forgot which one is the “real” one. Six months later, you’re debugging why your application works in development but not in production and it’s because you’ve been editing the wrong copy for three months.

Or you have a binary at /opt/some-tool/bin/tool-v2.4.1 and you want to type tool instead of the full path. So you added the entire directory to your PATH. Your PATH is now seventeen directories long and your shell startup takes two seconds because it’s searching all of them every time you press Tab.

Links solve both problems. A symlink is a pointer — one file that says “the real file is over there.” Change the real file, every symlink reflects the change. Point tool at the current version, update the link when you upgrade, done. No copies. No stale files. No PATH pollution.

Unless you’re running Windows then wtf none of this applies to you. But hey, come to the dark side, go install WSL2 and you can follow along. We’ll wait. Impatiently.

If you’re lazy like me (all sysadmins are!) then click here for the ln cheat sheet.


ln -s /path/to/target /path/to/link

The order is target then link name. Think of it as “make this link that points to that target.” Yes, it’s backwards from what feels natural. Yes, everyone gets it wrong the first time.

ln -s /opt/node-v20/bin/node /usr/local/bin/node

Now typing node runs /opt/node-v20/bin/node. Upgrade Node? Change the link. No PATH changes needed.

ln -s ~/projects/dotfiles/.bashrc ~/.bashrc

Your .bashrc is a symlink to your dotfiles repo. Edit it anywhere, commit it, and every machine that clones the repo gets the same config.

ls -la ~/.bashrc
lrwxrwxrwx 1 owner owner 38 Mar 15 10:22 .bashrc -> /home/owner/projects/dotfiles/.bashrc

The l at the beginning means it’s a link. The arrow -> shows where it points.

readlink -f ~/.bashrc

Resolves the full path the link points to, following chains of links if there are any.


ln -sf /opt/node-v22/bin/node /usr/local/bin/node

-f forces — overwrites the existing link without complaining. This is how you update a symlink to point to a new version. Remove the old link and create a new one in one step.


ln -s /mnt/storage/media ~/media

~/media now points to /mnt/storage/media. cd ~/media takes you there. ls ~/media lists its contents. It looks and acts like a real directory but it’s just a pointer.


There are two types of links. You want symbolic links. But you should know hard links exist.

ln -s target link
  • A separate file that contains a path to the target
  • Can cross filesystem boundaries
  • Can link to directories
  • If the target is deleted, the link becomes “dangling” (broken)
  • Has its own inode number
  • The -s flag creates these
ln target link
  • A second directory entry pointing to the same inode (same actual data on disk)
  • Cannot cross filesystem boundaries
  • Cannot link to directories (to prevent cycles)
  • If the “original” is deleted, the hard link still works (the data isn’t freed until all links are gone)
  • Same inode number as the original
  • No -s flag

Hard links are useful for specific cases — backup tools like rsync --link-dest use them to create space-efficient snapshots. But for everyday “I want file B to point to file A” work, symbolic links are what you want.


Symlinks break when the target is moved or deleted. Find them:

find /path -xtype l

Lists all broken symlinks. -xtype l means “files whose dereferenced type is a symlink” — which only happens when the target doesn’t exist.

find /usr/local/bin -xtype l -delete

Find and delete broken symlinks. Clean up after uninstalled tools.


Common patterns

Version switching

ln -sf /opt/python3.11/bin/python3 /usr/local/bin/python3
# Later, after upgrade:
ln -sf /opt/python3.12/bin/python3 /usr/local/bin/python3

One symlink, swap the version by updating the target.

Config management with dotfiles

ln -s ~/dotfiles/.vimrc ~/.vimrc
ln -s ~/dotfiles/.tmux.conf ~/.tmux.conf
ln -s ~/dotfiles/.gitconfig ~/.gitconfig

All your configs live in one repo. Symlinks put them where applications expect them. Clone the repo on a new machine, run a script to create the links, and you’re home.

Application data on a different disk

mv /var/lib/docker /mnt/bigdisk/docker
ln -s /mnt/bigdisk/docker /var/lib/docker

Docker thinks its data is at /var/lib/docker. It’s actually on your 2TB disk. No config changes needed. Docker doesn’t know and doesn’t care.


The flags that actually matter

Flag What it does
-s Create a symbolic link (you almost always want this).
-f Force — overwrite existing link.
-n Treat link target as a normal file even if it’s a directory (useful with -f).
-v Verbose — print each link as it’s created.
-r Create a relative symlink instead of absolute.

“But I just copy—”

Of course you do.

“I just copy the file to both locations.” And when you update one, the other is stale. And three months later you’ve forgotten which copy you’ve been editing. And then you spend two hours debugging a “config mismatch” that’s actually “you have two copies and changed the wrong one.” A symlink is one source of truth.

“Windows has shortcuts.” Windows shortcuts are .lnk files that only work in Explorer. They don’t work in cmd, PowerShell, or any application that opens files by path. They’re a GUI-only concept. Symlinks work at the filesystem level — every application, every tool, every script sees them as the real file. Because at the filesystem level, they effectively are.

“I’ll just add it to my PATH.” Your PATH is already a mess. Adding another directory to it so you can run one binary is like building a new highway because you need to visit one store. A symlink in /usr/local/bin puts the binary where your shell already looks. Clean. Targeted. No PATH bloat.

“I use alias for that.” Aliases only work in interactive shells. Scripts, cron jobs, and other programs don’t read your .bashrc. A symlink works everywhere because it’s a filesystem-level construct, not a shell trick.


ln cheat sheet

You made it. Or you skipped straight here. Either way, no judgment. Copy and paste these. Pin them. Tattoo them on your forearm. Whatever works.

What you’re doing Command
Create a symlink ln -s /path/to/target /path/to/link
Overwrite existing link ln -sf /new/target /path/to/link
Check where a link points readlink -f /path/to/link
List links (shows → target) ls -la /path/to/link
Find broken symlinks find /path -xtype l
Delete broken symlinks find /path -xtype l -delete
Create a hard link ln target link

The one command: ln -s target linkname — create a symbolic link. Remember: target first, link name second. It’s backwards. Everyone gets it wrong once.

Back to the top, you overachiever.