<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Härtung on Kabelbruch</title><link>https://kabelbruch.de/tags/h%C3%A4rtung/</link><description>Recent content in Härtung on Kabelbruch</description><image><title>Kabelbruch</title><url>https://kabelbruch.de/og-image.png</url><link>https://kabelbruch.de/og-image.png</link></image><generator>Hugo</generator><language>de-DE</language><lastBuildDate>Fri, 30 Oct 2026 23:13:00 +0100</lastBuildDate><atom:link href="https://kabelbruch.de/tags/h%C3%A4rtung/index.xml" rel="self" type="application/rss+xml"/><item><title>Wie Kabelbruch entstanden ist – Teil 1: Die Idee und der Server</title><link>https://kabelbruch.de/posts/kabelbruch-teil-1-idee-und-server/</link><pubDate>Fri, 30 Oct 2026 23:13:00 +0100</pubDate><guid>https://kabelbruch.de/posts/kabelbruch-teil-1-idee-und-server/</guid><description>Warum ich einen eigenen Tech-Blog starte, wieso er nicht im Homelab läuft und wie ich den kleinen VPS dafür Schritt für Schritt abgesichert habe.</description><content:encoded><![CDATA[<p>Ein Blog über Homelab, Netzwerk und Security, der mit einem Server anfängt – wie sollte es auch anders sein. In diesem ersten Teil geht es darum, warum es Kabelbruch überhaupt gibt, warum die Seite <strong>nicht</strong> in meinem Homelab läuft und wie ich den kleinen Server dafür abgesichert habe. Inklusive der Stellen, an denen ich mich selbst ausgesperrt habe. Fast.</p>
<h2 id="warum-ein-blog--und-warum-kabelbruch">Warum ein Blog – und warum „Kabelbruch“?</h2>
<p>Ich bin ITler, mein Alltag dreht sich um Netzwerk und Security. Und „Kabelbruch“ passte eigentlich schon immer gut zu dem, was ich mache – ich fand den Namen einfach witzig. Ein Kabelbruch ist ja genau die Art Fehler, nach der man stundenlang sucht, obwohl die Konfiguration perfekt ist.</p>
<p>Den Blog gibt es, weil ich meine Projekte gern teilen möchte: was ich so baue, woran ich scheitere und welche Erfolge ich habe. Deshalb steht über allem das Motto: <strong>Basteln, ausprobieren, auch mal scheitern und darüber schreiben.</strong> Die Fehlschläge gehören hier ausdrücklich dazu – meistens lernt man aus denen sowieso mehr.</p>
<p>Außerdem bin ich ein großer Fan von Open Source und versuche, in meinem privaten Umfeld wo immer möglich darauf zu setzen. Das zieht sich auch durch diese Seite: Vom Betriebssystem über den Webserver bis zum Git-Server und dem Seitengenerator ist hier alles Open Source.</p>
<p>Und weil ein Blog über Homelab und Security irgendwo laufen muss, ist sein Aufbau gleich das erste Projekt. Daraus ist diese kleine Serie geworden:</p>
<ol>
<li><strong>Die Idee und der Server</strong> (dieser Teil)</li>
<li>Caddy, DNS und Security-Header</li>
<li>Die Pipeline: vom <code>git push</code> zur fertigen Seite</li>
<li>Mail, Impressum und Favicon</li>
</ol>
<h2 id="die-grundsatzentscheidungen">Die Grundsatzentscheidungen</h2>
<h3 id="statisch-statt-wordpress">Statisch statt WordPress</h3>
<p>Ich wollte eine Seite, die ich verstehe, die wenig Pflege braucht und kaum Angriffsfläche bietet. Deshalb ist Kabelbruch eine <strong>statische Website</strong>, gebaut mit <a href="https://gohugo.io/" target="_blank" rel="noopener noreferrer">Hugo</a>. Ein statischer Seitengenerator macht aus Markdown-Dateien fertige HTML-Seiten. Auf dem Server liegen am Ende nur noch Dateien – keine Datenbank, kein PHP, kein Login-Formular, das man angreifen könnte.</p>
<p>Die Artikel schreibe ich als Markdown-Dateien in einem Git-Repository. Das hat einen netten Nebeneffekt: Ich habe eine Versionsgeschichte für jeden Text und kann Code-Beispiele direkt mit Syntax-Hervorhebung einbauen.</p>
<h3 id="nicht-im-homelab">Nicht im Homelab</h3>
<p>Natürlich war die erste Idee, die Seite einfach zu Hause auf meinem Proxmox-Server laufen zu lassen. Ich habe mich trotzdem dagegen entschieden:</p>
<ul>
<li>Eine öffentliche Website im Heimnetz bedeutet eine Portfreigabe oder einen Tunnel, ein eigenes DMZ-Netz und deutlich mehr Monitoring. Das kann man machen, aber es muss gut durchdacht sein.</li>
<li>Fällt zu Hause der Strom oder das Internet aus, wäre der Blog auch weg.</li>
</ul>
<p>Mein Kompromiss: <strong>Gebaut wird die Seite im Homelab, ausgeliefert von einem kleinen Server im Rechenzentrum.</strong> Mein Heimnetz muss dafür nichts nach außen öffnen – die Verbindung geht nur von innen nach außen. Wie das genau funktioniert, zeige ich in Teil 3.</p>
<h2 id="welcher-hoster">Welcher Hoster?</h2>
<p>Meine erste Wahl wäre Hetzner gewesen. Allerdings hat Hetzner 2026 kräftig an der Preisschraube gedreht: Zum April stiegen die Cloud-Server-Preise im Schnitt um rund 32 Prozent, im Juni folgte für einige Server-Linien eine weitere Runde. Ganz allein ist Hetzner damit nicht – auch andere Anbieter haben erhöht, Grund sind vor allem die gestiegenen Preise für Arbeitsspeicher. Trotzdem wollte ich mir die Alternativen ansehen.</p>
<p>Die gute Nachricht: Für eine statische Seite braucht man fast nichts. Ich habe mich für einen kleinen VPS (Virtual Private Server, also eine eigene virtuelle Maschine beim Anbieter) bei <strong>netcup</strong> entschieden:</p>
<table>
	<thead>
			<tr>
					<th></th>
					<th></th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Tarif</td>
					<td>VPS nano G11.5s</td>
			</tr>
			<tr>
					<td>CPU / RAM / SSD</td>
					<td>2 vCores / 2 GB / 60 GB</td>
			</tr>
			<tr>
					<td>Preis</td>
					<td>3,69 € im Monat inkl. MwSt. (6 Monate Laufzeit, Stand Oktober 2026)</td>
			</tr>
			<tr>
					<td>Standort</td>
					<td>Deutschland</td>
			</tr>
			<tr>
					<td>Betriebssystem</td>
					<td>Debian 13 „trixie“</td>
			</tr>
	</tbody>
</table>
<p>Für die Website allein ist das sogar überdimensioniert. Aber ich habe schon die nächste Idee im Hinterkopf (dazu am Ende mehr), und dafür ist ein bisschen Luft ganz praktisch.</p>
<p><strong>Ein Tipp beim Preisvergleich:</strong> Manche Anbieter zeigen Preise netto, andere inklusive 19 % MwSt. Wer das nicht beachtet, vergleicht Äpfel mit Birnen.</p>
<p><strong>Und noch etwas Rechtliches:</strong> Sobald personenbezogene Daten auf dem Server landen – und eine IP-Adresse zählt schon dazu –, gehört ein <strong>Vertrag zur Auftragsverarbeitung</strong> (AV-Vertrag nach Art. 28 DSGVO) mit dem Hoster dazu. Bei netcup lässt sich der im Kundenmenü abschließen. Ich bin kein Anwalt, aber das ist eine Sache von zehn Minuten und sollte man einfach machen.</p>
<h2 id="voraussetzungen">Voraussetzungen</h2>
<p>Was ich für diesen Teil benutzt habe:</p>
<ul>
<li>Einen VPS mit <strong>Debian 13</strong> (frisch installiert, Root-Zugang per SSH)</li>
<li>Einen Linux-Rechner mit OpenSSH-Client</li>
<li>Einen vorhandenen SSH-Schlüssel vom Typ <strong>ed25519</strong></li>
</ul>
<p>In allen Beispielen steht <code>203.0.113.10</code> für die IP meines Servers und <code>thomas</code> für meinen Benutzer. Setz dort deine eigenen Werte ein.</p>
<h2 id="schritt-1-den-ssh-schlüssel-hinterlegen">Schritt 1: Den SSH-Schlüssel hinterlegen</h2>
<p>Ein SSH-Schlüssel ist sicherer als jedes Passwort – sofern man den richtigen erwischt. Bei mir lagen schon zwei Schlüssel in <code>~/.ssh/</code>, einer davon für meinen Git-Server. Mit <code>ssh-keygen -lf</code> sieht man, welcher Schlüssel welchen Kommentar trägt, und damit meistens auch, wofür er gedacht war:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ls ~/.ssh/*.pub
</span></span><span class="line"><span class="cl">ssh-keygen -lf ~/.ssh/id_ed25519.pub
</span></span></code></pre></div><p>Den öffentlichen Teil (die <code>.pub</code>-Datei – der private Schlüssel verlässt den eigenen Rechner nie) kopiere ich auf den Server:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ssh-copy-id -i ~/.ssh/id_ed25519.pub root@203.0.113.10
</span></span></code></pre></div><p>Damit ich mir die IP nicht merken muss, bekommt der Server einen Eintrag in der <code>~/.ssh/config</code> auf meinem Rechner:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Host kabelbruch
</span></span><span class="line"><span class="cl">    HostName 203.0.113.10
</span></span><span class="line"><span class="cl">    User root
</span></span><span class="line"><span class="cl">    IdentityFile ~/.ssh/id_ed25519
</span></span><span class="line"><span class="cl">    IdentitiesOnly yes
</span></span></code></pre></div><p><code>IdentitiesOnly yes</code> sorgt dafür, dass genau dieser Schlüssel benutzt wird und SSH nicht der Reihe nach alle durchprobiert. Ab jetzt reicht <code>ssh kabelbruch</code>.</p>
<h2 id="schritt-2-ein-eigener-benutzer-statt-root">Schritt 2: Ein eigener Benutzer statt root</h2>
<p>Als <code>root</code> zu arbeiten ist bequem, aber genau deshalb keine gute Idee. Ich lege mir einen eigenen Benutzer an, der per <code>sudo</code> Administratorrechte bekommt:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">apt update <span class="o">&amp;&amp;</span> apt install -y sudo
</span></span><span class="line"><span class="cl">adduser thomas
</span></span><span class="line"><span class="cl">usermod -aG sudo thomas
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">mkdir -p /home/thomas/.ssh
</span></span><span class="line"><span class="cl">cp /root/.ssh/authorized_keys /home/thomas/.ssh/
</span></span><span class="line"><span class="cl">chown -R thomas:thomas /home/thomas/.ssh
</span></span><span class="line"><span class="cl">chmod <span class="m">700</span> /home/thomas/.ssh
</span></span><span class="line"><span class="cl">chmod <span class="m">600</span> /home/thomas/.ssh/authorized_keys
</span></span></code></pre></div><p>Das Passwort, das <code>adduser</code> abfragt, brauche ich später nur noch für <code>sudo</code>, nicht mehr zum Anmelden.</p>
<p><strong>Wichtig:</strong> Bevor man irgendetwas am SSH-Zugang ändert, immer eine zweite Sitzung offen lassen. Das ist der Rettungsanker, falls man sich aussperrt.</p>
<h2 id="schritt-3-passwörter-und-root-login-abschalten">Schritt 3: Passwörter und root-Login abschalten</h2>
<p>Jetzt kommt der eigentliche Härtungsschritt. Ich lege die Einstellungen in einer eigenen Datei unter <code>sshd_config.d</code> ab, statt die Hauptkonfiguration zu bearbeiten:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo tee /etc/ssh/sshd_config.d/00-hardening.conf &gt; /dev/null <span class="s">&lt;&lt;&#39;EOF&#39;
</span></span></span><span class="line"><span class="cl"><span class="s">PasswordAuthentication no
</span></span></span><span class="line"><span class="cl"><span class="s">KbdInteractiveAuthentication no
</span></span></span><span class="line"><span class="cl"><span class="s">PermitRootLogin no
</span></span></span><span class="line"><span class="cl"><span class="s">EOF</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">sudo sshd -t <span class="o">&amp;&amp;</span> sudo systemctl reload ssh
</span></span><span class="line"><span class="cl">sudo sshd -T <span class="p">|</span> grep -E <span class="s1">&#39;passwordauth|permitroot&#39;</span>
</span></span></code></pre></div><p>Die Ausgabe sollte <code>passwordauthentication no</code> und <code>permitrootlogin no</code> zeigen.</p>
<p>Warum <code>00-</code> im Dateinamen? Bei <code>sshd</code> gewinnt der <strong>erste</strong> gefundene Wert. Manche Server-Images bringen eine eigene Datei mit, zum Beispiel <code>50-cloud-init.conf</code> mit <code>PasswordAuthentication yes</code>. Mit <code>00-</code> am Anfang wird meine Datei zuerst gelesen und setzt sich durch.</p>
<h3 id="gescheitert-teil-1-permission-denied">Gescheitert, Teil 1: <code>Permission denied</code></h3>
<p>Mein erster Versuch sah so aus:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cat &gt; /etc/ssh/sshd_config.d/00-hardening.conf <span class="s">&lt;&lt;&#39;EOF&#39;
</span></span></span><span class="line"><span class="cl"><span class="s">...
</span></span></span><span class="line"><span class="cl"><span class="s">EOF</span>
</span></span></code></pre></div><p>Ergebnis: <code>Permission denied</code> – und gleich danach <code>sshd: command not found</code>. Ich war schon als <code>thomas</code> angemeldet, nicht mehr als root. Und hier kommt eine klassische Falle: Ein <code>sudo</code> vor dem Befehl hilft bei der Umleitung <code>&gt;</code> <strong>nicht</strong>, denn die Umleitung macht die Shell, und die läuft weiterhin als normaler Benutzer. Die Lösung ist <code>sudo tee</code>, wie oben im Beispiel. Das <code>sshd: command not found</code> hat übrigens einen ganz harmlosen Grund: <code>sshd</code> liegt in <code>/usr/sbin</code>, und das steht bei normalen Benutzern nicht im Pfad. Mit <code>sudo</code> davor wird es gefunden.</p>
<h3 id="gescheitert-teil-2-plötzlich-komme-ich-nicht-mehr-rein">Gescheitert, Teil 2: Plötzlich komme ich nicht mehr rein</h3>
<p>Nach der Härtung wollte ich mich testweise neu verbinden:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ ssh kabelbruch
</span></span><span class="line"><span class="cl">root@203.0.113.10: Permission denied (publickey).
</span></span></code></pre></div><p>Kurzer Schreckmoment. Dabei hat schlicht alles funktioniert: In meiner <code>~/.ssh/config</code> stand noch <code>User root</code> – und root darf sich ja jetzt nicht mehr anmelden. Die Sperre hat also genau das getan, was sie sollte.</p>
<p>Ich habe dann <code>grep</code> auf die <code>~/.ssh/config</code> losgelassen, um nachzusehen – allerdings im falschen Terminal, nämlich auf dem Server statt auf meinem Laptop. Dort gibt es diese Datei natürlich nicht. Eine Faustregel, die ich mir seitdem merke: <strong>Steht im Prompt der Name des Servers, bin ich auf dem Server.</strong> Alles rund um den eigenen SSH-Schlüssel und die SSH-Config passiert lokal.</p>
<p>Nachdem in der Config <code>User thomas</code> stand, ging es sofort. Der Gegentest gehört trotzdem dazu:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ssh kabelbruch            <span class="c1"># klappt als thomas</span>
</span></span><span class="line"><span class="cl">ssh root@203.0.113.10     <span class="c1"># wird abgelehnt</span>
</span></span></code></pre></div><h2 id="schritt-4-die-firewall">Schritt 4: Die Firewall</h2>
<p>Als Nächstes kommt eine einfache Firewall. Ich nutze <code>ufw</code> (Uncomplicated Firewall), die macht ihrem Namen alle Ehre:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo apt install -y ufw
</span></span><span class="line"><span class="cl">sudo ufw default deny incoming
</span></span><span class="line"><span class="cl">sudo ufw default allow outgoing
</span></span><span class="line"><span class="cl">sudo ufw allow OpenSSH
</span></span><span class="line"><span class="cl">sudo ufw allow 80/tcp
</span></span><span class="line"><span class="cl">sudo ufw allow 443/tcp
</span></span><span class="line"><span class="cl">sudo ufw <span class="nb">enable</span>
</span></span><span class="line"><span class="cl">sudo ufw status verbose
</span></span></code></pre></div><p>Die Reihenfolge ist entscheidend: <strong>SSH erlauben, bevor man <code>ufw enable</code> ausführt.</strong> Sonst sperrt man sich mit dem Aktivieren selbst aus. <code>ufw</code> warnt beim Einschalten trotzdem, dass bestehende SSH-Verbindungen unterbrochen werden könnten. Diese Warnung kommt immer, egal welche Regeln existieren. Wenn die SSH-Regel drin ist, kann man sie mit <code>y</code> bestätigen.</p>
<p>Am Ende sind nur noch SSH (22), HTTP (80) und HTTPS (443) offen, für IPv4 und IPv6. Port 80 und 443 brauche ich in Teil 2 für den Webserver und die TLS-Zertifikate.</p>
<h2 id="schritt-5-automatische-sicherheitsupdates">Schritt 5: Automatische Sicherheitsupdates</h2>
<p>Ein Server, der sich nicht selbst aktualisiert, wird irgendwann vergessen. Debian bringt dafür <code>unattended-upgrades</code> mit:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo apt install -y unattended-upgrades apt-listchanges
</span></span><span class="line"><span class="cl">sudo dpkg-reconfigure -plow unattended-upgrades
</span></span></code></pre></div><p>Im Dialog „Ja“ wählen, fertig. Sicherheitsupdates werden ab jetzt täglich automatisch eingespielt.</p>
<h2 id="stolperfallen--learnings">Stolperfallen &amp; Learnings</h2>
<ul>
<li><strong>Immer eine zweite SSH-Sitzung offen lassen</strong>, solange man am Zugang schraubt.</li>
<li><strong><code>sudo</code> hilft nicht bei <code>&gt;</code></strong>, dafür gibt es <code>sudo tee</code>.</li>
<li><strong><code>00-</code> im Dateinamen unter <code>sshd_config.d</code></strong>, weil der erste Wert gewinnt.</li>
<li><strong>Erst SSH in der Firewall erlauben, dann <code>ufw enable</code>.</strong></li>
<li><strong>Auf den Prompt schauen:</strong> Laptop oder Server? Die Hälfte meiner Fehler an diesem Abend waren Befehle im falschen Terminal.</li>
<li><strong><code>Permission denied (publickey)</code> nach der Härtung</strong> ist oft gar kein Fehler, sondern die neue Sperre bei der Arbeit. Erst die eigene SSH-Config prüfen.</li>
</ul>
<h2 id="fazit--ausblick">Fazit &amp; Ausblick</h2>
<p>Nach etwa einer Stunde hatte ich einen Server, auf dem man sich nur noch mit meinem Schlüssel und nicht als root anmelden kann, bei dem nur drei Ports offen sind und der sich selbst mit Sicherheitsupdates versorgt. Das ist kein Hochsicherheitstrakt, aber eine solide Grundlage – und genau das, was ein öffentlicher Server mindestens braucht.</p>
<p><strong>Was als Nächstes kommt:</strong> In Teil 2 installiere ich Caddy als Webserver, stelle das DNS ein und setze Security-Header. Dabei hat mich eine Content-Security-Policy eine Weile beschäftigt: Plötzlich waren auf meiner Seite Mond <em>und</em> Sonne gleichzeitig zu sehen.</p>
<p>Und die Idee im Hinterkopf, für die der Server ein bisschen Luft hat: ein eigener kleiner Fediverse-Server. Aber dazu mehr, wenn es so weit ist.</p>
]]></content:encoded></item></channel></rss>