{"id":7666,"date":"2026-05-14T21:56:09","date_gmt":"2026-05-14T19:56:09","guid":{"rendered":"https:\/\/swissmakers.ch\/?p=7666"},"modified":"2026-05-15T12:18:19","modified_gmt":"2026-05-15T10:18:19","slug":"inodes-hardlinks-and-symlinks","status":"publish","type":"post","link":"https:\/\/swissmakers.ch\/en\/inodes-hardlinks-und-symlinks\/","title":{"rendered":"Inodes, hardlinks and symlinks"},"content":{"rendered":"\n<p>Wie genau ein Linux System Dateinen und Ordner Speichert oder wie dann darauf referenziert wird, ist oft nicht allen klar.<\/p>\n\n\n\n<p>Bevor wir beginnen einmal kurz die Grundlagen \u00fcber Inodes. Inodes sind Datenstrukturen, dargestellt als eine ganze Zahl und einzigartig je Datei. Sie enthalten s\u00e4mtliche Informationen wie Zugriffsrechte (Lesen, Schreiben, Ausf\u00fchren), Eigentum, Gruppe, Dateiart, Dateigr\u00f6sse, SELinux-Kontext und auch die Anzahl der &#171;Links&#187; welche auf den Inhalt der Datei zeigen. Der Inhalt der Datei selbst, sowie der Dateinamen werden seperat gespeichert und sind nicht im Inode enthalten.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Inodes anzeigen<\/h2>\n\n\n\n<p>Die Inode-Nummer einer Datei zeigt <code>ls -i<\/code> an. Vollst\u00e4ndige Metadaten liefert <code>stat<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">[root@rlwebp01 ~]# ls -i swissmakers-apache-dos.conf\n537286221 swissmakers-apache-dos.conf\n\n[root@rlwebp01 ~]# stat swissmakers-apache-dos.conf\n  File: swissmakers-apache-dos.conf\n  Size: 520       \tBlocks: 8          IO Block: 4096   regular file\nDevice: fd00h\/64768d\tInode: 537286221   Links: 1\nAccess: (0644\/-rw-r--r--)  Uid: (    0\/    root)   Gid: (    0\/    root)\nContext: unconfined_u:object_r:admin_home_t:s0\nAccess: 2025-08-04 10:39:30.552986781 +0200\nModify: 2025-06-30 01:07:07.825337403 +0200\nChange: 2025-06-30 01:07:07.825337403 +0200\n Birth: 2025-06-30 01:07:07.825337403 +0200<\/code><\/pre>\n\n\n\n<p>Der Dateiname der angezeigt wird, kommt vom Verzeichniseintrag, nicht nicht vom Inode. Ein Verzeichnis ist im Kern eine Tabelle, die Dateinamen auf Inode-Nummern abbildet. Diese Trennung ist die Voraussetzung daf\u00fcr, dass Hardlinks und Symlinks \u00fcberhaupt funktionieren k\u00f6nnen.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Wenn Inodes ausgehen<\/h2>\n\n\n\n<p>Bei der Formatierung eines Dateisystems wird eine fixe Anzahl Inodes reserviert. Bei ext4 ist die Standardeinstellung ein Inode pro 16 KB Speicher. Auf gr\u00f6sseren Systemen mit vielen kleinen Dateien (z.B. Mailserver, global Session-Caches, Elastcsearch Clustern ect..) reicht dieses Verh\u00e4ltnis nicht immer aus.<\/p>\n\n\n\n<p>Das System meldet dann &#171;No space left on device&#187;, obwohl <code>df -h<\/code> ausreichend freien Speicher anzeigt. Der zweite Blick geh\u00f6rt in solchen F\u00e4llen <code>df -i<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">[root@rlwebp01 ~]# df -i\nFilesystem                                     Inodes  IUsed     IFree IUse% Mounted on\ndevtmpfs                                      2008078    474   2007604    1% \/dev\ntmpfs                                         2013666      3   2013663    1% \/dev\/shm\ntmpfs                                          819200   1000    818200    1% \/run\n\/dev\/mapper\/rl-root                         153659392 648597 153010795    1% \/\n\/dev\/sda1                                      524288    374    523914    1% \/boot<\/code><\/pre>\n\n\n\n<p>Bei <code>IUse% 100<\/code> l\u00e4sst sich das Problem nur beheben, indem nicht mehr ben\u00f6tigte Dateien gel\u00f6scht oder das Dateisystem mit h\u00f6herer Inode-Dichte neu erstellt wird (<code>mkfs.ext4 -i \u2026<\/code>).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Hardlinks<\/h2>\n\n\n\n<p>Ein Hardlink ist ein zus\u00e4tzlicher Verzeichniseintrag, der auf denselben Inode zeigt wie ein bereits bestehender Eintrag. <strong>Es gibt kein Original und keine Kopie<\/strong>, beide Namen sind gleichwertig. Der Inode kennt nur seinen Link-Counter.<\/p>\n\n\n\n<p>Erstellt wird ein Hardlink mit <code>ln<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">[root@rlwebp01 blog]# echo \"Swissmakers\" &gt; original.txt\n[root@rlwebp01 blog]# ln original.txt hardlink.txt\n\n[root@rlwebp01 blog]# ls -li\ntotal 8\n827116010 -rw-r--r--. 2 root root 12 May 14 15:13 hardlink.txt\n827116010 -rw-r--r--. 2 root root 12 May 14 15:13 original.txt<\/code><\/pre>\n\n\n\n<p>Beide Eintr\u00e4ge tragen dieselbe Inode-Nummer, der Link-Counter steht auf 2. Eine \u00c4nderung an <code>original.txt<\/code> ist sofort auch in <code>hardlink.txt<\/code> sichtbar, weil beide Namen auf exakt dieselben Datenbl\u00f6cke verweisen.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Einschr\u00e4nkungen<\/h3>\n\n\n\n<p>Hardlinks haben zwei H\u00fcrden:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Dateisystem-gebunden<\/strong>: Inode-Nummern sind nur innerhalb eines Dateisystems eindeutig. \u00dcber Mountpoints hinweg funktionieren Hardlinks nicht.<\/li>\n\n\n\n<li><strong>Keine Verzeichnisse<\/strong>: Hardlinks auf Verzeichnisse sind per Default (f\u00fcr unprivilegierte User) gesperrt. Andernfalls liessen sich Schleifen erzeugen, die <code>find<\/code>, <code>du<\/code> oder Backup-Tools in Endlosschleifen schicken w\u00fcrden.<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">Wo werden Hardlinks Eingesetzt?<\/h3>\n\n\n\n<p>Die St\u00e4rke von Hardlinks liegt bei platzsparenden Snapshot-Backups. Tools wie <code>rsnapshot<\/code> oder <code>rsync<\/code> k\u00f6nnen pro Snapshot ein vollst\u00e4ndiges Verzeichnis erstellen, kopieren aber z.B. nur ver\u00e4nderte Dateien neu. Unver\u00e4nderte Dateien werden als zus\u00e4tzlicher Hardlink im neuen Snapshot eingeh\u00e4ngt:<\/p>\n\n\n<div class=\"gb-container gb-container-a61a809e\">\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">[root@rlwebp01 blog]# rsync -a --link-dest=\/tmp\/backup\/2026-05-08 \/tmp\/blog\/ \/tmp\/backup\/2026-05-09\/<\/code><\/pre>\n\n<\/div>\n\n\n<p><code>rsync<\/code>&nbsp;wird \u00fcberpr\u00fcfen, ob Dateien, die im Quellverzeichnis (<code>\/tmp\/blog\/<\/code>) vorhanden sind, auch im Link-Verzeichnis (<code>\/tmp\/backup\/2026-05-08<\/code>) vorhanden sind. Wenn dies der Fall ist, werden Hardlinks zu diesen Dateien im Zielverzeichnis (<code>\/tmp\/backup\/2026-05-09\/<\/code>) erstellt, anstatt die Dateien physisch zu kopieren. Nach aussen wirkt jeder Snapshot wie ein Vollbackup. Der tats\u00e4chliche Speicherbedarf entspricht der Summe der \u00c4nderungen zwischen den Snapshots.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Symlinks<\/h2>\n\n\n\n<p>Ein Symlink (Symbolic Link, auch Soft Link genannt) ist eine eigenst\u00e4ndige Datei mit eigenem Inode. Ihr Inhalt besteht aus einem Pfad. Bei jedem Zugriff folgt der Kernel diesem Pfad zur Zieldatei. Konzeptionell entspricht ein Symlink einer Verkn\u00fcpfung unter Windows oder einem Alias auf macOS.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">[root@rlwebp01 blog]# ln -s \/etc\/redhat-release symlink-to-sysrelaese\n\n[root@rlwebp01 blog]# ls -li symlink-to-sysrelaese\n827116011 lrwxrwxrwx. 1 root root 19 May 14 15:41 symlink-to-sysrelaese -&gt; \/etc\/redhat-release<\/code><\/pre>\n\n\n\n<p>Das <code>l<\/code> in den Permissions kennzeichnet die Datei als Link, der Pfeil zeigt auf das Original. Den reinen Pfadinhalt gibt <code>readlink<\/code> zur\u00fcck. Mit <code>-f<\/code> werden zus\u00e4tzlich verschachtelte Symlinks und relative Pfade aufgel\u00f6st, sofern vorhanden:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">[root@rlwebp01 blog]# readlink symlink-to-sysrelaese\n\/etc\/redhat-release\n\n[root@rlwebp01 blog]# readlink -f symlink-to-sysrelaese\n\/etc\/rocky-release<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Nice to Know<\/h3>\n\n\n\n<p>Symlinks d\u00fcrfen praktisch alles, was Hardlinks nicht d\u00fcrfen:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Auf Ziele in anderen Dateisystemen zeigen<\/li>\n\n\n\n<li>Per Default auf Verzeichnisse zeigen<\/li>\n\n\n\n<li>Relative oder absolute Pfade enthalten<\/li>\n<\/ul>\n\n\n\n<p>Der Preis daf\u00fcr: Wird das Ziel verschoben oder gel\u00f6scht, zeigt der Symlink ins Leere. Solche &#171;dangling symlinks&#187; lassen sich mit <code>find<\/code> aufsp\u00fcren (im optimalen Fall wird nicht angezeigt):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">[root@rlwebp01 blog]# find \/etc -xtype l<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Wo werden Symlinks Eingesetzt?<\/h3>\n\n\n\n<p>In einem modernen Linux-System praktisch \u00fcberall. Drei sehr typische Einsatzgebiete sind dabei:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Versionsmanagement<\/strong> im Debian Universum, via <code>update-alternatives<\/code>: <code>\/usr\/bin\/python<\/code> zeigt auf <code>\/etc\/alternatives\/python<\/code>, dieser wiederum auf <code>\/usr\/bin\/python3.11<\/code>. Ein Versionswechsel ist im Wesentlichen eine Symlink-\u00c4nderung.<\/li>\n\n\n\n<li><strong>Systemd<\/strong> zum aktivieren oder deaktivieren von Services, welche beim Boot automatisch gestartet werden sollen wird nach absetzen von z.B. <code>systemctl enable httpd<\/code> ein Symlink erstellt: (<code>Created symlink \/etc\/systemd\/system\/multi-user.target.wants\/httpd.service \u2192 \/usr\/lib\/systemd\/system\/httpd.service.<\/code>) <\/li>\n\n\n\n<li><strong>Site-Konfigurationen<\/strong>: Bei Nginx und Apache kann ein vhost ganz einfach aktiviert werden, indem die Konfiguration aus <code>sites-available<\/code> nach <code>sites-enabled<\/code> verlinkt wird.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Hardlink oder Symlink?<\/h2>\n\n\n\n<p>Die Entscheidung l\u00e4sst sich auf wenige Kriterien reduzieren:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Anforderung<\/th><th>Hardlink<\/th><th>Symlink<\/th><\/tr><\/thead><tbody><tr><td>Verlinkung \u00fcber mehrere Dateisysteme<\/td><td>Nein<\/td><td>Ja<\/td><\/tr><tr><td>Verlinkung auf Verzeichnisse<\/td><td>Nein<\/td><td>Ja<\/td><\/tr><tr><td>Bleibt g\u00fcltig nach Umbenennen des Ziels<\/td><td>Ja<\/td><td>Nein<\/td><\/tr><tr><td>Als Link sichtbar in <code>ls -l<\/code><\/td><td>Nein<\/td><td>Ja<\/td><\/tr><tr><td>Eigene Permissions<\/td><td>Nein (Inode-gebunden)<\/td><td>Ja (jedoch greifen schlussendlich diese des Ziels)<\/td><\/tr><tr><td>Dateigr\u00f6sse<\/td><td>wie Original<\/td><td>L\u00e4nge des Pfads in Bytes<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>Einfach gesagt: <strong>Hardlinks<\/strong> f\u00fcr deduplizierte Snapshots innerhalb eines Volumes, <strong>Symlinks<\/strong> f\u00fcr alles andere.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Was <code>rm<\/code> tats\u00e4chlich macht<\/h2>\n\n\n\n<p><code>rm<\/code> l\u00f6scht keine Dateien. Der Befehl ruft den Syscall <code>unlink(2)<\/code> auf, entfernt also einen Verzeichniseintrag und dekrementiert den Link-Counter im Inode. Der effektive Storage wird erst freigegeben, wenn zwei Bedingungen erf\u00fcllt sind:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Der Link-Counter steht auf 0<\/li>\n\n\n\n<li>Kein Prozess h\u00e4lt die Datei mehr offen<\/li>\n<\/ol>\n\n\n\n<p>Daraus ergeben sich zwei Verhaltensweisen, die im Server-Alltag regelm\u00e4ssig auftauchen.<\/p>\n\n\n\n<p><strong>Logrotation funktioniert ohne Service-Neustart.<\/strong> Solange ein Prozess die alte Logdatei offen h\u00e4lt, schreibt er weiter hinein, selbst wenn der Verzeichniseintrag bereits entfernt wurde. Erst beim Schliessen des File Descriptors oder nach einem <code>SIGHUP<\/code> zur Wieder\u00f6ffnung wird der Speicher freigegeben.<\/p>\n\n\n\n<p>Der <strong>Speicher bleibt nach <code>rm<\/code> belegt.<\/strong> Wenn ein Prozess eine grosse Datei offen h\u00e4lt und diese gel\u00f6scht wird, gibt das Dateisystem den Platz nicht frei. Diagnostizieren l\u00e4sst sich das mit <code>lsof<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">[root@rlwebp01 blog]# lsof | grep deleted<\/code><\/pre>\n\n\n\n<p>In dieser Situation hilft kein weiteres <code>rm<\/code>. Erst der Restart oder ein <code>kill -HUP<\/code> des haltenden Prozesses gibt den ben\u00f6tigten Platz frei. Alternativ l\u00e4sst sich der File Descriptor unter <code>\/proc\/&lt;pid&gt;\/fd\/&lt;n&gt;<\/code> mit <code>truncate -s 0<\/code> direkt leeren, ohne den Prozess zu stoppen.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Fazit<\/h2>\n\n\n\n<p>Dateinamen in Linux sind nur Verzeichniseintr\u00e4ge, der Inode verwaltet die eigentliche Datei. Aus dieser Trennung erkl\u00e4ren sich mehrere Eigenheiten, die im Informatiker-Alltag auftauchen: das Dateisystem ist trotz freiem Speicher voll, gel\u00f6schte Logfiles wachsen weiter.<\/p>\n\n\n\n<p>Hardlinks und Symlinks machen diese Trennung weiter nutzbar. Hardlinks f\u00fcr deduplizierte Daten innerhalb eines Volumes, Symlinks als flexible Verkn\u00fcpfung \u00fcber mehrere Dateisysteme hinweg. Beide geh\u00f6ren seit den fr\u00fchen Unix-Tagen zum Standardinventar der Linux-Administration und tauchen in fast jeder produktiven Umgebung auf.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Wie genau ein Linux System Dateinen und Ordner Speichert oder wie dann darauf referenziert wird, &#8230; <\/p>\n<p class=\"read-more-container\"><a title=\"Inodes, hardlinks and symlinks\" class=\"read-more button\" href=\"https:\/\/swissmakers.ch\/en\/inodes-hardlinks-und-symlinks\/#more-7666\" aria-label=\"Read more about Inodes, hardlinks and symlinks\">Read more<\/a><\/p>","protected":false},"author":2,"featured_media":8359,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_eb_attr":"","_kad_blocks_custom_css":"","_kad_blocks_head_custom_js":"","_kad_blocks_body_custom_js":"","_kad_blocks_footer_custom_js":"","footnotes":""},"categories":[55],"tags":[],"class_list":["post-7666","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-linux","generate-columns","tablet-grid-50","mobile-grid-100","grid-parent","grid-50","resize-featured-image"],"taxonomy_info":{"category":[{"value":55,"label":"Linux"}]},"featured_image_src_large":["https:\/\/swissmakers.ch\/wp-content\/uploads\/2026\/05\/blog_inodes-hardlinks-symplinks.svg",1200,630,true],"author_info":{"display_name":"Michael Reber","author_link":"https:\/\/swissmakers.ch\/en\/author\/michael\/"},"comment_info":0,"category_info":[{"term_id":55,"name":"Linux","slug":"linux","term_group":0,"term_taxonomy_id":55,"taxonomy":"category","description":"","parent":0,"count":11,"filter":"raw","cat_ID":55,"category_count":11,"category_description":"","cat_name":"Linux","category_nicename":"linux","category_parent":0}],"tag_info":false,"_links":{"self":[{"href":"https:\/\/swissmakers.ch\/en\/wp-json\/wp\/v2\/posts\/7666","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/swissmakers.ch\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/swissmakers.ch\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/swissmakers.ch\/en\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/swissmakers.ch\/en\/wp-json\/wp\/v2\/comments?post=7666"}],"version-history":[{"count":18,"href":"https:\/\/swissmakers.ch\/en\/wp-json\/wp\/v2\/posts\/7666\/revisions"}],"predecessor-version":[{"id":8383,"href":"https:\/\/swissmakers.ch\/en\/wp-json\/wp\/v2\/posts\/7666\/revisions\/8383"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/swissmakers.ch\/en\/wp-json\/wp\/v2\/media\/8359"}],"wp:attachment":[{"href":"https:\/\/swissmakers.ch\/en\/wp-json\/wp\/v2\/media?parent=7666"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/swissmakers.ch\/en\/wp-json\/wp\/v2\/categories?post=7666"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/swissmakers.ch\/en\/wp-json\/wp\/v2\/tags?post=7666"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}