- Published on
- · 9 min read
Symlinks Explained: How to Create and Use Symbolic Links on macOS and Linux
Symlinks Explained: How to Create and Use Symbolic Links on macOS and Linux
Symbolic links (symlinks) are one of the most useful filesystem features hiding in plain sight. They let you make a file or folder appear to exist in multiple places at once — without copying anything.
This guide covers everything from first principles to real-world workflows, with macOS-specific behavior and gotchas that will save you from data loss.
Table of Contents
- What Is a Symlink?
- Symlinks vs Hard Links vs Finder Aliases
- Creating Symlinks with ln -s
- Absolute vs Relative Paths
- The Trailing Slash Trap
- Overwriting Directory Symlinks
- Listing, Verifying, and Removing Symlinks
- macOS-Specific Behavior
- Real-World Use Cases
- Cloud Storage Warning
- Quick Reference Cheatsheet
- Common Mistakes Checklist
What Is a Symlink?
A symlink is a special file that points to another file or directory. It stores a path string — that's it. When the OS encounters a symlink, it transparently follows the path to the real target.
Think of it like a shortcut, but one that works everywhere — Terminal, scripts, apps, build tools, all of it.
# The symlink itself is tiny (just a path string)
ls -la ~/.zshrc
# lrwxr-xr-x 1 dylan staff 28 Feb 27 10:00 .zshrc -> ~/dotfiles/.zshrcThe l at the beginning of the permissions string means "link." The arrow -> shows what it points to.
Symlinks vs Hard Links vs Finder Aliases
macOS has three kinds of "links." They look similar but behave very differently.
| Feature | Symbolic Link | Hard Link | Finder Alias |
|---|---|---|---|
| Stores | Path string to target | Direct reference to same inode | Path + inode (hybrid) |
| Works on directories | Yes | No (APFS blocks it) | Yes |
| Cross-volume | Yes | No (same filesystem only) | Yes |
| Survives target move | No (breaks) | Yes (same data) | Yes (inode fallback) |
| Works in Terminal | Yes | Yes | No (Finder only) |
| Created with | ln -s | ln | Finder ⌘L |
Key takeaway: Symlinks are the Unix standard. They work everywhere. Use them.
Finder aliases only work in Finder and Cocoa apps. Hard links can't point to directories on APFS. Symlinks are the right tool for 99% of use cases.
Creating Symlinks with ln -s
The basic command:
ln -s <target> <link_name>target= the real file or directory you want to point tolink_name= the new pointer you're creating
# Link a file
ln -s ~/dotfiles/.zshrc ~/.zshrc
# Link a directory
ln -s ~/dotfiles/nvim ~/.config/nvim
# Link into current directory (keeps the same name)
ln -s ~/Documents/notes.md .Essential Flags
# -s Create a symbolic link (without this, ln creates a hard link)
# -f Force: overwrite if link_name already exists
# -n Don't follow existing symlink-to-directory (replace it instead)
# -v Verbose: print what was created
# The workhorse combo — idempotent, safe for scripts
ln -sfn /new/target /existing/linkThe -sfn combo is what you want in setup scripts and dotfiles managers. It creates the link if it doesn't exist, replaces it if it does, and handles directory symlinks correctly.
Absolute vs Relative Paths
This is the #1 source of symlink bugs.
# ABSOLUTE — always works, regardless of where the symlink lives
ln -s /Users/dylan/dotfiles/.zshrc ~/.zshrc
# RELATIVE — path is relative to the SYMLINK's location, NOT your cwd
ln -s dotfiles/.zshrc ~/.zshrc
# Works: from ~/.zshrc's perspective, dotfiles/.zshrc is correct
# WRONG — common mistake
cd ~/dotfiles
ln -s .zshrc ~/.zshrc
# Creates ~/.zshrc -> .zshrc, which looks for ~/.zshrc (itself!) — broken!Rule of thumb: Use absolute paths unless you have a specific reason for relative paths (like keeping symlinks portable inside a git repo that gets cloned to different locations).
The Trailing Slash Trap
This is the most dangerous gotcha when working with directory symlinks. Getting this wrong can delete your data.
# Given: my_link -> /some/directory/
# Removing a symlink — SAFE
rm my_link # Removes the symlink only
# Removing with trailing slash — DANGEROUS
rm my_link/ # May try to remove contents of target
rm -r my_link/ # DELETES THE TARGET'S CONTENTS — DATA LOSS
# Correct way to remove a directory symlink
unlink my_linkNever use a trailing slash when removing directory symlinks. Use unlink to be explicit and safe.
Overwriting Directory Symlinks
Another common pitfall — updating a symlink that already points to a directory.
# This does NOT work (creates link INSIDE the target dir):
ln -sf /new/target existing_link
# This works correctly (replaces the symlink itself):
ln -sfn /new/target existing_linkThe -n flag tells ln not to follow the existing symlink. Without it, ln enters the directory and creates the new link inside it instead of replacing it.
Listing, Verifying, and Removing Symlinks
Check if something is a symlink
# ls -l shows the arrow notation
ls -l ~/.zshrc
# lrwxr-xr-x 1 dylan staff 28 .zshrc -> /Users/dylan/dotfiles/.zshrc
# readlink shows the target path
readlink ~/.zshrc
# /Users/dylan/dotfiles/.zshrc
# test -L checks programmatically
test -L ~/.zshrc && echo "symlink" || echo "not a symlink"Find all symlinks in a directory
# List all symlinks recursively
find ~/Projects -type l
# List with full details and targets
find ~/Projects -type l -ls
# Find only in current directory (not recursive)
find . -maxdepth 1 -type lFind broken symlinks
A broken (dangling) symlink is one whose target no longer exists. It still shows up in ls but resolves to nothing.
# Find broken symlinks
find ~ -maxdepth 3 -type l ! -exec test -e {} \; -print
# Clean up broken symlinks (use with caution)
find ~/Projects -type l ! -exec test -e {} \; -deleteRemove a symlink
# Preferred — explicit and safe
unlink ~/.zshrc
# Also works for file symlinks
rm ~/.zshrc
# NEVER for directory symlinks
rm -r my_link/ # This deletes the target's contents!macOS-Specific Behavior
Finder
- Symlinks show a curved arrow overlay on the icon (identical to Finder aliases — you can't distinguish them visually)
- Right-click → "Show Original" (⌘R) navigates to the real target
- Dragging a symlink to Trash removes only the link, not the target
- Spotlight indexes the original file, not symlinks — no duplicate search results
APFS Notes
- APFS fully supports symlinks with no special considerations
- Firmlinks are an internal APFS concept (bridges the read-only System volume and writable Data volume since Catalina). You don't create these yourself
- Hard links to directories are not allowed by APFS for users — only the OS uses them internally for Time Machine
The readlink Quirk
macOS ships with BSD readlink, which doesn't support -f (full canonical path resolution). This trips up people coming from Linux.
# macOS built-in: resolves one level only
readlink /some/symlink
# Full resolution options on macOS
realpath /some/symlink # Available on macOS 13+
python3 -c "import os; print(os.path.realpath('/some/link'))"
# Or install GNU coreutils
brew install coreutils
greadlink -f /some/linkSIP-Protected Paths
System Integrity Protection blocks symlink creation in certain system paths:
/System,/usr(except/usr/local),/bin,/sbin/usr/localis writable — this is where Homebrew lives
If you need a root-level path (like /nix for the Nix package manager), use /etc/synthetic.conf:
# /etc/synthetic.conf (tab-separated, NOT spaces — reboot required)
nix /System/Volumes/Data/nixReal-World Use Cases
1. Dotfiles Management
The classic use case. Three popular approaches:
# Approach 1: Manual symlinks
ln -sfn ~/dotfiles/.zshrc ~/.zshrc
ln -sfn ~/dotfiles/.gitconfig ~/.gitconfig
ln -sfn ~/dotfiles/nvim ~/.config/nvim
# Approach 2: GNU Stow (automates the symlinking)
brew install stow
cd ~/dotfiles
stow zsh # Creates ~/.zshrc -> ~/dotfiles/zsh/.zshrc
stow nvim # Creates ~/.config/nvim -> ~/dotfiles/nvim/.config/nvim
# Approach 3: Bare git repo (no symlinks needed — files live in $HOME)
git clone --bare https://github.com/user/dotfiles ~/.dotfilesEach has trade-offs. Stow is the most popular symlink-based approach — it computes the correct relative paths automatically. Bare git repos avoid symlinks entirely by treating $HOME as the working tree.
2. Obsidian Vault — Link External Folders
Symlink project directories into your Obsidian vault to search and edit everything from one place — without copying files:
# Link Claude Code plans into your vault
ln -s ~/.claude/plans ~/vault/claude-plans
# Link a monorepo's docs folder
ln -s ~/Desktop/sm-core ~/vault/sm-core
# Link standalone reference guides
ln -s ~/linux-filesystem-guide.md ~/vault/linux-filesystem-guide.mdNow your vault's graph view, search, and backlinks work across all your content. Obsidian has supported symlinks since v0.11.1 — just avoid creating loops or overlapping targets.
3. Shared Config Across Projects
Stop duplicating config files across repos:
# One Prettier config for all projects
ln -s ~/.config/prettier/.prettierrc ~/project-a/.prettierrc
ln -s ~/.config/prettier/.prettierrc ~/project-b/.prettierrc
# Shared TypeScript base config
ln -s ~/config/tsconfig.base.json ~/app/tsconfig.base.json4. Development Shortcuts
# Quick access to deeply nested output directories
ln -s ~/Desktop/sm-core/video-production/output ~/Desktop/video-output
# Switch between SSH config profiles
ln -sfn ~/.ssh/config.work ~/.ssh/config # Work mode
ln -sfn ~/.ssh/config.personal ~/.ssh/config # Personal mode
# Point to a specific tool version
ln -sfn /usr/local/opt/node@18/bin/node /usr/local/bin/node5. Inspect Homebrew Symlinks
Homebrew works by installing packages in /usr/local/Cellar/ and symlinking binaries into your PATH:
# See what Homebrew has linked into your PATH
ls -la /usr/local/bin | grep "^l"
# Find broken Homebrew symlinks (stale packages)
find /usr/local/bin -type l ! -exec test -e {} \; -print
# Fix with
brew cleanupCloud Storage Warning
Symlinks and cloud storage don't mix well in 2026.
# This used to work with Dropbox — no longer reliable
ln -s ~/Projects ~/Dropbox/ProjectsiCloud Drive, Dropbox, and Google Drive handle symlinks poorly or not at all. iCloud is especially dangerous — it can interpret symlinked content as deletions and corrupt your data. Do not rely on symlinks for cloud sync.
Quick Reference Cheatsheet
| Action | Command |
|---|---|
| Create symlink | ln -s /target /link |
| Create or replace (idempotent) | ln -sfn /target /link |
| Check target | readlink /link |
| Check if symlink | test -L /path && echo yes |
| Find all symlinks | find /path -type l |
| Find broken symlinks | find /path -type l ! -exec test -e {} \; -print |
| Remove symlink safely | unlink /link |
Common Mistakes Checklist
Avoid these pitfalls when working with symlinks:
- Relative path confusion — Relative paths are relative to the link's location, not your current directory
- Missing
-nflag — Forgetting-nwhen overwriting a directory symlink creates the link inside the target instead of replacing it - Trailing slash on removal —
rm -r link/deletes the target's contents, not the symlink - Expecting symlinks to follow moves — Moving or renaming the target breaks the symlink
- Finder aliases in Terminal — Finder aliases (⌘L) don't work in Terminal or scripts; use
ln -sinstead - Cloud storage — iCloud Drive and Dropbox handle symlinks unreliably; avoid
- Circular symlinks —
a -> b -> acreates an infinite loop; macOS stops after ~32 redirects with an ELOOP error