<?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>Audit – Serverküche</title><link>https://serverkueche.de/tags/audit/</link><description>Audit – Neueste Beiträge von Serverküche</description><generator>Hugo</generator><language>de-DE</language><managingEditor>feedback@serverkueche.de (Serverküche)</managingEditor><webMaster>feedback@serverkueche.de (Serverküche)</webMaster><copyright>2026 Serverküche</copyright><lastBuildDate>Sat, 17 Oct 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://serverkueche.de/tags/audit/index.xml" rel="self" type="application/rss+xml"/><item><title>Wie sicher ist mein Server? Selbst-Audit mit Lynis</title><link>https://serverkueche.de/tutorials/lynis-sicherheits-audit/</link><pubDate>Sat, 17 Oct 2026 00:00:00 +0000</pubDate><author>feedback@serverkueche.de (Serverküche)</author><guid>https://serverkueche.de/tutorials/lynis-sicherheits-audit/</guid><description>Lynis 3.1.7 auf Debian 13: Server auditieren, den Hardening-Index verstehen, Findings abarbeiten – und wissen, welche Vorschläge den Docker-Host zerlegen.</description><content:encoded><![CDATA[<p>Du hast SSH abgesichert, eine Firewall gesetzt, fail2ban laufen – und weißt trotzdem nicht, was du
übersehen hast. Lynis beantwortet genau diese Frage: Es geht deinen Server von innen durch und sagt
dir, was noch offen ist. Der schwierigere Teil kommt danach, nämlich zu entscheiden, welche der
Vorschläge du wirklich umsetzt.</p>
<h2 id="was-bauen-wir">Was bauen wir?</h2>
<p>Wir installieren <strong>Lynis 3.1.7</strong> auf einem Debian 13 und lassen ein vollständiges Audit laufen. Aus
dem Ergebnis – auf einem frischen netcup-Server mit Docker zunächst <strong>Hardening-Index 64, eine
Warnung und 44 Vorschläge</strong> – arbeiten wir die Punkte ab, die tatsächlich etwas bringen, und lassen
die anderen bewusst stehen. Am Ende steht ein reproduzierbarer Audit-Lauf per systemd-Timer, eine
dokumentierte Ausnahmeliste und ein Index von 77.</p>
<p>Genauso wichtig ist, was wir <strong>nicht</strong> tun: Einer der Vorschläge nimmt jedem Container auf der Stelle
das Netz, ein zweiter legt beim nächsten neuen Stack eine Zeitbombe, ein dritter beanstandet eine
Einstellung, die bereits strenger ist als gefordert. Lynis kennt deinen Server nicht – du schon.</p>
<h2 id="voraussetzungen">Voraussetzungen</h2>
<ul>
<li>Ein <a href="/tutorials/ssh-absichern/">abgesicherter Server</a> mit Debian 13 und root- bzw. sudo-Zugang</li>
<li>Grundverständnis für <a href="/tutorials/systemd-grundlagen/">systemd-Units und Timer</a> – für die
Automatisierung am Ende</li>
<li>Etwa eine Stunde Zeit; das erste Audit selbst dauert nur 1–2 Minuten</li>
<li>Optional, aber praktisch: <a href="/tutorials/automatische-updates-unattended-upgrades/">unattended-upgrades</a>
ist schon eingerichtet – dann fällt eines der Findings von selbst weg</li>
</ul>
<div class="not-prose my-6 overflow-hidden rounded-xl border border-paprika-200 bg-paprika-50 dark:border-paprika-800 dark:bg-paprika-900/20"
     data-track-content data-content-name="Affiliate-Box · /tutorials/lynis-sicherheits-audit/" data-content-piece="VPS 1000 G12.5">
  <div class="flex items-center justify-between border-b border-paprika-200 bg-paprika-100 px-4 py-1.5 text-xs font-semibold uppercase tracking-wide text-paprika-700 dark:border-paprika-800 dark:bg-paprika-900/40 dark:text-paprika-300">
    <span>🍳 Empfehlung</span>
    <span title="Mit * markierte Links sind Affiliate-Links.">Anzeige</span>
  </div>
  <div class="flex flex-col gap-4 p-4 sm:flex-row sm:items-center sm:justify-between">
    <div>
      <p class="text-lg font-bold text-slate-900 dark:text-white">VPS 1000 G12.5</p>
      <p class="mt-1 text-sm text-slate-600 dark:text-slate-300">4 vCore · 8 GB RAM · 128 GB SSD</p>
      <p class="mt-1 text-sm font-semibold text-paprika-700 dark:text-paprika-400">ab 14,50 €/Monat</p>
      <p class="mt-2 text-sm text-slate-600 dark:text-slate-400">Lynis läuft auf jedem noch so kleinen Server – ein VPS 1000 reicht für Audit und Docker-Stack locker.</p>
    </div>
    <a href="https://www.netcup.com/de/server/vps/vps-1000-g12.5-24m-eu?ref=44083#vps-1000-g12.5-12m-eu" rel="sponsored noopener" target="_blank"
   data-track-event="Affiliate|netcup: Affiliate-Box|VPS 1000 G12.5 · {page}"
   class="inline-flex shrink-0 items-center justify-center rounded-lg bg-paprika-600 px-5 py-2.5 font-semibold text-white transition-colors hover:bg-paprika-700">
  Zu netcup →
</a>

  </div><div class="px-4 pb-4"><div class="not-prose my-3 rounded-lg border border-herb-500/40 bg-herb-50 px-3 py-2 text-sm text-slate-700 dark:bg-herb-900/20 dark:text-slate-200"
     data-voucher-api="vps-1000" data-voucher-msg-empty="Gerade ist für diesen Tarif kein Code frei – nimm den 5,95-€-Code." data-voucher-msg-limit="Für heute sind keine weiteren Codes drin – nimm den 5,95-€-Code oder versuch es morgen." data-voucher-msg-error="Der Code ließ sich gerade nicht abrufen – nimm den 5,95-€-Code."
     data-voucher-hint="Warenkorb vorher leeren – der Code legt den Server selbst hinein.">
  <p class="flex flex-wrap items-center gap-x-2 gap-y-1"><span>💶 <span class="font-semibold">1 Monat gratis</span>:</span>
    <button type="button" data-voucher-reveal data-copy-title="Zum Kopieren klicken"
            class="cursor-pointer rounded bg-white px-2 py-0.5 font-mono text-sm font-semibold text-herb-800 hover:ring-2 hover:ring-herb-500/40 dark:bg-slate-800 dark:text-herb-400">Code anzeigen</button>
    <button type="button" data-voucher-next hidden
            class="rounded border border-herb-500/40 px-2 py-0.5 text-xs text-herb-800 hover:bg-white dark:text-herb-400 dark:hover:bg-slate-800">anderer Code</button>
  </p><p class="mt-1 text-xs text-slate-500 dark:text-slate-400" data-voucher-hint-target>
    Einmal einlösbar, gilt für VPS 1000 G12.5.
    <a href="https://www.netcup.com/de/checkout/warenkorb?ref=44083" rel="sponsored noopener" target="_blank"
       data-track-event="Affiliate|netcup: Gutschein einlösen|VPS 1000 G12.5 · {page}"
       class="font-medium text-herb-800 underline underline-offset-2 hover:text-herb-900 dark:text-herb-400">Im Warenkorb einlösen →</a>
  </p><p class="mt-2 flex flex-wrap items-center gap-x-2 gap-y-1 border-t border-herb-500/30 pt-2">
    <span>💶 <span class="font-semibold">5,95 € Gutschein</span> für netcup-Neukunden:</span>
    <button type="button" data-voucher-code data-track-voucher="36nc17844976032"
            title="Zum Kopieren klicken" class="cursor-pointer rounded bg-white px-2 py-0.5 font-mono text-sm font-semibold text-herb-800 hover:ring-2 hover:ring-herb-500/40 dark:bg-slate-800 dark:text-herb-400">36nc17844976032</button>
    <span class="text-xs text-slate-500 dark:text-slate-400">immer gültig · nicht für Domains und VPS Lite</span>
  </p><p class="mt-1 text-xs"><a href="/netcup-gutschein/" class="font-medium text-herb-800 underline underline-offset-2 hover:text-herb-900 dark:text-herb-400">Alle netcup-Gutscheine →</a></p>
</div></div>
</div>

<h2 id="schritt-für-schritt">Schritt für Schritt</h2>
<h3 id="schritt-1-lynis-aus-dem-cisofy-repository-installieren">Schritt 1: Lynis aus dem CISOfy-Repository installieren</h3>
<p>Debian bringt Lynis mit, aber in einer älteren Fassung:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">apt-cache policy lynis</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">lynis:
</span></span><span class="line"><span class="cl">  Installed: (none)
</span></span><span class="line"><span class="cl">  Candidate: 3.1.4-1</span></span></code></pre></div>
</div>
<p>Das ist kein Schönheitsfehler. Lynis besteht fast vollständig aus <strong>Tests</strong>, und die veralten
schneller als das Programm drumherum – neue Kernel-Parameter, neue Dienste, neue Standardwerte. Ein
Audit mit einer alten Testsammlung übersieht genau die Dinge, die neu dazugekommen sind. Deshalb
nehmen wir das Repository von CISOfy, den Lynis-Entwicklern.</p>
<p>Den Signaturschlüssel legen wir als <strong>ASCII-armored Datei</strong> (<code>.asc</code>) ab. Das ist der Trick, der dir
die Installation von <code>gnupg</code> erspart – apt kann Klartext-Schlüssel direkt lesen, wenn die Datei auf
<code>.asc</code> endet:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">install -d -m <span class="m">0755</span> /etc/apt/keyrings
</span></span><span class="line"><span class="cl">curl -fsSL https://packages.cisofy.com/keys/cisofy-software-public.key <span class="se">\
</span></span></span><span class="line"><span class="cl">  -o /etc/apt/keyrings/cisofy.asc</span></span></code></pre></div>
</div>
<p>Prüfe kurz, dass wirklich ein Schlüssel angekommen ist und nicht eine Fehlerseite:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">head -1 /etc/apt/keyrings/cisofy.asc</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">-----BEGIN PGP PUBLIC KEY BLOCK-----</span></span></code></pre></div>
</div>
<p>Jetzt die Paketquelle eintragen. Das <code>signed-by</code> bindet die Quelle fest an genau diesen Schlüssel –
CISOfy darf damit nur Lynis-Pakete signieren, nicht dein halbes System ersetzen:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;deb [signed-by=/etc/apt/keyrings/cisofy.asc] https://packages.cisofy.com/community/lynis/deb/ stable main&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  &gt; /etc/apt/sources.list.d/cisofy-lynis.list
</span></span><span class="line"><span class="cl">apt-get update
</span></span><span class="line"><span class="cl">apt-get install -y lynis</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">lynis --version</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">3.1.7</span></span></code></pre></div>
</div>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-sky-300 bg-sky-50 dark:border-sky-800 dark:bg-sky-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">ℹ️</span>Hinweis
  </p>
  <div class="prose-kitchen text-sm">Wenn <code>apt-cache policy lynis</code> weiter nur 3.1.4 anbietet, hat <code>apt-get update</code> die neue Quelle nicht
eingelesen – meist wegen eines Tippfehlers im Dateinamen. Die Liste muss auf <code>.list</code> enden und in
<code>/etc/apt/sources.list.d/</code> liegen.</div>
</div>
<h3 id="schritt-2-der-erste-audit-lauf">Schritt 2: Der erste Audit-Lauf</h3>
<p>Lynis braucht keinen Dienst und keine Konfiguration. Einmal aufrufen genügt:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">lynis audit system --quick --no-colors <span class="p">|</span> tee /root/lynis-run1.txt</span></span></code></pre></div>
</div>
<p><code>--quick</code> überspringt die Pausen, in denen Lynis sonst auf einen Tastendruck wartet – sonst
identisch zum normalen Lauf. <code>--no-colors</code> macht die Ausgabe kopierbar, <code>tee</code> hebt sie auf, damit du
später vergleichen kannst.</p>
<p>Nach ein bis zwei Minuten steht am Ende die Zusammenfassung:</p>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">  Details:
</span></span><span class="line"><span class="cl">  Hardening index : 64 [############        ]
</span></span><span class="line"><span class="cl">  Tests performed : 251
</span></span><span class="line"><span class="cl">  Plugins enabled : 0</span></span></code></pre></div>
</div>
<p>Darüber stehen die eigentlichen Ergebnisse, getrennt in zwei Kategorien:</p>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">  Warnings (1):
</span></span><span class="line"><span class="cl">  ----------------------------
</span></span><span class="line"><span class="cl">  ! Found one or more vulnerable packages. [PKGS-7392]
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  Suggestions (44):</span></span></code></pre></div>
</div>
<p>Der Unterschied ist wichtig. Eine <strong>Warning</strong> ist für Lynis ein Zustand, der jetzt falsch ist – ein
ungepatchtes Paket, ein Dienst, der nicht tut, was er soll. Eine <strong>Suggestion</strong> ist eine Empfehlung,
die je nach Einsatzzweck sinnvoll sein kann oder eben nicht. Warnungen schaust du dir immer an;
Vorschläge sortierst du.</p>
<p>Der <strong>Hardening-Index</strong> ist dabei die am häufigsten missverstandene Zahl. Er ist kein Prozentwert
für „Sicherheit&quot;, sondern ein simpler Punktequotient: Jeder Test vergibt erreichte und mögliche
Punkte, am Ende wird geteilt. Ein Server ohne Webserver bekommt für nicht durchgeführte
Webserver-Tests weder Punkte noch Maximum – ein Server mit Webserver kann also bei gleicher
Sorgfalt schlechter dastehen. <strong>Vergleiche den Index nur mit dir selbst, nie mit anderen.</strong></p>
<h3 id="schritt-3-den-bericht-lesen-nicht-die-bildschirmausgabe">Schritt 3: Den Bericht lesen, nicht die Bildschirmausgabe</h3>
<p>Die Konsolenausgabe ist die schlechteste der drei Informationsquellen. Zehn Zeilen
„Consider hardening SSH configuration [SSH-7408]&quot; untereinander sagen dir nicht, <em>welche</em> zehn
Optionen gemeint sind. Die stehen im maschinenlesbaren Bericht:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">grep <span class="s2">&#34;^suggestion\[\]=SSH-7408&#34;</span> /var/log/lynis-report.dat</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">suggestion[]=SSH-7408|Consider hardening SSH configuration|AllowTcpForwarding (set YES to NO)|-|
</span></span><span class="line"><span class="cl">suggestion[]=SSH-7408|Consider hardening SSH configuration|ClientAliveCountMax (set 3 to 2)|-|
</span></span><span class="line"><span class="cl">suggestion[]=SSH-7408|Consider hardening SSH configuration|LogLevel (set INFO to VERBOSE)|-|
</span></span><span class="line"><span class="cl">suggestion[]=SSH-7408|Consider hardening SSH configuration|MaxAuthTries (set 6 to 3)|-|
</span></span><span class="line"><span class="cl">suggestion[]=SSH-7408|Consider hardening SSH configuration|MaxSessions (set 10 to 2)|-|
</span></span><span class="line"><span class="cl">suggestion[]=SSH-7408|Consider hardening SSH configuration|PermitRootLogin (set YES to (FORCED-COMMANDS-ONLY|NO|PROHIBIT-PASSWORD|WITHOUT-PASSWORD))|-|
</span></span><span class="line"><span class="cl">suggestion[]=SSH-7408|Consider hardening SSH configuration|Port (set 22 to )|-|
</span></span><span class="line"><span class="cl">suggestion[]=SSH-7408|Consider hardening SSH configuration|TCPKeepAlive (set YES to NO)|-|
</span></span><span class="line"><span class="cl">suggestion[]=SSH-7408|Consider hardening SSH configuration|X11Forwarding (set YES to NO)|-|
</span></span><span class="line"><span class="cl">suggestion[]=SSH-7408|Consider hardening SSH configuration|AllowAgentForwarding (set YES to NO)|-|</span></span></code></pre></div>
</div>
<p>Das dritte Feld ist die konkrete Handlungsanweisung. Merke dir die drei Dateien:</p>
<ul>
<li><code>/var/log/lynis-report.dat</code> – Ergebnis als Schlüssel-Wert-Liste, ideal zum Greppen</li>
<li><code>/var/log/lynis.log</code> – das vollständige Protokoll inklusive der Begründung jedes Tests</li>
<li>die Bildschirmausgabe – nur zum Überfliegen</li>
</ul>
<p>Was Lynis bei einem Test genau erwartet, steht im Log. Beispiel Kernel-Parameter:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">grep <span class="s2">&#34;different value than expected&#34;</span> /var/log/lynis.log <span class="p">|</span> sed <span class="s2">&#34;s/.*sysctl key //&#34;</span></span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">dev.tty.ldisc_autoload has a different value than expected in scan profile. Expected=0, Real=1
</span></span><span class="line"><span class="cl">fs.protected_fifos has a different value than expected in scan profile. Expected=2, Real=1
</span></span><span class="line"><span class="cl">kernel.kptr_restrict has a different value than expected in scan profile. Expected=2, Real=0
</span></span><span class="line"><span class="cl">kernel.modules_disabled has a different value than expected in scan profile. Expected=1, Real=0
</span></span><span class="line"><span class="cl">kernel.sysrq has a different value than expected in scan profile. Expected=0, Real=438
</span></span><span class="line"><span class="cl">kernel.unprivileged_bpf_disabled has a different value than expected in scan profile. Expected=1, Real=2
</span></span><span class="line"><span class="cl">kernel.yama.ptrace_scope has a different value than expected in scan profile. Expected=1 2 3, Real=0
</span></span><span class="line"><span class="cl">net.core.bpf_jit_harden has a different value than expected in scan profile. Expected=2, Real=0
</span></span><span class="line"><span class="cl">net.ipv4.conf.all.forwarding has a different value than expected in scan profile. Expected=0, Real=1
</span></span><span class="line"><span class="cl">net.ipv4.conf.all.log_martians has a different value than expected in scan profile. Expected=1, Real=0
</span></span><span class="line"><span class="cl">net.ipv4.conf.all.rp_filter has a different value than expected in scan profile. Expected=1, Real=0
</span></span><span class="line"><span class="cl">net.ipv4.conf.all.send_redirects has a different value than expected in scan profile. Expected=0, Real=1
</span></span><span class="line"><span class="cl">net.ipv4.conf.default.accept_redirects has a different value than expected in scan profile. Expected=0, Real=1
</span></span><span class="line"><span class="cl">net.ipv4.conf.default.log_martians has a different value than expected in scan profile. Expected=1, Real=0
</span></span><span class="line"><span class="cl">net.ipv6.conf.all.accept_redirects has a different value than expected in scan profile. Expected=0, Real=1
</span></span><span class="line"><span class="cl">net.ipv6.conf.default.accept_redirects has a different value than expected in scan profile. Expected=0, Real=1</span></span></code></pre></div>
</div>
<p>Sechzehn Abweichungen – und drei davon darfst du auf keinen Fall übernehmen. Dazu gleich mehr.</p>
<h3 id="schritt-4-die-findings-abarbeiten-die-etwas-bringen">Schritt 4: Die Findings abarbeiten, die etwas bringen</h3>
<h4 id="pakete-aktualisieren-pkgs-7392">Pakete aktualisieren (PKGS-7392)</h4>
<p>Die erste Warnung ist die banalste und die wichtigste. Lynis vergleicht die installierten Pakete mit
den verfügbaren Sicherheitsupdates:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">apt list --upgradable
</span></span><span class="line"><span class="cl">apt-get -y upgrade</span></span></code></pre></div>
</div>
<p>Wenn das bei dir regelmäßig auftaucht, ist nicht Lynis das Problem, sondern der fehlende Automatismus – dafür
gibt es <a href="/tutorials/automatische-updates-unattended-upgrades/">unattended-upgrades</a>.</p>
<h4 id="ssh-nachziehen-ssh-7408">SSH nachziehen (SSH-7408)</h4>
<p>Statt <code>/etc/ssh/sshd_config</code> anzufassen, legen wir eine Drop-in-Datei an. Debian 13 lädt alles aus
<code>/etc/ssh/sshd_config.d/</code>, und bei einem Paketupdate bleibt deine Änderung so unangetastet:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <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/99-hardening.conf <span class="s">&lt;&lt;&#39;EOF&#39;
</span></span></span><span class="line"><span class="cl"><span class="s">LogLevel VERBOSE
</span></span></span><span class="line"><span class="cl"><span class="s">MaxAuthTries 3
</span></span></span><span class="line"><span class="cl"><span class="s">MaxSessions 2
</span></span></span><span class="line"><span class="cl"><span class="s">TCPKeepAlive no
</span></span></span><span class="line"><span class="cl"><span class="s">X11Forwarding no
</span></span></span><span class="line"><span class="cl"><span class="s">AllowAgentForwarding no
</span></span></span><span class="line"><span class="cl"><span class="s">EOF</span></span></span></code></pre></div>
</div>
<p><code>LogLevel VERBOSE</code> protokolliert zusätzlich den Fingerprint des verwendeten Schlüssels – genau die
Information, die du bei einem Vorfall brauchst. <code>MaxAuthTries 3</code> begrenzt Fehlversuche pro
Verbindung, <code>AllowAgentForwarding no</code> verhindert, dass jemand mit Root auf dem Server deinen
lokalen SSH-Agent mitbenutzt.</p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-amber-400 bg-amber-50 dark:border-amber-700 dark:bg-amber-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">⚠️</span>Achtung
  </p>
  <div class="prose-kitchen text-sm"><strong>Erst testen, dann neu laden.</strong> <code>sshd -t</code> prüft die Konfiguration, ohne den Dienst anzufassen. Ein
Tippfehler plus blindes <code>restart</code> sperrt dich aus – und ein zweites Terminal aufzumachen hilft nur,
solange es schon offen ist.</div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sshd -t <span class="o">&amp;&amp;</span> systemctl reload ssh
</span></span><span class="line"><span class="cl">sshd -T <span class="p">|</span> grep -E <span class="s2">&#34;^(maxauthtries|maxsessions|x11forwarding|loglevel)&#34;</span></span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">maxauthtries 3
</span></span><span class="line"><span class="cl">maxsessions 2
</span></span><span class="line"><span class="cl">x11forwarding no
</span></span><span class="line"><span class="cl">loglevel VERBOSE</span></span></code></pre></div>
</div>
<p>Die beiden schärfsten Punkte aus der Liste – <code>PermitRootLogin</code> und <code>PasswordAuthentication</code> –
stehen hier bewusst nicht drin. Die setzt du erst, wenn ein funktionierender SSH-Schlüssel liegt,
und genau das macht das Tutorial <a href="/tutorials/ssh-absichern/">SSH absichern</a> Schritt für Schritt.
Wenn du es durchgearbeitet hast, ist SSH-7408 bei dir von vornherein kürzer.</p>
<h4 id="kernel-parameter--hier-lauert-die-docker-falle">Kernel-Parameter – hier lauert die Docker-Falle</h4>
<p>Von den sechzehn abweichenden sysctl-Werten übernehmen wir dreizehn:</p>
<div class="sk-code">
  <span class="sk-code-head">INI</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="c1"># /etc/sysctl.d/99-lynis.conf</span>
</span></span><span class="line"><span class="cl"><span class="na">dev.tty.ldisc_autoload</span> <span class="o">=</span> <span class="s">0</span>
</span></span><span class="line"><span class="cl"><span class="na">fs.protected_fifos</span> <span class="o">=</span> <span class="s">2</span>
</span></span><span class="line"><span class="cl"><span class="na">kernel.kptr_restrict</span> <span class="o">=</span> <span class="s">2</span>
</span></span><span class="line"><span class="cl"><span class="na">kernel.sysrq</span> <span class="o">=</span> <span class="s">0</span>
</span></span><span class="line"><span class="cl"><span class="na">kernel.yama.ptrace_scope</span> <span class="o">=</span> <span class="s">1</span>
</span></span><span class="line"><span class="cl"><span class="na">net.core.bpf_jit_harden</span> <span class="o">=</span> <span class="s">2</span>
</span></span><span class="line"><span class="cl"><span class="na">net.ipv4.conf.all.log_martians</span> <span class="o">=</span> <span class="s">1</span>
</span></span><span class="line"><span class="cl"><span class="na">net.ipv4.conf.default.log_martians</span> <span class="o">=</span> <span class="s">1</span>
</span></span><span class="line"><span class="cl"><span class="na">net.ipv4.conf.all.rp_filter</span> <span class="o">=</span> <span class="s">1</span>
</span></span><span class="line"><span class="cl"><span class="na">net.ipv4.conf.all.send_redirects</span> <span class="o">=</span> <span class="s">0</span>
</span></span><span class="line"><span class="cl"><span class="na">net.ipv4.conf.default.accept_redirects</span> <span class="o">=</span> <span class="s">0</span>
</span></span><span class="line"><span class="cl"><span class="na">net.ipv6.conf.all.accept_redirects</span> <span class="o">=</span> <span class="s">0</span>
</span></span><span class="line"><span class="cl"><span class="na">net.ipv6.conf.default.accept_redirects</span> <span class="o">=</span> <span class="s">0</span></span></span></code></pre></div>
</div>
<p>Die letzten vier betreffen ICMP-Redirects – Hinweise eines Routers, ein Paket doch besser über einen
anderen Weg zu schicken. Ein Server mit fester Standardroute braucht sie weder zu senden noch
anzunehmen, und Docker-Container kommen ohne sie über IPv4 wie IPv6 ins Netz.</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sysctl -p /etc/sysctl.d/99-lynis.conf</span></span></code></pre></div>
</div>
<p>Drei Werte fehlen in der Datei, und das ist Absicht:</p>
<ul>
<li><strong><code>net.ipv4.conf.all.forwarding = 0</code></strong> – Lynis will IP-Forwarding abschalten. Docker braucht es,
um Pakete zwischen Containernetz und Außenwelt zu routen. Setzt du das auf 0, hat <strong>kein einziger
Container mehr Netz</strong>. Wenn du wissen willst, warum, hilft
<a href="/tutorials/docker-netzwerke-verstehen/">Docker-Netzwerke verstehen</a>.</li>
<li><strong><code>kernel.modules_disabled = 1</code></strong> – verbietet dem Kernel, weitere Module zu laden. Docker lädt
je nach Stack <code>overlay</code>, <code>br_netfilter</code> oder <code>nf_tables</code> bei Bedarf nach. Auf einem Container-Host
ist das eine Zeitbombe, die beim nächsten neuen Stack hochgeht.</li>
<li><strong><code>kernel.unprivileged_bpf_disabled = 1</code></strong> – hier ist Lynis schlicht zu eng. Debian 13 steht auf
<code>2</code>, und das ist die <strong>strengere</strong> Einstellung: BPF für unprivilegierte Nutzer ist aus und lässt
sich zur Laufzeit nicht wieder einschalten. Lynis prüft auf exakte Gleichheit und meckert
deswegen über eine Verbesserung.</li>
</ul>
<p>Prüfe nach dem Anwenden, dass die Container noch reden können – <code>rp_filter</code> ist der Wert, der am
ehesten Ärger macht:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker run --rm alpine:3.22 sh -c <span class="s2">&#34;wget -qO- -T5 https://example.com &gt;/dev/null &amp;&amp; echo NET-OK&#34;</span></span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">NET-OK</span></span></code></pre></div>
</div>
<h4 id="passwort-richtlinien-und-umask-auth-9286-auth-9328">Passwort-Richtlinien und umask (AUTH-9286, AUTH-9328)</h4>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sed -i <span class="s1">&#39;s/^PASS_MAX_DAYS.*/PASS_MAX_DAYS\t365/; s/^PASS_MIN_DAYS.*/PASS_MIN_DAYS\t1/&#39;</span> /etc/login.defs
</span></span><span class="line"><span class="cl">grep -q <span class="s2">&#34;^UMASK&#34;</span> /etc/login.defs <span class="o">||</span> <span class="nb">printf</span> <span class="s1">&#39;UMASK\t\t027\n&#39;</span> &gt;&gt; /etc/login.defs</span></span></code></pre></div>
</div>
<p><code>UMASK 027</code> sorgt dafür, dass neu angelegte Dateien nicht mehr für alle lesbar sind. Das ist
sinnvoll – hat aber eine Nebenwirkung, die dich beim nächsten Konfigurationsschritt einholt, siehe
„Wenn es nicht funktioniert&quot;.</p>
<p>Nicht übernommen haben wir <strong>AUTH-9229</strong> und <strong>AUTH-9230</strong>, die <code>SHA_CRYPT_MIN_ROUNDS</code> in
<code>/etc/login.defs</code> verlangen. Debian 13 hasht Passwörter mit <strong>yescrypt</strong>:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">grep ^ENCRYPT_METHOD /etc/login.defs</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">ENCRYPT_METHOD YESCRYPT</span></span></code></pre></div>
</div>
<p>Die <code>SHA_CRYPT_*</code>-Werte gelten ausschließlich für SHA-512-Hashes. Sie hier einzutragen würde die
Lynis-Meldung zum Verschwinden bringen, ohne irgendetwas zu ändern – die klassische
Scheinverbesserung. Der Test schaut wirklich nur nach diesen beiden Zeilen und ignoriert
<code>ENCRYPT_METHOD</code> komplett.</p>
<h4 id="ungenutzte-kernel-module-sperren-usb-1000-strg-1846-netw-3200">Ungenutzte Kernel-Module sperren (USB-1000, STRG-1846, NETW-3200)</h4>
<p>Ein VPS braucht weder USB-Speicher noch FireWire noch die Netzwerkprotokolle DCCP, SCTP, RDS oder
TIPC. Jedes geladene Modul ist Angriffsfläche:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cat &gt; /etc/modprobe.d/99-lynis-blacklist.conf <span class="s">&lt;&lt;&#39;EOF&#39;
</span></span></span><span class="line"><span class="cl"><span class="s">install usb-storage /bin/true
</span></span></span><span class="line"><span class="cl"><span class="s">install firewire-core /bin/true
</span></span></span><span class="line"><span class="cl"><span class="s">install firewire_ohci /bin/true
</span></span></span><span class="line"><span class="cl"><span class="s">install ohci1394 /bin/true
</span></span></span><span class="line"><span class="cl"><span class="s">install dccp /bin/true
</span></span></span><span class="line"><span class="cl"><span class="s">install sctp /bin/true
</span></span></span><span class="line"><span class="cl"><span class="s">install rds /bin/true
</span></span></span><span class="line"><span class="cl"><span class="s">install tipc /bin/true
</span></span></span><span class="line"><span class="cl"><span class="s">EOF</span></span></span></code></pre></div>
</div>
<p><code>install &lt;modul&gt; /bin/true</code> ist stärker als <code>blacklist</code>: <code>blacklist</code> verhindert nur das automatische
Laden, ein <code>modprobe</code> von Hand geht trotzdem. Prüfe das Ergebnis im Trockenlauf:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">modprobe -n -v sctp</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">install /bin/true</span></span></code></pre></div>
</div>
<h4 id="ein-banner-das-lynis-zählen-kann-bann-7126-bann-7130">Ein Banner, das Lynis zählen kann (BANN-7126, BANN-7130)</h4>
<p>Lynis sucht in <code>/etc/issue</code> nach juristischen Reizwörtern und will <strong>mindestens fünf</strong> davon finden –
aus einer fest eingebauten englischen Liste (<code>authori</code>, <code>monitor</code>, <code>log</code>, <code>legal</code>, <code>prohibited</code>,
<code>access</code>, <code>system</code> …). Ein rein deutscher Text besteht den Test nicht, egal wie deutlich er ist.
Deshalb zweisprachig:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cat &gt; /etc/issue <span class="s">&lt;&lt;&#39;EOF&#39;
</span></span></span><span class="line"><span class="cl"><span class="s">Zugriff nur für autorisierte Nutzer. Alle Zugriffe werden protokolliert.
</span></span></span><span class="line"><span class="cl"><span class="s">Authorized access only. All connections to this system are monitored and
</span></span></span><span class="line"><span class="cl"><span class="s">logged. Unauthorized use is prohibited and may be prosecuted by law.
</span></span></span><span class="line"><span class="cl"><span class="s">EOF</span>
</span></span><span class="line"><span class="cl">cp /etc/issue /etc/issue.net</span></span></code></pre></div>
</div>
<p><code>/etc/issue</code> sieht man an der lokalen Konsole, <code>/etc/issue.net</code> zeigt SSH vor dem Login an. Letzteres
passiert allerdings nur, wenn du es sshd auch sagst – ab Werk steht das Banner auf <code>none</code>:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Banner /etc/issue.net&#34;</span> &gt;&gt; /etc/ssh/sshd_config.d/99-hardening.conf
</span></span><span class="line"><span class="cl">sshd -t <span class="o">&amp;&amp;</span> systemctl reload ssh
</span></span><span class="line"><span class="cl">sshd -T <span class="p">|</span> grep -i <span class="s2">&#34;^banner&#34;</span></span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">banner /etc/issue.net</span></span></code></pre></div>
</div>
<h4 id="paketintegrität-und-audit-protokoll-pkgs-7370-acct-9622-acct-9628">Paketintegrität und Audit-Protokoll (PKGS-7370, ACCT-9622, ACCT-9628)</h4>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">apt-get install -y debsums apt-show-versions acct auditd</span></span></code></pre></div>
</div>
<p><code>debsums</code> vergleicht die Prüfsummen installierter Dateien mit denen aus dem Paket – damit fällt auf,
wenn jemand <code>/usr/bin/ssh</code> ausgetauscht hat. Lynis will zusätzlich, dass die Prüfung regelmäßig
läuft:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sed -i <span class="s1">&#39;s/^CRON_CHECK=.*/CRON_CHECK=weekly/&#39;</span> /etc/default/debsums</span></span></code></pre></div>
</div>
<p><code>auditd</code> protokolliert Kernel-Ereignisse. Frisch installiert hat es allerdings <strong>kein einziges
Regelwerk</strong> – und Lynis quittiert das prompt mit einem neuen Finding, das vorher nicht da war:</p>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">* Audit daemon is enabled with an empty ruleset. Disable the daemon or define rules [ACCT-9630]</span></span></code></pre></div>
</div>
<p>Das ist kein Fehler, sondern korrekt: ein Audit-Daemon ohne Regeln kostet Ressourcen und liefert
nichts. Ein kleines, sinnvolles Regelwerk für einen Docker-Host:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cat &gt; /etc/audit/rules.d/99-serverkueche.rules <span class="s">&lt;&lt;&#39;EOF&#39;
</span></span></span><span class="line"><span class="cl"><span class="s">-w /etc/passwd -p wa -k identity
</span></span></span><span class="line"><span class="cl"><span class="s">-w /etc/shadow -p wa -k identity
</span></span></span><span class="line"><span class="cl"><span class="s">-w /etc/group -p wa -k identity
</span></span></span><span class="line"><span class="cl"><span class="s">-w /etc/sudoers -p wa -k privileged
</span></span></span><span class="line"><span class="cl"><span class="s">-w /etc/sudoers.d/ -p wa -k privileged
</span></span></span><span class="line"><span class="cl"><span class="s">-w /etc/ssh/sshd_config -p wa -k sshd
</span></span></span><span class="line"><span class="cl"><span class="s">-w /etc/ssh/sshd_config.d/ -p wa -k sshd
</span></span></span><span class="line"><span class="cl"><span class="s">-w /var/log/lastlog -p wa -k logins
</span></span></span><span class="line"><span class="cl"><span class="s">-w /usr/bin/docker -p x -k docker
</span></span></span><span class="line"><span class="cl"><span class="s">EOF</span>
</span></span><span class="line"><span class="cl">augenrules --load
</span></span><span class="line"><span class="cl">auditctl -l</span></span></code></pre></div>
</div>
<p><code>-p wa</code> heißt „bei Schreib- und Attributänderung&quot;, <code>-k</code> setzt ein Stichwort, nach dem du später
suchst: <code>ausearch -k sshd</code>. Die letzte Regel protokolliert jeden <code>docker</code>-Aufruf – auf einem Server,
auf dem Docker-Zugriff faktisch Root-Zugriff bedeutet, ist das die interessanteste Zeile.</p>
<h4 id="die-zeit-warnung-die-früher-oder-später-kommt-time-3185">Die Zeit-Warnung, die früher oder später kommt (TIME-3185)</h4>
<p>Eine zweite Warnung taucht auf fast jedem Server irgendwann auf und sieht nach einem echten Problem
aus, ist aber keins:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">timedatectl timesync-status <span class="p">|</span> grep -E <span class="s2">&#34;Poll|Offset&#34;</span></span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Poll interval: 34min 8s (min: 32s; max 34min 8s)
</span></span><span class="line"><span class="cl">       Offset: +1.462ms</span></span></code></pre></div>
</div>
<p>Die Uhr geht 1,5 Millisekunden falsch – sie ist perfekt synchron. Lynis misst aber das Alter der
Datei <code>/run/systemd/timesync/synchronized</code> und warnt ab <strong>2048 Sekunden</strong>. Genau 2048 Sekunden ist
auch das Standard-Maximum, auf das systemd-timesyncd sein Abfrageintervall hochzieht, wenn die Uhr
stabil läuft. Der Test schlägt also auf jedem gut laufenden System zwangsläufig irgendwann an.</p>
<p>Statt die Warnung zu ignorieren, drücken wir das Intervall knapp darunter. Dann bedeutet ein
Anschlagen dieses Tests wieder etwas:</p>
<div class="sk-code">
  <span class="sk-code-head">INI</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="c1"># /etc/systemd/timesyncd.conf.d/99-lynis.conf</span>
</span></span><span class="line"><span class="cl"><span class="k">[Time]</span>
</span></span><span class="line"><span class="cl"><span class="na">PollIntervalMaxSec</span><span class="o">=</span><span class="s">1024</span></span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">chmod <span class="m">0644</span> /etc/systemd/timesyncd.conf.d/99-lynis.conf
</span></span><span class="line"><span class="cl">systemctl restart systemd-timesyncd
</span></span><span class="line"><span class="cl">timedatectl timesync-status <span class="p">|</span> grep Poll</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Poll interval: 1min 4s (min: 32s; max 17min 4s)</span></span></code></pre></div>
</div>
<p>Das <code>chmod</code> ist kein Schreibfehler – warum es nötig ist, steht weiter unten.</p>
<h3 id="schritt-5-nachmessen">Schritt 5: Nachmessen</h3>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">lynis audit system --quick --no-colors <span class="p">|</span> tee /root/lynis-run2.txt
</span></span><span class="line"><span class="cl">grep -E <span class="s2">&#34;Hardening index|Tests performed&#34;</span> /root/lynis-run2.txt</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">  Hardening index : 77 [###############     ]
</span></span><span class="line"><span class="cl">  Tests performed : 254</span></span></code></pre></div>
</div>
<p>Von 64 auf 77, und der Abschnitt <code>Warnings</code> fehlt jetzt komplett – bei null Warnungen lässt Lynis
ihn einfach weg. Die Vorschläge sind von 44 auf 22 geschrumpft.</p>
<h3 id="schritt-6-bewusste-ausnahmen-in-customprf">Schritt 6: Bewusste Ausnahmen in <code>custom.prf</code></h3>
<p>Was übrig bleibt, sind Vorschläge, die du bei jedem Lauf wieder liest und jedes Mal wieder
verwirfst. Genau dafür gibt es <code>/etc/lynis/custom.prf</code>. Wichtig: <strong>jede Zeile bekommt eine
Begründung</strong>, sonst weißt du in einem halben Jahr nicht mehr, ob das eine Entscheidung war oder
Bequemlichkeit.</p>
<div class="sk-code">
  <span class="sk-code-head">INI</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="c1"># /etc/lynis/custom.prf</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># FILE-6310: eigene Partitionen für /home und /var. Ein VPS hat eine Disk;</span>
</span></span><span class="line"><span class="cl"><span class="c1"># nachträglich partitionieren wäre hier mehr Risiko als Gewinn.</span>
</span></span><span class="line"><span class="cl"><span class="na">skip-test</span><span class="o">=</span><span class="s">FILE-6310</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># AUTH-9229/9230: verlangen SHA_CRYPT_*_ROUNDS. Debian 13 nutzt yescrypt,</span>
</span></span><span class="line"><span class="cl"><span class="c1"># die Werte hätten keinerlei Wirkung.</span>
</span></span><span class="line"><span class="cl"><span class="na">skip-test</span><span class="o">=</span><span class="s">AUTH-9229</span>
</span></span><span class="line"><span class="cl"><span class="na">skip-test</span><span class="o">=</span><span class="s">AUTH-9230</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># BOOT-5122: GRUB-Passwort. Wer über die netcup-VNC-Konsole kommt, hat ohnehin</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Zugriff auf das Serverpanel und damit auf Snapshots und Rescue-System.</span>
</span></span><span class="line"><span class="cl"><span class="na">skip-test</span><span class="o">=</span><span class="s">BOOT-5122</span></span></span></code></pre></div>
</div>
<p>Der nächste Lauf ist danach deutlich ruhiger – aber der Index bewegt sich nicht:</p>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">  Hardening index : 77 [###############     ]
</span></span><span class="line"><span class="cl">  Tests performed : 250
</span></span><span class="line"><span class="cl">  Suggestions (17):</span></span></code></pre></div>
</div>
<p>Das ist kein Fehler, sondern der Beweis, dass die Zahl sich nicht schönrechnen lässt: Ein
übersprungener Test nimmt seine erreichten <strong>und</strong> seine möglichen Punkte aus der Rechnung. Von 22
auf 17 Vorschläge, vier Tests weniger – Index unverändert 77. <code>custom.prf</code> ist ein Werkzeug gegen
Rauschen, kein Werkzeug für den Punktestand.</p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-amber-400 bg-amber-50 dark:border-amber-700 dark:bg-amber-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">⚠️</span>Achtung
  </p>
  <div class="prose-kitchen text-sm"><code>skip-test</code> schaltet den Test <strong>dauerhaft</strong> ab – auch dann noch, wenn sich die Lage ändert. Nimm die
Liste bei jedem Quartals-Audit einmal durch und frag dich, ob die Begründung noch stimmt. Was du nur
diesmal nicht sehen willst, gehört nicht hier hinein.</div>
</div>
<p>Zwei Vorschläge lassen wir bewusst <strong>sichtbar</strong> stehen, statt sie wegzuschalten: <code>HRDN-7230</code>
(Malware-Scanner) und <code>LOGG-2154</code> (zentrales Logging). Beides ist auf einem einzelnen kleinen VPS
schwer zu rechtfertigen – ClamAV allein zieht sich rund ein Gigabyte RAM für die Signaturdatenbank –
aber sobald ein zweiter Server dazukommt, ändert sich die Rechnung. Ein Vorschlag, den du jedes Mal
wieder liest und wieder ablehnst, ist eine Erinnerung; ein weggeschalteter ist vergessen.</p>
<h3 id="schritt-7-das-audit-automatisieren">Schritt 7: Das Audit automatisieren</h3>
<p>Ein einmaliges Audit ist eine Momentaufnahme. Interessant wird es, wenn du den Verlauf siehst –
besonders nach jedem neuen Stack, den du deployst. Lynis bringt dafür einen eigenen Modus mit:</p>
<div class="sk-code">
  <span class="sk-code-head">INI</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="c1"># /etc/systemd/system/lynis-audit.service</span>
</span></span><span class="line"><span class="cl"><span class="k">[Unit]</span>
</span></span><span class="line"><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">Lynis Sicherheits-Audit</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Service]</span>
</span></span><span class="line"><span class="cl"><span class="na">Type</span><span class="o">=</span><span class="s">oneshot</span>
</span></span><span class="line"><span class="cl"><span class="na">ExecStart</span><span class="o">=</span><span class="s">/usr/sbin/lynis audit system --cronjob --quiet</span></span></span></code></pre></div>
</div>
<p><code>--cronjob</code> schaltet Farben und Rückfragen ab, <code>--quiet</code> unterdrückt alles außer Warnungen. Der
Timer daneben:</p>
<div class="sk-code">
  <span class="sk-code-head">INI</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="c1"># /etc/systemd/system/lynis-audit.timer</span>
</span></span><span class="line"><span class="cl"><span class="k">[Unit]</span>
</span></span><span class="line"><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">Monatliches Lynis-Audit</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Timer]</span>
</span></span><span class="line"><span class="cl"><span class="na">OnCalendar</span><span class="o">=</span><span class="s">monthly</span>
</span></span><span class="line"><span class="cl"><span class="na">RandomizedDelaySec</span><span class="o">=</span><span class="s">1h</span>
</span></span><span class="line"><span class="cl"><span class="na">Persistent</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Install]</span>
</span></span><span class="line"><span class="cl"><span class="na">WantedBy</span><span class="o">=</span><span class="s">timers.target</span></span></span></code></pre></div>
</div>
<p><code>Persistent=true</code> holt einen verpassten Lauf nach, wenn der Server zum Termin aus war.</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">systemctl daemon-reload
</span></span><span class="line"><span class="cl">systemctl <span class="nb">enable</span> --now lynis-audit.timer
</span></span><span class="line"><span class="cl">systemctl list-timers lynis-audit.timer</span></span></code></pre></div>
</div>
<p>Den Index eines Laufs holst du dir danach mit einer Zeile aus dem Bericht – das ist die Zahl, die du
über die Monate beobachtest:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">grep ^hardening_index<span class="o">=</span> /var/log/lynis-report.dat</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">hardening_index=77</span></span></code></pre></div>
</div>
<p>Wenn du dir das Ergebnis aufs Handy schicken lassen willst, hängst du eine
<code>ExecStartPost</code>-Zeile mit <a href="/tutorials/ntfy-push-benachrichtigungen/">ntfy</a> an den Service.</p>
<h2 id="wenn-es-nicht-funktioniert">Wenn es nicht funktioniert</h2>
<div class="troubleshoot not-prose">
<p><strong>Nach dem Setzen von <code>UMASK 027</code> startet ein Dienst nicht mehr und meldet „Permission denied&quot; auf seine eigene Konfigurationsdatei.</strong> Die neue umask gilt ab der nächsten Anmeldung, und alles, was du danach mit <code>&gt;</code> oder <code>cat &gt;</code> anlegst, bekommt Modus <code>0640</code> statt <code>0644</code>. Dienste, die unter einem eigenen Benutzer laufen – <code>systemd-timesyncd</code> etwa nutzt <code>DynamicUser</code> –, dürfen die Datei dann nicht mehr lesen. Im Journal steht <code>Failed to open ...: Permission denied</code>. Setz die Rechte für Konfigurationsdateien, die nicht root gehören, explizit: <code>chmod 0644 /etc/systemd/timesyncd.conf.d/99-lynis.conf</code>.</p>
<p><strong>Nach dem Anwenden der sysctl-Werte haben alle Container kein Netz mehr.</strong> Dann ist <code>net.ipv4.conf.all.forwarding</code> auf 0 gelandet. Lynis schlägt das vor, Docker kann ohne IP-Forwarding aber keine Pakete zwischen Containernetz und Außenwelt weiterreichen. Nimm die Zeile aus <code>/etc/sysctl.d/99-lynis.conf</code> heraus und setz den Wert mit <code>sysctl -w net.ipv4.conf.all.forwarding=1</code> sofort zurück – ein Neustart von Docker allein hilft nicht.</p>
<p><strong><code>systemd-timesyncd did not synchronized the time recently</code> bleibt stehen, obwohl <code>timedatectl</code> <code>NTPSynchronized=yes</code> meldet.</strong> Lynis prüft nicht die Uhr, sondern das Alter von <code>/run/systemd/timesync/synchronized</code>, und warnt ab 2048 Sekunden. Da systemd sein Abfrageintervall bei stabiler Uhr genau auf 2048 Sekunden hochzieht, läuft der Test zwangsläufig in die Warnung. Entweder du senkst <code>PollIntervalMaxSec</code> wie in Schritt 4 oder du nimmst den Test in <code>custom.prf</code> auf – ignorieren solltest du ihn nicht, denn dann übersiehst du den Tag, an dem die Zeitsynchronisation wirklich hängt.</p>
<p><strong>Lynis meldet <code>Consider hardening SSH configuration</code> zehnmal und sagt nicht, was gemeint ist.</strong> Die Bildschirmausgabe kürzt die Details weg. Sie stehen im dritten Feld von <code>grep &quot;^suggestion\[\]=SSH-7408&quot; /var/log/lynis-report.dat</code> – dort findest du für jede Zeile den Parameter und den Zielwert.</p>
<p><strong>Nach der Installation von auditd ist ein Finding dazugekommen statt weg.</strong> <code>ACCT-9630</code> meldet einen laufenden Audit-Daemon mit leerem Regelwerk. Das ist beabsichtigt: auditd ohne Regeln verbraucht Ressourcen, ohne etwas zu protokollieren. Leg Regeln unter <code>/etc/audit/rules.d/</code> ab und lade sie mit <code>augenrules --load</code> – oder deinstalliere auditd wieder, wenn du das Protokoll nicht auswertest.</p>

</div>
<p class="mt-3 text-sm text-slate-500 dark:text-slate-400">
  Dein Problem ist nicht dabei?
  <a href="/fehler/" class="font-medium text-paprika-700 hover:underline dark:text-paprika-400">Alle Fehlermeldungen durchsuchen →</a>
</p>

<h2 id="wartung--backups">Wartung &amp; Backups</h2>
<p>Lynis selbst aktualisiert sich über apt mit; durch das CISOfy-Repository bekommst du neue
Testsammlungen zeitnah. Prüfe nach einem Major-Update kurz, ob plötzlich neue Findings auftauchen –
das sind meist neue Tests, keine neuen Probleme auf deinem Server.</p>
<p><strong>Realistischer Aufwand:</strong> Das erste Audit samt Abarbeiten kostet dich einen Abend. Danach sind es
etwa 20 Minuten pro Quartal – Bericht überfliegen, neue Findings einsortieren, <code>custom.prf</code>
gegenlesen. Der monatliche Timer läuft ohne dich; du schaust nur hin, wenn sich der Index bewegt.</p>
<p><strong>Sichere die Ergebnisse deiner Arbeit mit.</strong> Die eigentlichen Werte stecken nicht in Lynis, sondern
in den Dateien, die du angelegt hast. Nimm sie in dein
<a href="/tutorials/backups-mit-restic/">Restic-Backup</a> auf:</p>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">/etc/lynis/custom.prf
</span></span><span class="line"><span class="cl">/etc/sysctl.d/99-lynis.conf
</span></span><span class="line"><span class="cl">/etc/modprobe.d/99-lynis-blacklist.conf
</span></span><span class="line"><span class="cl">/etc/ssh/sshd_config.d/99-hardening.conf
</span></span><span class="line"><span class="cl">/etc/audit/rules.d/
</span></span><span class="line"><span class="cl">/etc/systemd/system/lynis-audit.{service,timer}</span></span></code></pre></div>
</div>
<p>Nach jedem größeren Umbau – neuer Reverse-Proxy, neue Datenbank, neuer offener Port – lohnt ein
Lauf von Hand. Genau dann entstehen die Lücken, die ein Monatstimer erst Wochen später findet.</p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-sky-300 bg-sky-50 dark:border-sky-800 dark:bg-sky-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">ℹ️</span>Was Lynis nicht ist
  </p>
  <div class="prose-kitchen text-sm">Lynis auditiert <strong>von innen</strong>: Es liest Konfigurationsdateien und Systemzustände mit Root-Rechten.
Es sagt dir nicht, was ein Angreifer von außen sieht, prüft keine Webanwendung auf Schwachstellen
und ersetzt keinen Penetrationstest. Für die Außensicht brauchst du einen Portscan von einem anderen
Host und – bei öffentlich erreichbaren Diensten – einen Blick auf
<a href="/tutorials/traefik-middlewares-absichern/">Traefik-Middlewares</a>.</div>
</div>
]]></content:encoded></item></channel></rss>