{"id":22478,"date":"2026-07-17T14:57:25","date_gmt":"2026-07-17T14:57:25","guid":{"rendered":"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/?p=22478"},"modified":"2026-07-17T14:57:25","modified_gmt":"2026-07-17T14:57:25","slug":"ln-command-in-linux-hard-links-vs-symlinks","status":"publish","type":"post","link":"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/ln-command-in-linux-hard-links-vs-symlinks\/","title":{"rendered":"ln Command in Linux: Hard Links vs. Symlinks"},"content":{"rendered":"<p>The <code>ln<\/code> command in Linux is one of those tools that looks simple on the surface but trips people up constantly. Hard link or symbolic link? What&#8217;s the difference? When does it actually matter? I&#8217;ve seen even experienced sysadmins use symlinks out of habit without thinking about whether a hard link would be better.<\/p><div class=\"hayden-after-paragraph hayden-entity-placement\" id=\"hayden-219502001\"><div data-nosnippet id=\"hayden-1063362739\" style=\"margin-left: auto;margin-right: auto;text-align: center;\"><a href=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/www.manageengine.com\/it-operations-management\/infrastructure-observability\/server-observability.html?utm_source=linuxblog&#038;utm_medium=bannerad&#038;utm_campaign=ServerObservability\" data-bid=\"1\" data-no-instant=\"1\" rel=\"noopener nofollow sponsored\">\r\n  <img fetchpriority=\"high\" src=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/static.linuxblog.io\/wp-content\/uploads\/2026\/06\/868\u00d7300V3_x1.png\"\r\n       srcset=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/static.linuxblog.io\/wp-content\/uploads\/2026\/06\/868\u00d7300V3_x1.png 1x,\r\n               https:\/\/round-lake.dustinice.workers.dev:443\/https\/static.linuxblog.io\/wp-content\/uploads\/2026\/06\/868\u00d7300V3.png 2x\"\r\n       alt=\"ManageEngine Site24x7 - Fix Linux issues in minutes, not hours!\"\r\n       width=\"868\" height=\"300\"\r\n       loading=\"eager\"\r\n       style=\"display:block;\">\r\n<\/a><\/div><div align=\"center\" style=\"font-style: italic; font-size: 0.85em; font-weight: normal; margin-top: 4px;\">\r\n\tAdvertisement\r\n<\/div><\/div>\n<p>Here&#8217;s what each link type does, when to use which, and how to avoid the common mistakes that cause silent data loss or broken configs.<\/p>\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_85 ez-toc-wrap-center counter-hierarchy ez-toc-counter ez-toc-custom ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\"><div class=\"ez-toc-title\" style=\"cursor:inherit\">In This Article<\/div>\n<\/div><nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/ln-command-in-linux-hard-links-vs-symlinks\/#What_is_a_Link_in_Linux\" >What is a Link in Linux?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/ln-command-in-linux-hard-links-vs-symlinks\/#Hard_Links\" >Hard Links<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/ln-command-in-linux-hard-links-vs-symlinks\/#Hard_Link_Limitations\" >Hard Link Limitations<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/ln-command-in-linux-hard-links-vs-symlinks\/#Symbolic_Links_Soft_Links\" >Symbolic Links (Soft Links)<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/ln-command-in-linux-hard-links-vs-symlinks\/#Symlink_Advantages\" >Symlink Advantages<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/ln-command-in-linux-hard-links-vs-symlinks\/#Side-by-Side_Comparison\" >Side-by-Side Comparison<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/ln-command-in-linux-hard-links-vs-symlinks\/#Common_ln_Options\" >Common ln Options<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/ln-command-in-linux-hard-links-vs-symlinks\/#Practical_Examples\" >Practical Examples<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-9\" href=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/ln-command-in-linux-hard-links-vs-symlinks\/#Symlink_a_config_directory\" >Symlink a config directory<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-10\" href=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/ln-command-in-linux-hard-links-vs-symlinks\/#Create_a_versioned_symlink_for_binaries\" >Create a versioned symlink for binaries<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-11\" href=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/ln-command-in-linux-hard-links-vs-symlinks\/#Updating_a_symlink_pointing_to_a_directory\" >Updating a symlink pointing to a directory<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-12\" href=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/ln-command-in-linux-hard-links-vs-symlinks\/#Hard_link_for_an_atomic_backup_snapshot\" >Hard link for an atomic backup snapshot<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-13\" href=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/ln-command-in-linux-hard-links-vs-symlinks\/#Find_all_hard_links_to_an_inode\" >Find all hard links to an inode<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-14\" href=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/ln-command-in-linux-hard-links-vs-symlinks\/#Identify_and_fix_dangling_symlinks\" >Identify and fix dangling symlinks<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-15\" href=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/ln-command-in-linux-hard-links-vs-symlinks\/#Absolute_vs_Relative_Symlinks\" >Absolute vs. Relative Symlinks<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-16\" href=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/ln-command-in-linux-hard-links-vs-symlinks\/#Checking_and_Debugging_Links\" >Checking and Debugging Links<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-17\" href=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/ln-command-in-linux-hard-links-vs-symlinks\/#When_to_Use_Which\" >When to Use Which<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-18\" href=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/ln-command-in-linux-hard-links-vs-symlinks\/#Practical_Pitfalls\" >Practical Pitfalls<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-19\" href=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/ln-command-in-linux-hard-links-vs-symlinks\/#Copying_symlinks_with_cp\" >Copying symlinks with cp<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-20\" href=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/ln-command-in-linux-hard-links-vs-symlinks\/#tar_and_symlinks\" >tar and symlinks<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-21\" href=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/ln-command-in-linux-hard-links-vs-symlinks\/#Permissions_on_symlinks\" >Permissions on symlinks<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-22\" href=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/ln-command-in-linux-hard-links-vs-symlinks\/#Summary\" >Summary<\/a><\/li><\/ul><\/nav><\/div>\n<h2><span class=\"ez-toc-section\" id=\"What_is_a_Link_in_Linux\"><\/span>What is a Link in Linux?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-22484 size-large\" title=\"Hard Links vs. Symlinks\" src=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/wp-content\/uploads\/2026\/07\/ln-command-linux-hard-soft-links-868x431.jpg\" alt=\"Hard Links vs. Symlinks\" width=\"868\" height=\"431\" srcset=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/wp-content\/uploads\/2026\/07\/ln-command-linux-hard-soft-links-868x431.jpg 868w, https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/wp-content\/uploads\/2026\/07\/ln-command-linux-hard-soft-links-300x149.jpg 300w, https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/wp-content\/uploads\/2026\/07\/ln-command-linux-hard-soft-links-560x278.jpg 560w\" sizes=\"auto, (max-width: 868px) 100vw, 868px\" \/><\/p>\n<p>In Linux, everything on a filesystem revolves around <a href=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/www.kernel.org\/doc\/html\/latest\/filesystems\/ext4\/inodes.html\" target=\"_blank\" rel=\"noopener\">inodes<\/a>. An inode is a data structure that stores metadata about a file: permissions, ownership, timestamps, and pointers to the actual data blocks on disk. Crucially, an inode does <strong>not<\/strong> store the filename. That mapping happens in the directory.<\/p>\n<p>When you create a file, Linux creates an inode and a directory entry pointing to it. A link is just another directory entry pointing to the same inode, or in the case of a symbolic link, a file that contains a path to something else.<\/p>\n<p>This distinction matters a lot once you start working with both link types.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Hard_Links\"><\/span>Hard Links<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>A hard link is a second directory entry pointing to the same inode as the original file. Both the original and the hard link are equal. There is no &#8220;source&#8221; and &#8220;copy&#8221; here. They are two names for the same data.<\/p>\n<pre>ln \/path\/to\/original \/path\/to\/hardlink<\/pre>\n<p>Quick example:<\/p>\n<pre>touch original.txt\r\necho \"hello\" &gt; original.txt\r\nln original.txt hardlink.txt\r\n\r\nls -li original.txt hardlink.txt<\/pre>\n<p>Output:<\/p>\n<pre>1048577 -rw-r--r-- 2 user user 6 Jan 10 09:00 hardlink.txt\r\n1048577 -rw-r--r-- 2 user user 6 Jan 10 09:00 original.txt<\/pre>\n<p>Notice the inode number on the left: both are <code>1048577<\/code>. The link count shows <code>2<\/code>. They are the same file with two names.<\/p>\n<p>Now delete the original:<\/p>\n<pre>rm original.txt\r\ncat hardlink.txt<\/pre>\n<p>Output:<\/p>\n<pre>hello<\/pre>\n<p>The data is still there. This is the key behavior of hard links: the data on disk persists until all hard links pointing to that inode are removed. The kernel tracks this with the link count. Only when the link count reaches zero is the inode and its data blocks freed.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"Hard_Link_Limitations\"><\/span>Hard Link Limitations<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Hard links have three firm restrictions:<\/p>\n<ul>\n<li><strong>No cross-filesystem hard links.<\/strong> Both the link and the target must live on the same filesystem. Inodes are local to a filesystem, so linking across filesystems is impossible.<\/li>\n<li><strong>No hard links to directories.<\/strong> Most Linux systems prevent this. The exception is the implicit <code>.<\/code> and <code>..<\/code> entries, which the filesystem manages internally. Allowing user-created directory hard links would break filesystem traversal tools and create loops.<\/li>\n<li><strong>There is a ceiling on link count.<\/strong> Filesystems cap how many hard links a single inode can have. On ext4 the limit is 65,000. You will rarely hit it, but backup tooling that hard-links aggressively sometimes does.<\/li>\n<\/ul>\n<pre># This will fail:\r\nln \/mnt\/data\/file.txt \/home\/user\/file.txt\r\n# ln: failed to create hard link: Invalid cross-device link\r\n\r\n# This will also fail:\r\nln \/home\/user\/mydir \/home\/user\/dirlink\r\n# ln: \/home\/user\/mydir: hard link not allowed for directory<\/pre>\n<h2><span class=\"ez-toc-section\" id=\"Symbolic_Links_Soft_Links\"><\/span>Symbolic Links (Soft Links)<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>A symbolic link, or symlink, is a special file that contains a path to another file or directory. It gets its own inode, but its content is just the target path string. The kernel follows that path transparently when you access the symlink.<\/p>\n<pre>ln -s \/path\/to\/target \/path\/to\/symlink<\/pre>\n<p>Example:<\/p>\n<pre>echo \"world\" &gt; target.txt\r\nln -s target.txt symlink.txt\r\n\r\nls -li target.txt symlink.txt<\/pre>\n<p>Output:<\/p>\n<pre>1048578 -rw-r--r-- 1 user user 6 Jan 10 09:01 target.txt\r\n1048579 lrwxrwxrwx 1 user user 10 Jan 10 09:01 symlink.txt -&gt; target.txt<\/pre>\n<p>Different inodes. The symlink has its own inode (<code>1048579<\/code>) and the <code>l<\/code> at the start of the permissions flags it as a symlink. The <code>-&gt; target.txt<\/code> shows where it points.<\/p>\n<p>Now delete the target:<\/p>\n<pre>rm target.txt\r\ncat symlink.txt<\/pre>\n<p>Output:<\/p>\n<pre>cat: symlink.txt: No such file or directory<\/pre>\n<p>The symlink still exists but it&#8217;s broken. This is called a <strong>dangling symlink<\/strong>. The file it points to is gone.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"Symlink_Advantages\"><\/span>Symlink Advantages<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<ul>\n<li>Can cross filesystem boundaries.<\/li>\n<li>Can link to directories.<\/li>\n<li>Can link to files that do not yet exist.<\/li>\n<li>Easy to identify visually with <code>ls -l<\/code>.<\/li>\n<li>Can be updated without touching the target.<\/li>\n<\/ul>\n<h2><span class=\"ez-toc-section\" id=\"Side-by-Side_Comparison\"><\/span>Side-by-Side Comparison<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<ul>\n<li><strong>Hard link:<\/strong> Same inode as target. Survives deletion of original. Cannot cross filesystems. Cannot link directories.<\/li>\n<li><strong>Symlink:<\/strong> Own inode, stores a path. Breaks if target is deleted. Can cross filesystems. Can link directories.<\/li>\n<\/ul>\n<p>In practice, symlinks are used far more often because they are more flexible. But hard links solve specific problems that symlinks cannot.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Common_ln_Options\"><\/span>Common ln Options<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<pre>ln [OPTION] TARGET LINK_NAME<\/pre>\n<ul>\n<li><code>-s<\/code> : Create a symbolic link instead of a hard link.<\/li>\n<li><code>-f<\/code> : Remove the existing destination file if it exists.<\/li>\n<li><code>-n<\/code> : Do not dereference the link destination if it is a symlink to a directory. Useful with <code>-sf<\/code> when updating a symlink that points to a directory.<\/li>\n<li><code>-v<\/code> : Verbose. Print the name of each linked file.<\/li>\n<li><code>-r<\/code> : Create symbolic links relative to the link location (GNU ln).<\/li>\n<li><code>-i<\/code> : Prompt before removing destinations.<\/li>\n<li><code>-b<\/code> : Make a backup of each existing destination file.<\/li>\n<\/ul>\n<p>The full set of flags, including the less common ones, lives in the <a href=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/www.gnu.org\/software\/coreutils\/manual\/html_node\/ln-invocation.html\" target=\"_blank\" rel=\"noopener\">GNU coreutils manual<\/a>.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Practical_Examples\"><\/span>Practical Examples<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<h3><span class=\"ez-toc-section\" id=\"Symlink_a_config_directory\"><\/span>Symlink a config directory<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>A common pattern for dotfile management or deploying configs from a central location. Create the target with <a href=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/touch-command-in-linux-w-examples\/\" target=\"_blank\" rel=\"noopener\">touch<\/a> first if it does not exist yet:<\/p>\n<pre>ln -s \/opt\/myapp\/conf\/app.conf \/etc\/myapp\/app.conf<\/pre>\n<p>Now <code>\/etc\/myapp\/app.conf<\/code> points to the real config. Update the file in <code>\/opt\/myapp\/conf\/<\/code> and the change is reflected everywhere the symlink is used.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"Create_a_versioned_symlink_for_binaries\"><\/span>Create a versioned symlink for binaries<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>This is standard practice for managing multiple versions of software:<\/p>\n<pre>ln -s \/usr\/local\/bin\/python3.11 \/usr\/local\/bin\/python3\r\n\r\n# Later, upgrade to 3.12:\r\nln -sf \/usr\/local\/bin\/python3.12 \/usr\/local\/bin\/python3<\/pre>\n<p>The <code>-f<\/code> flag removes the old symlink and creates the new one in a single step. No dangling links left behind.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"Updating_a_symlink_pointing_to_a_directory\"><\/span>Updating a symlink pointing to a directory<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>This one catches people out. If <code>current<\/code> is a symlink pointing to a directory, running <code>ln -sf new_dir current<\/code> may place the new symlink <em>inside<\/em> the directory that <code>current<\/code> points to rather than replacing <code>current<\/code>. Use <code>-sfn<\/code> to avoid this:<\/p>\n<pre># Wrong approach (may go inside the directory):\r\nln -sf \/opt\/myapp-2.0 \/opt\/myapp\r\n\r\n# Correct approach:\r\nln -sfn \/opt\/myapp-2.0 \/opt\/myapp<\/pre>\n<p><em>Note: The <code>-n<\/code> flag tells <code>ln<\/code> to treat the destination as a normal file if it is a symlink to a directory. This is the safe way to atomically swap a directory symlink.<\/em><\/p>\n<h3><span class=\"ez-toc-section\" id=\"Hard_link_for_an_atomic_backup_snapshot\"><\/span>Hard link for an atomic backup snapshot<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Hard links are excellent for efficient incremental backups. Tools like <code>rsync --link-dest<\/code> use this pattern. Files that have not changed share inodes between backup snapshots, using no extra disk space:<\/p>\n<pre>rsync -a --link-dest=\/backups\/yesterday\/ \/home\/user\/ \/backups\/today\/<\/pre>\n<p>Files unchanged between runs appear in both <code>\/backups\/yesterday\/<\/code> and <code>\/backups\/today\/<\/code> but only occupy disk space once. This is one of the best real-world uses for hard links, and <a href=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/rsync.samba.org\/\" target=\"_blank\" rel=\"noopener\">rsync<\/a> documents the exact semantics of <code>--link-dest<\/code> if you plan to build a rotation script around it. Filesystem behavior under heavy link churn ties directly into <a href=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/linux-server-performance-disk-io-slowing-application\/\" target=\"_blank\" rel=\"noopener\">disk I\/O performance<\/a>.<\/p>\n<p>There is a sharp edge here. Those snapshots are hard links, not copies. Open a file in <code>\/backups\/yesterday\/<\/code>, save it in place, and you have just edited the live file in <code>\/home\/user\/<\/code> and every other snapshot sharing that inode. Nothing warns you. Mount hard-linked backup trees read-only, or at least keep them off any path you browse casually.<\/p>\n<p><em>Note: this only applies to in-place writes. Deleting a file from a snapshot is safe, since that drops one name and decrements the link count. Most editors write a new file and rename it over the old one, which is also safe. It is the ones that truncate and rewrite in place that bite you.<\/em><\/p>\n<h3><span class=\"ez-toc-section\" id=\"Find_all_hard_links_to_an_inode\"><\/span>Find all hard links to an inode<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>If you suspect a file has multiple hard links, use <a href=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/using-the-find-command-in-linux-with-examples\/\" target=\"_blank\" rel=\"noopener\">find<\/a> with <code>-inum<\/code>:<\/p>\n<pre># First, get the inode number:\r\nls -i myfile.txt\r\n# Output: 1048577 myfile.txt\r\n\r\n# Then find all links to that inode:\r\nfind \/home -xdev -inum 1048577 2&gt;\/dev\/null<\/pre>\n<p>Point <code>find<\/code> at the mount the file lives on, not at <code>\/<\/code>. Inode numbers are only unique within a single filesystem, so scanning every mount is slow and will turn up unrelated files that happen to share the number. The <code>-xdev<\/code> flag stops the search from crossing mount points.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"Identify_and_fix_dangling_symlinks\"><\/span>Identify and fix dangling symlinks<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Over time, especially after package upgrades or software moves, symlinks can go stale. Find them with:<\/p>\n<pre>find \/etc -xtype l 2&gt;\/dev\/null<\/pre>\n<p><code>-xtype l<\/code> matches symlinks where the target does not exist. A quick audit of <code>\/etc<\/code>, <code>\/usr\/local\/bin<\/code>, and your app directories will surface any broken links.<\/p>\n<p>Once you have read that list and know what you are looking at, <code>-delete<\/code> clears them:<\/p>\n<pre>find \/etc -xtype l -delete<\/pre>\n<p>Run the plain <code>find<\/code> first and read the output. A dangling symlink is sometimes pointing at a filesystem that just is not mounted right now. Deleting it fixes nothing and loses you the link.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Absolute_vs_Relative_Symlinks\"><\/span>Absolute vs. Relative Symlinks<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>By default, <code>ln -s<\/code> stores whatever path you give it verbatim. If you use an absolute path, the symlink always resolves to that exact location regardless of where the symlink itself lives. If you use a relative path, the symlink resolves relative to the directory it&#8217;s in.<\/p>\n<pre># Absolute symlink:\r\nln -s \/etc\/nginx\/nginx.conf \/home\/user\/nginx.conf\r\n\r\n# Relative symlink:\r\ncd \/home\/user\r\nln -s ..\/..\/etc\/nginx\/nginx.conf nginx.conf<\/pre>\n<p>Relative symlinks are portable: if you move both the symlink and the directory structure together (in a tarball, for example), the link still works. Absolute symlinks break if the root changes, which matters inside <a href=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/install-docker-linux-run-container\/\" target=\"_blank\" rel=\"noopener\">Docker containers<\/a> or chroot environments.<\/p>\n<p>GNU <code>ln<\/code> has the <code>-r<\/code> flag to automatically compute a relative path for you:<\/p>\n<pre>ln -sr \/etc\/nginx\/nginx.conf \/home\/user\/nginx.conf<\/pre>\n<h2><span class=\"ez-toc-section\" id=\"Checking_and_Debugging_Links\"><\/span>Checking and Debugging Links<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>A few commands that help when working with links:<\/p>\n<pre># Show link count and inode:\r\nls -li file.txt\r\n\r\n# Show where a symlink points:\r\nreadlink symlink.txt\r\n\r\n# Show full resolved path, following all symlinks:\r\nreadlink -f symlink.txt\r\n\r\n# Show file info including link count:\r\nstat file.txt\r\n\r\n# Check if something is a symlink in a script:\r\nif [ -L \/path\/to\/file ]; then echo \"is a symlink\"; fi\r\n\r\n# Check if a symlink target actually exists:\r\nif [ -e \/path\/to\/symlink ]; then echo \"target exists\"; fi<\/pre>\n<p><em>Note: <code>-L<\/code> tests for symlink. <code>-e<\/code> tests that the target exists. A dangling symlink passes <code>-L<\/code> but fails <code>-e<\/code>.<\/em><\/p>\n<h2><span class=\"ez-toc-section\" id=\"When_to_Use_Which\"><\/span>When to Use Which<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Use a <strong>hard link<\/strong> when:<\/p>\n<ul>\n<li>Both files are on the same filesystem and you want the data to survive deletion of either name.<\/li>\n<li>You are doing incremental backups and want deduplication at the filesystem level.<\/li>\n<li>You need a second name for a file in the same or different directory on the same mount.<\/li>\n<\/ul>\n<p>Use a <strong>symlink<\/strong> when:<\/p>\n<ul>\n<li>You are linking across filesystems or partitions.<\/li>\n<li>You are linking a directory.<\/li>\n<li>You need a versioned pointer that can be atomically swapped (e.g., <code>\/opt\/app\/current -&gt; \/opt\/app-2.1\/<\/code>).<\/li>\n<li>You want the link relationship to be obvious and auditable.<\/li>\n<li>You are managing dotfiles, config deployments, or anything that needs to be easily repointed.<\/li>\n<\/ul>\n<p>In most day-to-day sysadmin work, symlinks cover 95% of use cases. Hard links are a specialist tool, but a useful one when the situation calls for them. The <a href=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/linux-networking-commands-scripts\/\" target=\"_blank\" rel=\"noopener\">60 Linux networking commands reference<\/a> and the <a href=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/linux-ncdu-manage-large-directories\/\" target=\"_blank\" rel=\"noopener\">ncdu and mc guide<\/a> are worth bookmarking alongside this one if you spend time managing files and filesystems.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Practical_Pitfalls\"><\/span>Practical Pitfalls<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<h3><span class=\"ez-toc-section\" id=\"Copying_symlinks_with_cp\"><\/span>Copying symlinks with cp<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>By default, <code>cp<\/code> follows symlinks and copies the target file, not the link itself. Use <code>-P<\/code> to preserve the symlink:<\/p>\n<pre># Copies the target content:\r\ncp symlink.txt \/backup\/\r\n\r\n# Copies the symlink itself:\r\ncp -P symlink.txt \/backup\/<\/pre>\n<h3><span class=\"ez-toc-section\" id=\"tar_and_symlinks\"><\/span>tar and symlinks<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p><a href=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/tar-command-in-linux-with-examples\/\" target=\"_blank\" rel=\"noopener\">tar<\/a> preserves symlinks by default but follows them with <code>-h<\/code>. Know which behavior you want before archiving. Note that <code>f<\/code> must come last in a bundled flag group, since it consumes the next argument as the archive name:<\/p>\n<pre># Preserves symlinks as symlinks:\r\ntar -czf archive.tar.gz directory\/\r\n\r\n# Dereferences symlinks, stores actual file content:\r\ntar -czhf archive.tar.gz directory\/<\/pre>\n<h3><span class=\"ez-toc-section\" id=\"Permissions_on_symlinks\"><\/span>Permissions on symlinks<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>The permissions shown on a symlink (<code>lrwxrwxrwx<\/code>) are always <code>777<\/code> and are meaningless. The permissions that actually control access are those on the target file. Do not try to <code>chmod<\/code> a symlink directly; it will change the target instead.<\/p>\n<p>Ownership is a different story. A symlink does carry its own owner and group, and <code>chown -h<\/code> changes them without touching the target:<\/p>\n<pre># Changes the target's ownership:\r\nchown alice:alice symlink.txt\r\n\r\n# Changes the symlink's own ownership:\r\nchown -h alice:alice symlink.txt<\/pre>\n<p>This matters in sticky-bit directories like <code>\/tmp<\/code>, where the kernel checks symlink ownership before following a link. That check is what blocks a whole class of symlink attacks, so it is worth knowing <code>-h<\/code> exists before you need it. More on the underlying model in the <a href=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/linux-file-permissions-explained-chmod-chown-and-umask-in-practice\/\" target=\"_blank\" rel=\"noopener\">Linux file permissions guide<\/a>.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Summary\"><\/span>Summary<span class=\"ez-toc-section-end\"><\/span><\/h2><div class=\"hayden-end-of-article hayden-entity-placement\" id=\"hayden-1657120899\"><div data-nosnippet id=\"hayden-1063362739\" style=\"margin-left: auto;margin-right: auto;text-align: center;\"><a href=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/www.manageengine.com\/it-operations-management\/infrastructure-observability\/server-observability.html?utm_source=linuxblog&#038;utm_medium=bannerad&#038;utm_campaign=ServerObservability\" data-bid=\"1\" data-no-instant=\"1\" rel=\"noopener nofollow sponsored\">\r\n  <img fetchpriority=\"high\" src=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/static.linuxblog.io\/wp-content\/uploads\/2026\/06\/868\u00d7300V3_x1.png\"\r\n       srcset=\"https:\/\/round-lake.dustinice.workers.dev:443\/https\/static.linuxblog.io\/wp-content\/uploads\/2026\/06\/868\u00d7300V3_x1.png 1x,\r\n               https:\/\/round-lake.dustinice.workers.dev:443\/https\/static.linuxblog.io\/wp-content\/uploads\/2026\/06\/868\u00d7300V3.png 2x\"\r\n       alt=\"ManageEngine Site24x7 - Fix Linux issues in minutes, not hours!\"\r\n       width=\"868\" height=\"300\"\r\n       loading=\"eager\"\r\n       style=\"display:block;\">\r\n<\/a><\/div><div align=\"center\" style=\"font-style: italic; font-size: 0.85em; font-weight: normal; margin-top: 4px;\">\r\n\tAdvertisement\r\n<\/div><\/div>\n<p>The <code>ln<\/code> command is small but the concepts behind it are foundational. Hard links give you multiple names for the same data with no extra disk cost, and the data only disappears when the last name is gone. Symlinks give you a flexible pointer that can span filesystems and directories, but breaks if the target moves or is deleted.<\/p>\n<p>Get comfortable with <code>readlink<\/code>, <code>stat<\/code>, and <code>ls -li<\/code> alongside <code>ln<\/code>. Together they give you a clear picture of what is happening at the filesystem level. And use <code>-sfn<\/code> when atomically swapping directory symlinks: it is one of those small habits that prevents annoying incidents at 2am.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Hard links and soft links in Linux both use the ln command but behave very differently. This practical guide explains exactly how each works, when to use which, and how to avoid the common pitfalls that catch even experienced sysadmins.<\/p>\n","protected":false},"author":6,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"publish_to_discourse":"1","publish_post_category":"54","wpdc_auto_publish_overridden":"1","wpdc_topic_tags":"","wpdc_pin_topic":"","wpdc_pin_until":"","discourse_post_id":"25190","discourse_permalink":"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxcommunity.io\/t\/ln-command-in-linux-hard-links-vs-symlinks\/10272","wpdc_publishing_response":"","wpdc_publishing_error":"","footnotes":""},"categories":[1,11],"tags":[],"class_list":["post-22478","post","type-post","status-publish","format-standard","hentry","category-articles","category-linux"],"_links":{"self":[{"href":"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/wp-json\/wp\/v2\/posts\/22478","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/wp-json\/wp\/v2\/users\/6"}],"replies":[{"embeddable":true,"href":"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/wp-json\/wp\/v2\/comments?post=22478"}],"version-history":[{"count":4,"href":"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/wp-json\/wp\/v2\/posts\/22478\/revisions"}],"predecessor-version":[{"id":22495,"href":"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/wp-json\/wp\/v2\/posts\/22478\/revisions\/22495"}],"wp:attachment":[{"href":"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/wp-json\/wp\/v2\/media?parent=22478"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/wp-json\/wp\/v2\/categories?post=22478"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/round-lake.dustinice.workers.dev:443\/https\/linuxblog.io\/wp-json\/wp\/v2\/tags?post=22478"}],"curies":[{"name":"wp","href":"https:\/\/round-lake.dustinice.workers.dev:443\/https\/api.w.org\/{rel}","templated":true}]}}