IT security, FreeBSD, Linux, mail server hardening, post-quantum crypto, DNS, retro computing & hands-on hardware hacks. Privater Tech-Blog seit 2003.

Schlagwort: SelfHosted (Seite 2 von 8)

HTTPS RR und SVCB: Moderne DNS-Records für schnellere und sicherere Verbindungen

HTTPS RR und SVCB DNS-Records – schnellere Verbindungen mit HTTP/3, QUIC und DNSSEC

Wenn ein Browser eine HTTPS-Verbindung aufbaut, braucht er normalerweise mehrere DNS-Lookups und Round-Trips, bevor er überhaupt weiß, welche Protokolle der Server unterstützt. Erst A/AAAA-Record abfragen, dann TCP-Verbindung, dann TLS-Handshake, dann Alt-Svc-Header parsen für HTTP/3. Das ist ineffizient und seit November 2023 gibt es mit RFC 9460 eine saubere Lösung dafür: den HTTPS Resource Record.

Die großen Browser Hersteller unterstützen das ebenfalls schon, eigentlich mehr aus Eigeninteresse, denn viele Vorschläge kommen sogar direkt von ihnen. Oh, natürlich sollte die jeweilige Zone auch per DNSSec geschützt sein, denn wir wollen uns hier ja auf´s DNS verlassen können. Richtig?! Wenn ihr also noch kein DNSsec für eure Domain aktiviert habt (warum nicht?) dann bitte jetzt, wir haben bald 2026!

Ich habe das jetzt auf meiner DNS-Infrastruktur (BIND 9.20, FreeBSD, Master-Slave-Setup) für alle relevanten Dienste ausgerollt und dabei auch gleich SVCB-Records für die DNS-Server selbst gesetzt. Hier die Details.

Was ist der HTTPS RR?

Der HTTPS Resource Record (Typ 65) ist in RFC 9460 definiert („Service Binding and Parameter Specification via the DNS“, November 2023). Die Idee ist simpel: ein einziger DNS-Lookup liefert dem Client alles, was er für den Verbindungsaufbau braucht. IP-Adressen, unterstützte Protokolle wie HTTP/2 oder HTTP/3, Ports, und perspektivisch auch die ECH-Konfiguration für verschlüsselten SNI.

Ohne HTTPS RR sieht der Ablauf so aus: Der Client fragt A und AAAA ab, baut eine TCP-Verbindung auf, macht den TLS-Handshake, und erfährt erst aus dem Alt-Svc-Header oder durch ALPN im TLS, dass der Server auch HTTP/3 kann. Beim nächsten Request kann er dann QUIC probieren. Das sind mindestens zwei Verbindungsversuche, bis er auf dem optimalen Protokoll landet.

Mit HTTPS RR weiß der Client schon nach dem DNS-Lookup: „Dieser Server spricht h3 und h2, ist unter diesen IPs erreichbar, und hier ist die ECH-Config.“ Er kann direkt mit QUIC/HTTP/3 starten, ohne vorher TCP probiert zu haben.

Die SvcParams im Detail

Ein HTTPS RR besteht aus einer Priorität (SvcPriority), einem Zielnamen (TargetName) und einer Reihe von Service Parameters (SvcParams). Hier ein Überblick über alle definierten Parameter:

alpn (Application-Layer Protocol Negotiation): Signalisiert welche Protokolle der Server unterstützt. Typische Werte sind h2 (HTTP/2 über TLS), h3 (HTTP/3 über QUIC) oder dot (DNS over TLS). Der Client weiß damit vor dem Verbindungsaufbau, welche Protokolle zur Verfügung stehen.

ipv4hint / ipv6hint: IP-Adressen als Hint. Der Client kann diese nutzen, statt einen separaten A/AAAA-Lookup zu machen. Das spart einen Round-Trip. Wichtig: das sind Hints, keine autoritativen Antworten. Der Client darf und sollte trotzdem den normalen A/AAAA-Record prüfen.

ech (Encrypted Client Hello): Enthält den öffentlichen Schlüssel und die Parameter für ECH. Damit verschlüsselt der Client den SNI (Server Name Indication) im TLS-Handshake, sodass ein Beobachter auf dem Netzwerkpfad nicht sehen kann, welche Domain angefragt wird. Das ist der größte Privacy-Gewinn, den HTTPS RR bieten kann. Dazu später mehr.

port: Falls der Service auf einem nicht-Standard-Port läuft. Bei normalen Webservern auf 443 nicht nötig.

no-default-alpn: Signalisiert, dass die Standard-ALPNs (die sich aus dem Schema ergeben) nicht gelten. Wird benötigt wenn ein Server z.B. nur h3, aber nicht h2 unterstützt.

mandatory: Listet Parameter auf, die ein Client zwingend verstehen muss, um den Record nutzen zu können. Ein Client, der einen mandatory-Parameter nicht kennt, muss den ganzen Record ignorieren.

SvcPriority: Die Priorität des Records. 0 bedeutet AliasMode (Weiterleitung auf einen anderen Namen, ähnlich CNAME), Werte größer 0 sind ServiceMode. Mehrere Records mit unterschiedlichen Prioritäten ermöglichen Fallback-Ketten.

TargetName: Der Zielserver. Wenn er sich vom abgefragten Namen unterscheidet, leitet der Client die Anfrage an diesen Host weiter. Das ermöglicht Indirektion, ähnlich wie bei SRV-Records.

SVCB: Das generische Pendant

Der SVCB Resource Record (Typ 64) kommt aus demselben RFC 9460, ist aber nicht auf HTTPS beschränkt. HTTPS RR ist technisch gesehen nur eine spezialisierte Variante von SVCB für das HTTPS-Schema. SVCB kann für beliebige Protokolle genutzt werden.

Besonders interessant wird SVCB für die DNS Service Discovery nach RFC 9461 („Service Binding Mapping for DNS Servers“, ebenfalls 2023). Damit kann ein DNS-Server per DNS-Record signalisieren, dass er DoT (DNS over TLS) und DoH (DNS over HTTPS, RFC 8484) unterstützt. Der Record liegt unter dem Prefix _dns. vor dem Servernamen.

Der dohpath-Parameter aus RFC 9461 teilt dem Client direkt den URI-Pfad zum DoH-Endpoint mit, z.B. /dns-query{?dns}. Damit braucht der Client keine separate Konfiguration mehr, wo der DoH-Endpoint liegt. Zusammen mit RFC 9462 („Discovery of Designated Resolvers“, DDR) kann ein Client damit automatisch erkennen, dass sein Resolver verschlüsselte Protokolle unterstützt, und automatisch upgraden.

Was ich konkret deployt habe

Insgesamt 5 neue Records in zwei Zonen. Für www.kernel-error.de und cloud.kernel-error.com existierten bereits HTTPS RRs.

Zone kernel-error.de:

Apex HTTPS RR für kernel-error.de selbst:

dig HTTPS kernel-error.de +short
1 kernel-error.de. alpn="h3,h2" ipv4hint=148.251.30.200 ipv6hint=2a01:4f8:262:4716::443

HTTPS RR für den DoH-Endpoint dns.kernel-error.de:

dig HTTPS dns.kernel-error.de +short
1 dns.kernel-error.de. alpn="h3,h2" ipv4hint=37.120.183.220 ipv6hint=2a03:4000:38:20e::853

SVCB Records für DNS Service Discovery nach RFC 9461. Zwei Records mit unterschiedlichen Prioritäten, DoH bevorzugt vor DoT:

dig SVCB _dns.dns.kernel-error.de +short
1 dns.kernel-error.de. alpn="h2,dot" dohpath=/dns-query{?dns} port=443
2 dns.kernel-error.de. alpn="dot" port=853

Priorität 1 bietet DoH über HTTP/2 (Port 443), Priorität 2 reines DoT (Port 853). Ein DDR-fähiger Client (RFC 9462) kann damit automatisch erkennen, welche verschlüsselten DNS-Protokolle mein Resolver unterstützt.

Zone kernel-error.com:

Apex HTTPS RR für kernel-error.com (Matrix Federation und Web):

dig HTTPS kernel-error.com +short
1 kernel-error.com. alpn="h3,h2" ipv4hint=148.251.30.204 ipv6hint=2a01:4f8:262:4716::52

HTTPS RR für matrix.kernel-error.com (Synapse Reverse Proxy). Über CNAME-Auflösung deckt dieser Record auch chat.kernel-error.com und admin.kernel-error.com ab:

dig HTTPS matrix.kernel-error.com +short
1 matrix.kernel-error.com. alpn="h3,h2" ipv4hint=148.251.30.204 ipv6hint=2a01:4f8:262:4716::52

CNAME-Interaktion: Ein wichtiges Detail

Laut RFC 9460 können HTTPS RR und CNAME nicht am selben DNS-Namen koexistieren. Das hat direkte Auswirkungen auf mein Setup: chat.kernel-error.com und admin.kernel-error.com sind CNAMEs auf matrix.kernel-error.com. Ein separater HTTPS RR für diese Namen ist also nicht möglich und auch nicht nötig. Der Client folgt dem CNAME und nutzt dann den HTTPS RR des Ziels.

Gleiches gilt für signaling.kernel-error.com, das ein CNAME auf rtc.kernel-error.com ist.

Was bewusst nicht umgesetzt wurde

ECH (Encrypted Client Hello): Wäre der größte Privacy-Gewinn. ECH verschlüsselt den SNI im TLS-Handshake, sodass ein Beobachter nicht sehen kann, welche Domain der Client anfragt. OpenSSL 3.5 hat die API dafür, aber nginx nutzt sie nicht. Selbst in Version 1.29.7 gibt es keine native ECH-Unterstützung. Dafür bräuchte es entweder Patches für nginx oder einen anderen Reverse Proxy. Sobald sich das ändert, kommt der ech-Parameter in die HTTPS RRs.

DoQ (DNS over QUIC, RFC 9250): DoQ ist ein eigenes Protokoll, das DNS direkt über QUIC transportiert, ohne HTTP-Overhead. Das ist nicht dasselbe wie DoH über HTTP/3! BIND 9.20 unterstützt kein DoQ. Dafür müsste man ein separates Frontend wie dnsproxy oder AdGuard DNS davor setzen.

SVCB für SMTP/IMAP: Es gibt IETF-Drafts, die SVCB auf Mail-Protokolle ausweiten wollen (SMTP Submission, IMAPS). Da diese aber noch kein finaler RFC sind und aktuell kein MTA oder Client sie auswertet, habe ich darauf verzichtet. Die bestehenden SRV-Records (_imaps._tcp, _submission._tcp, _submissions._tcp) sind heute das Richtige.

HTTPS RR für turn.kernel-error.com: Der primäre Zweck ist TURN/STUN, nicht Web. Clients bekommen den Server aus der Synapse-Konfiguration, ein HTTPS RR bringt hier keinen Vorteil.

HTTPS RR für rtc.kernel-error.com: Kein HTTP/3 auf diesem Server, da der nginx dort ohne h3-Modul läuft. Ein HTTPS RR mit nur alpn="h2" würde kaum Mehrwert bringen.

Deployment in DNSSEC-signierten Zonen

Beide Zonen sind mit DNSSEC signiert (ECDSAP256SHA256, inline-signing). Der Workflow für Änderungen an signierten Zonen ist immer derselbe:

rndc freeze kernel-error.de
# Zonendatei editieren, Serial hochzählen
named-checkzone kernel-error.de /path/to/zone/file
rndc thaw kernel-error.de

Nach dem thaw signiert BIND die neuen Records automatisch und der Slave (ns1) übernimmt die Änderungen sofort per NOTIFY und AXFR. BIND 9.20 unterstützt HTTPS und SVCB Records nativ, es ist also kein TYPE65-Workaround mit generischer Record-Syntax nötig.

Records prüfen

Wer sich die Records anschauen will:

dig HTTPS kernel-error.de +short
dig HTTPS dns.kernel-error.de +short
dig SVCB _dns.dns.kernel-error.de +short
dig HTTPS kernel-error.com +short
dig HTTPS matrix.kernel-error.com +short

Ausblick

Die offensichtlichste Lücke ist ECH. Sobald nginx native Unterstützung bekommt, wird der ech-Parameter in alle HTTPS RRs eingetragen. Das wäre dann echte SNI-Verschlüsselung für alle Dienste.

SVCB für SMTP und IMAP wäre der nächste logische Schritt, sobald die aktuellen IETF-Drafts zu finalen RFCs werden und MTAs/Clients anfangen, sie auszuwerten. Immer mal wieder setzte ich auch IETF-Drafts in meinem Setup oder Labor Setup um. In diesem speziellen Fall sehe ich darin aber keinen Nutzen. Aus irgendeinem Grund schaffen es solche IT Security Themen bei E-Mails nur sehr selten in eine „schnelle“ Umsetzung. Die Browserhersteller machen da bei HTTPS wohl genug selbst. Viele Ideen kommen ja sogar von diesen.

Und DoQ (RFC 9250) steht auf der Liste, sobald BIND oder ein brauchbarer Proxy es unterstützt. Dann würden die SVCB-Records um alpn="doq" ergänzt. Ich möchte nicht wieder etwas vor meinen DNS stellen. Das wird aber bereits von den großen Browsern unterstützt!

Siehe auch:

Bei Fragen oder Anmerkungen, einfach fragen.

Kernel-Error jetzt auch im Tor-Netz: meine .onion-Adresse

Kurzfassung: www.kernel-error.de ist zusätzlich als Tor Hidden Service erreichbar.
Meine .onion-Adresse: jjyvff6eh3kp7ydfkamm27cldhsee2cl6wzfa5lfjyrfyribgeaesgqd.onion

Illustration: Kernel-Error Blog als HTTPS-Website und Tor Hidden Service (.onion) mit Fokus auf Privatsphäre, ohne Exit-Node und ohne DNS-Leaks

Der ursprüngliche Beitrag war ziemlich kurz gehalten. Seither werde ich immer wieder gefragt, wie das eigentlich zusammenspielt und was man bei so einem Setup alles bedenken muss. Also habe ich ihn deutlich erweitert und versuche, den Aufbau von der Idee bis zum fertigen vHost nachvollziehbar zu machen – inklusive der Stolperfallen, die ich unterwegs eingesammelt habe.

Warum überhaupt eine Onion-Variante?

Die Seite läuft ohnehin unter HTTPS mit aktuellen Cipher-Suites, Post-Quantum Key Exchange und HSTS. Technisch gesehen passiert zwischen Besucher und Server also schon heute nichts Unverschlüsseltes. Trotzdem gibt es ein paar Gründe, die für einen zusätzlichen Hidden Service sprechen:

  • Metadaten-Minimierung: Beim Clearnet-Abruf sieht der Provider mindestens, dass jemand mit meiner IP spricht. DNS-Resolver, Transit-Router und der Exit-Punkt kennen das Ziel. Bei einem Hidden Service ist außerhalb des Tor-Netzwerks gar nichts mehr zu sehen – weder IP noch DNS-Namen.
  • Kein Exit-Node: Wer meine Seite über einen normalen Tor-Browser mit Clearnet-URL besucht, spricht am Ende über einen Exit-Node mit meinem Server. Der Exit sieht zwar nur TLS, kann aber Metadaten wie SNI oder Zielhost mitbekommen und je nach Land auch gesperrt werden. Mit einer Onion-Adresse fällt der Exit-Node komplett weg.
  • Keine CA-Abhängigkeit: Die .onion-Adresse ist der Public-Key-Hash selbst. Wer die korrekte Adresse kennt, spricht kryptografisch nachweisbar mit meinem Server – ganz ohne Zertifikatsausstellerin und ohne OCSP-Kaskade.
  • Zensurresistenz: Sollte der Clearnet-Zugang aus irgendeinem Grund blockiert sein – falsche DNS-Antworten, gesperrte IP, generelle Sperre der Domain – bleibt die Onion-Variante unabhängig davon erreichbar.
  • Spielerei und Lerneffekt: Ich finde das Konzept hinter v3-Onions schlicht spannend. Ed25519-Keys, Rendezvous-Protokoll, Introduction Points – da steckt eine Menge gut durchdachte Krypto drin, die man am eigenen Dienst deutlich besser versteht als aus Diagrammen.

Wie das Ganze aufgebaut ist

Der Blog läuft in einer FreeBSD-Jail mit Nginx, PHP-FPM und einer eigenen Tor-Instanz. Für den Hidden Service sind drei Bausteine relevant:

  • Der Tor-Daemon hält das Schlüsselmaterial und den Rendezvous-Teil. Er lauscht nicht auf einer öffentlichen IP, sondern hängt sich in das Tor-Netzwerk und meldet dort den Dienst an.
  • Nginx stellt einen eigenen vHost bereit, der ausschließlich auf einer Loopback-Adresse (127.0.0.6:80) lauscht. Öffentlich erreichbar ist dieser vHost nie – er wird ausschließlich vom lokalen Tor-Prozess angesprochen.
  • PHP-FPM liefert wie gewohnt über einen Unix-Socket die WordPress-Inhalte aus – allerdings ohne FastCGI-Cache für den Onion-Pfad. Dazu gleich mehr.

Der Vorteil dieser Trennung: Alles, was der Tor-Daemon an den Nginx weiterreicht, kommt garantiert aus dem Tor-Netzwerk. Und alles, was der Clearnet-vHost ausliefert, geht garantiert nicht über das Onion-Interface. Zwei saubere Welten, gleiche Codebasis, null Überschneidung im Cache.

Tor-Daemon: torrc

Die minimale Konfiguration für einen v3-Hidden-Service ist überraschend kurz:

HiddenServiceDir      /var/db/tor/hidden_service/
HiddenServiceVersion  3
HiddenServicePort     80 127.0.0.6:80
SocksPort             127.0.0.6:9050
Log                   notice file /var/log/tor/notices.log

Beim ersten Start legt Tor in HiddenServiceDir das komplette Schlüsselmaterial selbst an: den privaten ed25519-Schlüssel, die daraus abgeleitete hostname-Datei mit der .onion-Adresse und ein paar weitere Dateien für die Introduction-Points. Diese Dateien sind privilegiert – wer sie hat, kann den Dienst übernehmen und sich gegenüber der Welt als mein Server ausgeben. Entsprechend gehören sie dem _tor-User, haben mode 700 und sind im Backup separat behandelt.

Die Adresse selbst ist 56 Zeichen lang und wird aus dem ed25519-Public-Key abgeleitet. Aenderbar ist sie nicht – wer eine bestimmte Wunschadresse will, muss mit Tools wie mkp224o so lange Schlüssel durchprobieren, bis der Base32-Prefix passt. Hat mich nicht gereizt, dafür ist meine Adresse eben zufällig.

Nginx-vHost für den Onion-Service

Der eigentliche vHost ist bewusst klein gehalten. Kein Cache, kein TLS, keine QUIC-Spielereien – alles, was spezifisch für den Clearnet-Teil ist, fehlt hier:

server {
    listen 127.0.0.6:80;
    server_name jjyvff6eh3kp7ydfkamm27cldhsee2cl6wzfa5lfjyrfyribgeaesgqd.onion;

    root  /usr/local/www/www.kernel-error.de;
    index index.php index.html index.htm;

    # access/error-logs per Default aus (keine Besucher-Spuren im Dateisystem).
    # Nur bei Bedarf zu Debugging-Zwecken aktivieren und später wieder aus.
    #access_log /var/log/nginx/www-kernel-error-de-access_tor.log combined;
    #error_log  /var/log/nginx/www-kernel-error-de-error_tor.log;

    add_header X-Robots-Tag      "noarchive, noimageindex" always;
    add_header Permissions-Policy "interest-cohort=()"    always;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~* \.(css|gif|ico|jpeg|jpg|js|png|svg|webp|woff2?)$ {
        access_log off;
        expires 30d;
        add_header Cache-Control "public";
        try_files $uri =404;
    }

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php-fpm.sock;
        fastcgi_index index.php;

        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO       $fastcgi_path_info;

        # Cache hart deaktivieren - kein gemeinsamer Pool mit dem Clearnet-vHost
        fastcgi_no_cache      1;
        fastcgi_cache_bypass  1;
    }
}

Ein paar Punkte sind bewusst so gewählt:

  • listen 127.0.0.6:80: Die Adresse ist frei wählbar innerhalb von 127.0.0.0/8, muss aber mit dem HiddenServicePort in der torrc übereinstimmen. Ich nehme bewusst nicht 127.0.0.1, damit der Onion-Listener klar vom Standard-Loopback zu unterscheiden ist – hilft beim Debugging und bei sockstat.
  • Kein TLS: Zwischen Tor-Daemon und Nginx liegt nur die Loopback-Schnittstelle, dafür braucht es kein Zertifikat. Und .onion-Zertifikate gibt es nur über kommerzielle CAs (Extended Validation), Let’s Encrypt stellt keine aus. Der eigentliche Schutz auf dem Draht kommt komplett aus dem Tor-Protokoll – End-to-End verschlüsselt vom Tor-Client bis hier zur Loopback-Adresse.
  • Getrennte Logs (per Default aus): Im Normalbetrieb sind access_log und error_log im Onion-vHost auskommentiert – so entstehen erst gar keine Dateien mit IP-, User-Agent- oder Pfad-Informationen der Onion-Besucher. Zum Debugging lassen sich die beiden Zeilen kurz einkommentieren, danach bewusst wieder aus. Wichtig ist auf jeden Fall, dass Clearnet- und Onion-Zugriffe nie im gleichen Logfile landen – über Zeitstempel und User-Agent-Muster ließen sich sonst leicht Korrelationen bilden.
  • X-Robots-Tag: noarchive, noimageindex verhindert, dass Suchmaschinen die Onion-Variante cachen oder Bilder indexieren. noindex setze ich bewusst nicht – wer die Onion-Adresse kennt, darf sie auch in Verzeichnissen auftauchen lassen.
  • Permissions-Policy: interest-cohort=() schaltet Googles FLoC-/Topics-API explizit aus. Für einen Privacy-Blog im Tor-Netz ist das eher symbolisch, schadet aber nicht.

Der Knackpunkt: Cache-Isolation

Der Clearnet-vHost nutzt einen FastCGI-Cache (fastcgi_cache_path), damit WordPress nicht jede Seitenauslieferung neu rendern muss. Das ist für einen Blog mit statischen Inhalten ein riesiger Performance-Boost. Genau dieser Cache ist aber auch der Punkt, an dem man sich beim Onion-Betrieb ins Knie schießen kann.

Wenn der Onion-vHost denselben Cache-Pool verwenden würde, könnten zwei unerwünschte Effekte auftreten:

  • Cross-Origin-Leakage im HTML: WordPress baut absolute Links anhand der siteurl/home-Option. Die steht auf https://www.kernel-error.de. Wenn eine Clearnet-Anfrage cached, landen in jedem Link Clearnet-URLs im Cache-Eintrag. Liefert der Onion-vHost dann dieselbe Seite aus dem Cache, sieht der Tor-Besucher eine Seite voller Clearnet-Links – und jeder Klick würde ihn aus dem Hidden Service hinauskatapultieren.
  • Fingerprinting über Cache-Keys: Abhängig davon, wer die Seite zuerst aufgerufen hat, liefert der Cache deterministisch „warme“ oder „kalte“ Antworten. Ein Angreifer, der beide Varianten beobachtet, kann Rückschlüsse ziehen. Klein, aber unnötig.

Die Lösung ist bewusst stumpf: Der Onion-vHost bekommt gar keinen fastcgi_cache_path-Verweis. Zusätzlich stehen im location ~ \.php$-Block die beiden Schalter fastcgi_no_cache 1 und fastcgi_cache_bypass 1, damit selbst bei versehentlich geerbter Konfiguration weder gelesen noch geschrieben wird. Jeder Request rendert frisch durch den FPM.

Bei der Besucher-Zahl über den Hidden Service ist das unkritisch. Die Clearnet-Seite bleibt gleichzeitig durch ihren eigenen Cache schnell – beide Welten profitieren von ihrer jeweils passenden Strategie.

Der Onion-Location-Header und die Meldung im Tor Browser

Damit Besucher überhaupt erfahren, dass es eine Onion-Variante gibt, setzt der Clearnet-vHost zwei zusätzliche Header:

add_header Onion-Location "http://jjyvff6eh3kp7ydfkamm27cldhsee2cl6wzfa5lfjyrfyribgeaesgqd.onion$request_uri" always;
add_header Alt-Svc        'http="jjyvff6eh3kp7ydfkamm27cldhsee2cl6wzfa5lfjyrfyribgeaesgqd.onion:80"; ma=86400' always;

Der Onion-Location-Header ist ein vom Tor-Project definiertes Signal. Ruft man den Blog im Tor Browser (Version 9.5 oder neuer) über die Clearnet-Adresse auf, blendet der Browser eine unauffällige „.onion available“-Schaltfläche in der URL-Leiste ein und bietet an, auf die Hidden-Service-Variante umzuschalten. Das ist die „Meldung“, die einige irritiert: Sie kommt nicht von meiner Seite, sondern direkt vom Tor Browser, der den Header interpretiert.

Wichtig dabei: Der Tor Browser akzeptiert den Header nur, wenn ein paar Bedingungen erfüllt sind. Das verhindert, dass ein kompromittierter Server Besucher auf fremde Hidden Services umlenken kann:

  • Die Ursprungsseite muss per HTTPS ausgeliefert worden sein.
  • Die im Header angegebene .onion-Adresse muss wohlgeformt sein (v3, 56 Zeichen, Base32).
  • Die Nutzerin muss die Umleitung aktiv bestätigen – es gibt keine automatische Weiterleitung.

Der Alt-Svc-Header (RFC 7838) ist die generische Variante. Einige alternative Clients nutzen ihn, um Alternative-Services im Hintergrund vorzumerken. Für den Tor Browser ist primär der Onion-Location-Header relevant, Alt-Svc ist eher Absicherung.

DNS als zusätzliche Verifikation

Ein Header auf der Seite ist eine gute Sache – wer dem Server vertraut, bekommt den Hinweis auf den Onion-Service. Was aber, wenn jemand meine Angabe kontrollieren will, ohne die Seite selbst aufgerufen zu haben?

Dafür habe ich einen einfachen TXT-Record im DNS hinterlegt:

dig +short TXT www.kernel-error.de
"onion=jjyvff6eh3kp7ydfkamm27cldhsee2cl6wzfa5lfjyrfyribgeaesgqd.onion"

Das Format onion=… ist kein offizieller Standard, sondern meine persönliche Konvention. Es gibt einen Internet-Draft von Alec Muffett (draft-muffett-same-origin-onion-v2), der einen ähnlichen Ansatz beschreibt, aber als RFC nie durchgegangen ist. Bis sich etwas Standardisiertes etabliert, bleibe ich bei meinem einfachen Ansatz: Wer wissen will, ob die Onion-Adresse wirklich zu mir gehört, vergleicht den Header, den TXT-Record und meine öffentlichen Ankündigungen. Die Zone selbst ist DNSSEC-signiert, der TXT-Record ist also nicht spürbar manipulierbar, solange der Resolver DNSSEC validiert.

Wem das zu wacklig ist: Für .onion gibt es auch kommerzielle Zertifikate (DigiCert stellt EV-Zertifikate für Onion-Services aus). Damit könnte man die Onion-Seite zusätzlich hinter HTTPS legen und in der Tor-Browser-URL-Leiste wäre die Organisation sichtbar. Für einen privaten Blog ist der Aufwand und die Kosten aber deutlich höher als der Nutzen.

Warum kein TLS-Zertifikat nötig ist

Der Punkt irritiert am Anfang fast jeden: Eine Webseite ohne HTTPS, im Jahr 2026, fühlt sich einfach falsch an. Wenn man sich aber anschaut, wofür TLS im Clearnet eigentlich da ist, wird schnell klar, warum es bei einem Tor Hidden Service tatsächlich überflüssig ist.

TLS erfüllt zwei Aufgaben gleichzeitig:

  • Vertraulichkeit auf der Leitung: Niemand zwischen Client und Server soll mitlesen können – weder der ISP, noch das WLAN im Cafe, noch ein Transit-Provider.
  • Identitätsnachweis des Servers: Der Client muss sicher sein können, dass er tatsächlich mit dem Server spricht, der hinter dem Domainnamen steht. Diese Aufgabe übernimmt die CA-Kette, die das Zertifikat an den DNS-Namen bindet.

Bei einem Tor Hidden Service sind beide Aufgaben bereits in das Protokoll selbst eingebaut – ohne dass irgendwo ein Zertifikat im Spiel wäre:

  • Vertraulichkeit kommt aus dem Tor-Tunnel: Der Datenstrom zwischen Client und Hidden Service lauft durch mehrere übereinander gelegte Verschlüsselungsschichten. Jedes Relay kann nur die eigene Schicht entfernen und sieht dadurch nur das nächste Ziel, nicht den Inhalt und auch nicht den gesamten Pfad. Auf dem Draht ist der Traffic zu keinem Zeitpunkt im Klartext zu sehen – ein zusätzliches TLS würde dieselbe Eigenschaft nur ein zweites Mal liefern.
  • Authentizität steckt in der Adresse: Die 56 Zeichen einer v3-Onion sind keine willkürliche Zeichenfolge, sondern die Base32-Darstellung eines ed25519-Public-Keys (plus Checksumme und Versionsbyte). Beim Verbindungsaufbau fordert der Tor-Client vom Server eine Signatur an und prüft sie gegen genau diesen Public-Key. Passt sie nicht, kommt keine Verbindung zustande. Damit übernimmt die Adresse selbst die Rolle, die im Clearnet das Zertifikat spielt – nur dass hier keine CA existieren muss, die das beglaubigt.

Anders formuliert: Der Hidden Service ist von der ersten Byte an selbst-authentisierend. Die Sicherheitsgarantie steht und fällt mit der korrekten Onion-Adresse, nicht mit einem Dritten, der sie beglaubigen müsste. Deshalb hat Let’s Encrypt für .onion-Adressen bewusst nie Zertifikate ausgestellt – es gibt schlicht nichts, was eine CA hier noch verifizieren könnte, was nicht schon durch die Adresse selbst belegt wäre.

Den einzigen verbleibenden Nutzen eines Zertifikats – das sichtbare Organisationskürzel in der Tor-Browser-URL-Leiste bei EV-Zertifikaten – kann man sich für einen privaten Blog guten Gewissens sparen.

Firewall und Systemhärtung

Die schöne Eigenschaft eines Hidden Service: Es muss kein zusätzlicher Port nach außen geöffnet werden. Der Tor-Prozess baut nur ausgehende Verbindungen auf, der Nginx-Onion-vHost lauscht nur auf der Loopback-Adresse. Aus Sicht einer Firewall ändert sich durch den Hidden Service gar nichts.

Trotzdem gibt es ein paar Punkte, die ich bewusst setze:

  • Jail-Isolation: Tor und Nginx laufen in derselben FreeBSD-Jail. Die Jail hat nur die öffentliche IP für den Clearnet-Nginx und separate Loopback-Adressen für interne Dienste. Dadurch kann der Tor-Prozess nicht „aus Versehen“ auf andere Dienste zugreifen – und er teilt sich seinen Kernel-Namespace nur mit dem, was ich bewusst in dieselbe Jail gelegt habe.
  • Dateirechte: /var/db/tor/hidden_service/ gehört dem _tor-User, mode 700. Das private Schlüsselmaterial muss genauso behandelt werden wie ein TLS-Private-Key – wer es hat, kann meine .onion übernehmen.
  • WordPress-Härtung: Der Login (wp-admin, wp-login.php) wird über das Plugin WPS Hide Login auf eine nicht-offensichtliche URL gelegt und ist ohnehin nur via Clearnet mit HTTPS erreichbar. Im Onion-vHost wird die Login-URL weder verlinkt noch erwähnt. Administration findet ausschließlich über die Clearnet-Variante statt.
  • Keine Log-Korrelation: Die getrennten Access-Logs habe ich schon erwähnt. Zusätzlich analysiere ich sie separat und führe keine IPs aus dem Clearnet-Log mit dem Onion-Log zusammen. Das wäre technisch möglich, würde aber den Sinn des Setups konterkarieren.
  • Keine externen Ressourcen: Das Theme und die eingebundenen Plugins laden keine Fonts von Google, keine Scripts von CDNs, keine Gravatare aus der Welt. Wenn ein Element doch mal nach www.kernel-error.de auflaufen sollte (etwa durch ein falsch konfiguriertes Plugin), würde der Tor Browser das als Mixed-Origin-Request anzeigen und viele Nutzerinnen würden es nicht mehr laden – das fällt dann schnell auf und ich fixe es.

Warum das als „sicher“ gilt

„Sicher“ ist immer relativ – gegenüber welchem Angreifer, unter welchem Modell? Ein paar Eigenschaften kann man aber konkret benennen:

  • End-to-End verschlüsselt: Tor baut zwischen Client und Hidden Service einen dreistufigen Tunnel durch Rendezvous- und Introduction-Points. Keiner der Zwischenknoten sieht, wer mit wem spricht oder was übertragen wird. Auf dem Draht sieht außerdem niemand eine .onion-Adresse – die Rendezvous-Logik wählt den Zielknoten indirekt.
  • Kryptografische Authentizität der Adresse: Die 56 Zeichen der v3-Onion sind der Base32-codierte ed25519-Public-Key samt Checksumme und Versionsbyte. Wer die richtige Adresse getippt oder kopiert hat, landet kryptografisch garantiert auf dem Server, der den zugehörigen privaten Schlüssel hat. Keine CA, kein DNS, kein Zwischenhändler kann das verändern, ohne dass die Adresse anders aussieht.
  • Kein TLS nötig: Verschlüsselung kommt aus dem Tor-Tunnel, Authentizität aus der Adresse selbst. Details dazu stehen weiter oben im eigenen Abschnitt – kurz: TLS würde die Eigenschaften nicht ergänzen, sondern nur doppeln.
  • Anonymität der Besucher: Der Server sieht keine echte Client-IP, nur einen Rendezvous-Punkt im Tor-Netz. Zensur und Traffic-Analyse auf dem letzten Stück entfallen vollständig.
  • Vertraulichkeit für mich: Der Server ist nicht öffentlich erreichbar, es gibt keinen offenen Port, den man mit nmap finden könnte. Die öffentliche IP des Servers ist durch die Tor-Rendezvous-Logik nicht aus dem Netzwerkverkehr ableitbar, solange ich nicht durch Fehlkonfiguration (etwa hart verlinkte Clearnet-URLs im HTML) Hinweise leake.

Stolperfallen und was man besser lässt

  • WordPress-URLs: siteurl und home bleiben auf der Clearnet-URL. Einige Anleitungen empfehlen, bei Aufruf der Onion-Variante die URL dynamisch umzuschreiben. Das habe ich bewusst nicht gemacht – es führt zu kaputten Canonical-Links, mischt Content- und Admin-Bereich und bricht meist den Blöck-Editor. Stattdessen akzeptiere ich, dass im Onion-HTML auch mal eine Clearnet-Link vorkommt (etwa im Footer), und setze darauf, dass der Tor Browser korrekt warnt, bevor er dort hinschickt.
  • Externe Embeds: YouTube-Videos, Twitter-Embeds, Gravatare – alles Dinge, die eine Onion-Seite sofort deanonymisieren könnten, wenn sie geladen würden. Das Theme und die Plugins auf diesem Blog laden bewusst keine externen Ressourcen.
  • Redis/Object-Cache: Der Object-Cache speichert keine gerenderten HTML-Seiten, sondern nur WP-interne Objekte. Hier ist die Vermischung unkritisch, weil die resultierenden URLs erst beim Rendern (durch den jeweiligen vHost) entstehen.
  • FastCGI-Cache: Wie oben beschrieben – für die Onion-Variante komplett deaktiviert. Wer es trotzdem aktivieren will, braucht zwingend einen eigenen fastcgi_cache_path, eigenes Key-Schema und muss den Host-Teil im Cache-Key haben.
  • Monitoring: Klassische Uptime-Checks von außen funktionieren auf .onion-Adressen nicht ohne weiteres. Dienste wie Uptime-Kuma können inzwischen Tor-Proxy nutzen, ansonsten hilft ein kleiner curl --socks5-hostname-Check.

Was man noch härter machen kann

Für meinen privaten Blog halte ich das beschriebene Setup für ein solides Minimum. Wer seinen Hidden Service strenger absichern will – etwa weil dort Whistleblower-Material, Redaktions-Inhalte oder andere wirklich schutzwürdige Dinge liegen – sollte ein paar zusätzliche Punkte beachten:

  • Zusätzliche HTTP-Header: Referrer-Policy: no-referrer verhindert, dass beim Klick auf einen externen Link die .onion-URL als Referer mitgeschickt wird. Content-Security-Policy (restriktiv, z. B. default-src 'self'; img-src 'self' data:;) blockt Mixed-Origin-Requests hart, bevor der Browser sie überhaupt versucht. Dazu X-Content-Type-Options: nosniff und X-Frame-Options: DENY, damit Clickjacking und MIME-Sniffing ausgeschlossen sind.
  • server_tokens off: Im Haupt-vHost ist der Nginx-Version-String schon länger abgeschaltet und durch einen eigenen Custom-Header ersetzt. Im Onion-vHost gehört server_tokens off; genauso rein – ohne das steht die Nginx-Version in jeder 404-Seite und erleichtert Fingerprinting.
  • Tor-Daemon härten: Sandbox 1 in der torrc aktiviert unter FreeBSD Capsicum und unter Linux Seccomp-Filter. Damit bekommt der Tor-Prozess nur die Syscalls, die er wirklich braucht. Zusätzlich lässt sich mit HiddenServiceEnableIntroDoSDefense 1 sowie HiddenServiceEnableIntroDoSRatePerSec und HiddenServiceEnableIntroDoSBurstPerSec ein integrierter DoS-Schutz am Introduction-Point aktivieren – seit Tor 0.4.2 gibt es das als Plugin-freie Bordmittel.
  • Client Authorization: Für wirklich nicht-öffentliche Dienste kennt Tor einen Mechanismus, bei dem nur Clients mit passendem x25519-Key-Paar den Hidden Service überhaupt erreichen. Die Adresse ist dann zwar im Tor-Netz bekannt, ohne die Private-Key-Datei auf dem Client kommt man aber keinen Zentimeter weit. Für einen öffentlichen Blog unpassend, für ein Journalisten-Dropbox-Setup die wichtigste Absicherung überhaupt.
  • Offline-Backup der Hidden-Service-Keys: Wenn der Inhalt von /var/db/tor/hidden_service/ verloren geht, ist die .onion-Adresse weg – es gibt keinen Weg, sie wiederherzustellen. Das private Schlüsselmaterial gehört deshalb auf einen verschlüsselten Offline-Datenträger und sollte genauso behandelt werden wie ein TLS-Root-Key. Wer die Adresse überträgt, überträgt gleichzeitig die Fähigkeit, sie zu betreiben – das ist nichts, was in einem Cloud-Backup liegen sollte.
  • Jail-/Container-Trennung von Tor und Web: Aktuell laufen Tor-Daemon und Nginx in derselben FreeBSD-Jail, weil sie über die Loopback-Adresse sowieso miteinander sprechen müssen. Wer paranoider sein will, packt den Tor-Prozess in eine eigene Jail mit eigener Loopback-IP und forwardet nur den HiddenService-Port – dann kann ein kompromittierter WordPress-Prozess nicht mal versehentlich an die Schlüsseldateien. Für mich persönlich ist der Aufwand zurzeit nicht gerechtfertigt, für einen Hochrisiko-Service aber eine ernsthafte Option.
  • Ehrliche Einschätzung zur Anonymität des Betreibers: Wer einen Hidden Service betreibt, um die eigene Identität zu schützen, muss auch alles drumherum sauber haben – Domain-Whois, Zertifikats-SANs auf dem Clearnet-Host, Uptime-Monitoring, Backups, Zeitstempel in Git-Commits, selbst die Systemzeit auf dem Server. Die .onion-Adresse alleine macht den Betreiber nicht anonym. Sie ist ein Baustein, kein Gesamtkonzept.

Für diesen Blog ist das bewusst nicht alles umgesetzt. Ich veröffentliche unter Klarnamen und möchte nur die Daten meiner Besucher besser schützen. Für jemanden, der aus guten Gründen wirklich anonym bleiben muss, sind die Punkte oben Pflicht, nicht Kür.

Fazit

Ein Tor Hidden Service für einen bestehenden WordPress-Blog ist überraschend geradlinig aufzusetzen. Die technische Umsetzung umfasst im Kern einen Tor-Daemon mit v3-Konfiguration, einen getrennten Nginx-vHost ohne HTTPS und ohne FastCGI-Cache, sowie zwei Header im Clearnet-vHost. Der Aufwand bleibt überschaubar, der Gewinn an Erreichbarkeit und Metadaten-Minimierung ist messbar.

Wer die Adresse im Tor Browser aufruft, bekommt exakt den gleichen Blog zu sehen – nur ohne TLS-Handshake, ohne Exit-Node und ohne Spur im eigenen DNS-Resolver. Die Seite wird nicht häufig aus dem Tor-Netz aufgerufen, aber sie ist für die Fälle da, in denen sie gebraucht wird. Und ehrlich gesagt: Es macht auch einfach Spaß, ein System so zu bauen, dass beide Welten sauber nebeneinander existieren.

Siehe auch: HTTP/3 und QUIC, Post-Quantum TLS für Nginx, TLS-ECDHE einfach erklärt

Fragen? Einfach melden.

WordPress wp-cron.php: Ist die angebliche Sicherheitslücke real?

Picture of an hacker checken for wordpress vulnerability

In letzter Zeit begegnen mir immer wieder sogenannte „Vulnerability Report Scams“. Klar, mit Angst und Unwissenheit kann man Geld verdienen, also wird es auch jemand tun. Besonders fällt mir das im Zusammenhang mit der wp-cron.php auf.

Ich habe häufig Reports gesehen, die in etwa so aussehen:

Critical Vulnerability Report- {Critical BUG #P1} - https://www.example.com/ - vulnerable to attack via wp-cron.php

Hello  Security team,

I am a Security Engineer, Cyber Security Researcher, Bug Bounty Hunter  & Ethical Hacker. While testing your domain https://www.example.com/ I have found some important vulnerabilities in your site.

Vulnerability Name:   https://www.example.com/  -  vulnerable to DoS attack via wp-cron.php

Vulnerable Domain:  https://www.example.com/wp-cron.php

Description:

The WordPress application is vulnerable to a Denial of Service (DoS) attack via the wp-cron.php script. This script is used by WordPress to perform scheduled tasks, such as publishing scheduled posts, checking for updates, and running plugins.
An attacker can exploit this vulnerability by sending a large number of requests to the wp-cron.php script, causing it to consume excessive resources and overload the server. This can lead to the application becoming unresponsive or crashing, potentially causing data loss and downtime.

I found this vulnerability at https://www.example.com/wp-cron.php endpoint.

Steps to Reproduce: reference- https://hackerone.com/reports/1888723

navigate to: https://www.example.com/wp-cron.php
intercept the request through the burp suite
right click on the request and send it to the repeater
Now send a request, and you will see the response as  200 OK

---

this can be also done by the curl command given below

curl -I "https://www.example.com/wp-cron.php"

POC: Attached

Impact:

If successful, this misconfigured wp-cron.php file can cause lots of damage to the site, such as:

Potential Denial of Service (DoS) attacks, resulting in unavailability of the application.
Server overload and increased resource usage, leading to slow response times or application crashes.
Potential data loss and downtime of the site.
Hackers can exploit the misconfiguration to execute malicious tasks, leading to security breaches.

Exploitation:
Exploitation can be done through a GitHub tool called doser.go https://github.com/Quitten/doser.go
I did not do that as this can impact your website.
Get the doser.py script at https://github.com/Quitten/doser.py
Use this command to run the script: python3 doser.py -t 999 -g 'https://www.example.com/wp-cron.php'
Go after https://www.example.com/ 1000 requests of the doser.py script.
The site returns code 502.

Suggested Mitigation/Remediation Actions:

To mitigate this vulnerability, it is recommended to disable the default WordPress wp-cron.php script and set up a server-side cron job instead. Here are the steps to disable the default wp-cron.php script and set up a server-side cron job:
Access your website's root directory via FTP or cPanel File Manager.
Locate the wp-config.php file and open it for editing.
Add the following line of code to the file, just before the line that says "That's all, stop editing! Happy publishing.":

1define('DISABLE_WP_CRON', true);

Save the changes to the wp-config.php file.
Set up a server-side cron job to run the wp-cron.php script at the desired interval. This can be done using the server's control panel or by editing the server's crontab file.
References:

For more information about this vulnerability, please refer to the following resources:

https://hackerone.com/reports/1888723

https://medium.com/@mayank_prajapati/what-is-wp-cron-php-0dd4c31b0fee

Cron
Fix Them ----- I have protected your company and saved it from a big loss so give me some appreciation Bounty Reward. I am sharing my PayPal ID with you. Paypal ID: woop woop Current Market Value Minimum Bounty Reward for Critical BUG P1 Type. The bug I reported is part of type P1 Vulnerability severity Bug bounty reward amount (in USD) P1 (Critical) $2500 P2 (High) $1500 P3 (Medium) $1000 P4 (Low) $500 Please feel free to let me know if you have any other questions or need further information. I am happy to secure it. I hope this will be fixed soon. Feel free to let me know if you have any other questions. Thanks & Regards

Ist das nun ein echtes Problem oder nicht?

Ja und Nein. In der Nachricht wird korrekt beschrieben, was die wp-cron.php tut und warum sie wichtig ist. Auch die Tatsache, dass sie extern unendlich oft aufgerufen werden kann und dadurch potenziell eine Überlastung auslösen könnte, ist nicht falsch. Selbst der Tipp, auf eine lokale Crontab-Version umzusteigen, ist nicht verkehrt. Allerdings muss man das Ganze in den richtigen Kontext setzen: wp-cron.php ist standardmäßig in WordPress aktiviert und wird für geplante Aufgaben genutzt. Die geplanten Aufgaben werden in der Datenbank abgelegt. Gibt es etwas zu tun und die wp-cron.php wird aufgerufen, dann wird auch gearbeitet. Gibt es nichts zu tun, dann gibt es auch keine Arbeit. Die Empfehlung, sie zu deaktivieren und durch einen serverseitigen Cron-Job zu ersetzen, ist eher eine Performance-Optimierung als eine echte Sicherheitsmaßnahme.

Es handelt sich nicht um einen Zero-Day-Exploit und es gibt keine direkte Gefahr eines Datenabflusses. Falls es wirklich zu Performance-Problemen kommt, gibt es einfache Gegenmaßnahmen. Sollte tatsächlich jemand versuchen, die wp-cron.php gezielt anzugreifen, hilft ein simples Rate Limiting, entweder über die Firewall oder direkt mit mod_security (Apache) bzw. limit_req (nginx).

Rate Limiting mit nginx

limit_req_zone $binary_remote_addr zone=cronlimit:10m rate=1r/s;

server {
    location = /wp-cron.php {
        limit_req zone=cronlimit burst=3 nodelay;
        limit_req_status 429;
    }
}

Das begrenzt die Anfragen auf 1 pro Sekunde, mit maximal 3 Anfragen in kurzer Zeit.

Sollte man wp-cron.php deaktivieren?

Nicht unbedingt. Klar, im Fall eines Angriffs kann das als erste Maßnahme helfen. Besser ist es aber, wp-cron.php lokal auszuführen und den Zugriff darauf über den Webserver auf bestimmte IP-Adressen zu beschränken. Anschließend kann man einen Cronjob anlegen, der alle 15 Minuten ausgeführt wird:

*/15 * * * * wget -q -O - https://www.example.com/wp-cron.php?doing_wp_cron >/dev/null 2>&1

Zugriff per nginx einschränken:

location ~* ^/wp-cron.php$ {
    allow 1.2.3.4;  # Ersetze mit deiner IP
    deny all;
}

Fazit

Das ist ganz sicher kein P1-Bug. Und wenn der Report direkt eine Preistabelle mitliefert, ist das schon ein ziemlich eindeutiges Zeichen für einen Scam.

  • Ja, wp-cron.php könnte unter bestimmten Umständen zu Problemen führen.
  • Nein, es ist kein echter Sicherheits-Bug.
  • Wer weiß, was er tut, hat bereits die richtigen Maßnahmen getroffen.

Keine Panik. Stattdessen lieber kurz die eigene Konfiguration prüfen und gut ist. Wer auf das nginx Rate Limit setzt und dieses testen möchte, kann mein rate_limit_test.sh auf GitHub nutzen.

Siehe auch: Ist mein Netzwerk kompromittiert?

Fragen? Einfach melden.

DNS over TLS mit BIND, Stunnel und Android 9: Eigener DoT-Server

Die Zeit ging weiter, die Entwicklung bei BIND und DNS ebenfalls. Daher gibt es nun einen neuen Beitrag, der das aktuelle Setup mit BIND 9.20 auf FreeBSD 15 beschreibt – inklusive sauberer Trennung von authoritative DNS (Port 53) und öffentlichem Resolver (DoT/DoH) sowie reproduzierbaren CLI-Tests für IPv4 und IPv6. Bitte dort weiterlesen.

Über die Techniken DoT (DNS over TLS) habe ich bereits im Zusammenhang mit Bind 9.16 geschrieben. Ebenfalls DoH (DNS over HTTPS) gibt es einen kleinen Beitrag.

Bilder der Bind 9 TLS Konfiguration

Zu diesem Zeitpunkt bracht BIND 9 die Unterstützung für DoH und DoT noch nicht selbst mit. Daher waren zu diesem Zeitpunkt noch Umwege über stunnel oder nginx zusammen mit doh-proxy nötig.

Zum Glück kommt die letzte stable Version 9.18.0 (26. Januar 2022) mit dem nötigen Support.

named now supports securing DNS traffic using Transport Layer Security (TLS). TLS is used by both DNS over TLS (DoT) and DNS over HTTPS (DoH).

Warum möchte man noch gleich DoH oder DoT benutzen? Ganz einfach… Über diese Techniken werden DNS Abfragen verschlüsselt übertragen. Dieses ist ein weiterer Schutz davor manipulierte Antworten zu bekommen und selbstverständlich, damit die eigenen DNS Abfragen erst überhaupt nicht mitgelesen werden. Denn wenn von einem Gerät im Netzwerk die DNS Abfrage zu z.B.: www.tagesschau.de kommt, könnte man davon bereits Dinge ableiten.

Wie die meisten Bind Konfigurationen ist dieses ebenfalls straightforward. Ab Version 9.18 bringt Bind alles Nötige mit. Da wir nun TLS mit dem Bind sprechen möchten, benötigen wir natürlich ein gültiges Zertifikat, wie z.B. beim nginx für seine Webseite.

Ebenfalls sollte man ein paar frische Diffie-Hellmann Parameter generieren:

openssl dhparam -out dhparam.pem 4096

Die eigentliche bind Konfiguration kann in der named.conf.options geschehen:

options {
        [...]
        listen-on port 853 tls local-tls { 37.120.183.220; };
        listen-on-v6 port 853 tls local-tls { 2a03:4000:38:20e::853; };
        listen-on port 443 tls local-tls http default { 37.120.183.220;  };
        listen-on-v6 port 443 tls local-tls http default { 2a03:4000:38:20e::853; };
        [...]
        allow-recursion-on { 127.0.0.0/8; ::1/128; 2a03:4000:38:20e::853; 37.120.183.220; };
        [...]
};

Da der bind auf weiteren Ports lauschen soll erweitert man diese für IPv4 und IPv6. Der Default Port für DoH ist dabei 443 und der default Port für DoT ist 853, beides TCP.

listen-on sowie listen-on-v6 sind wohl selbsterklärend.
port ist der TCP Port und erklärt sich ebenfalls.
tls sagt dem Bind das wir tls sprechen möchten.
local-tls verweißt auf den gleichnamigen tls Block über welchen man seine TLS Konfiguration vornimmt.
http ist für DoH.
default gibt den eigentlichen endpoint für die DoH Abfragen an, im default ist es /dns-query

Da der Server unsere DNS Abfragen erledigen soll, müssen wir ihm dieses noch per allow-recursion-on auf den jeweiligen Adressen erlauben.

Als nächstes wird die eigentliche TLS Terminierung konfiguriert (das lässt sich ebenfalls auslagern, wenn gewünscht). Dafür wird der folgende Block, außerhalb der Options Blocks, ergänzt:

tls local-tls {
    cert-file "/usr/local/etc/ssl/wild.kernel-error.de/2022/ecp/chain.crt";
    key-file "/usr/local/etc/ssl/wild.kernel-error.de/2022/ecp/http.key";
    dhparam-file "/usr/local/etc/ssl/dhparam.pem";
    protocols { TLSv1.2; TLSv1.3; };
    ciphers "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256";
    prefer-server-ciphers yes;
    session-tickets no;
};

local-tls ist dabei der name des Blocks. Auf diesen verweisen wir oben.
cert-file ist der Pfad zum Zertifikat. Ich habe dort nicht nur das Zertifikat, sondern die gesamte Chain, also mit Intermediate und Root.
key-file ist der Pfad zum Key des Zertifikates.
dhparam-file ist der Pfad zu den Diffie-Hellman Parametern.
protocols definiert die zu verwendenden TLS Protokolle. In diesem Beispiel TLS1.2 sowie TLS1.3.
ciphers definiert die zu verwendenden cipher. Es soll ja „sicher“ bleiben.
prefer-server-ciphers übermittelt dem Client die Information, in welcher Reihenfolge protokoll/cipher Kombinationen probiert werden sollen um einen Match zu finden. Erst das vermeintlich sicherste und dann immer „schlechter“.
session-tickets regelt ob eine Wiederaufnahme von TLS Sessions erlaubt ist oder nicht. Da ich forward secrecy nutzen möchte, ist es deaktiviert.

Damit ist die Konfiguration schon abgeschlossen (Firewall ggf. nicht vergessen!). Also testen….

Ein einfaches Tool dafür ist dog, oder natürlich dig aus den bind-tools aber Version 9.18. Für bind gibt es dann die Optionen +https oder auch +tls

dig +https @dns.kernel-error.de www.kernel-error.de A
dig +tls @dns.kernel-error.de www.kernel-error.de A

Der gleiche Test mit dog, sieht wie folgt aus:

dog www.kernel-error.de --tls "@dns.kernel-error.de"
A www.kernel-error.de. 6h00m00s   148.251.40.23
dog www.kernel-error.de --https "@https://dns.kernel-error.de/dns-query"
A www.kernel-error.de. 6h00m00s   148.251.40.23

Das war es auch schon! Viele Spaß mit einem „besseren“ DNS und wenn es noch Fragen gibt, einfach fragen.

Kodi auf dem Raspberry Pi 4: Ruckelfreie Wiedergabe einrichten

Der Raspberry Pi 4 , egal ob mit 4GB oder 8GB RAM, ist in der Kombination mit Kodi eine wunderbare Erweiterung am Fernseher. Leider sorgte die letzte Version Kodi v19.3 (Matrix) bei mir für ein paar Problemchen. So stockte oder ruckelte die Wiedergabe von Videos oder die Wiedergabe lief für einige Minuten gut, dann wurde gebuffert, nur damit sich dieses Spielchen alle paar Minuten wiederholte. Egal ob im WLAN oder direkt am LAN.

Folgende Änderungen haben bei mir für eine Lösung der Probleme gesorgt:

  1. Erstellen einer XML Datei, welche die default Einstellungen des Cachings überschreibt.

    Speicherort und Dateiname ist: /storage/.kodi/userdata/advancedsettings.xml
<?xml version="1.0" encoding="utf-8"?>
<advancedsettings>
        <cache>
                <memorysize>524288000</memorysize>
                <buffermode>1</buffermode>
                <readfactor>6</readfactor>
        </cache>
</advancedsettings>

Achtung… Bei XML Dateien, spielt das richtige „Einrücken“ schon mal eine Rolle.

2. Erweitern des Arbeitsspeichers für die GPU, sowie das Erzwingen des „Turbo“ Modus.
Dafür einfach die Datei /flash/config.txt um folgende Zeilen erweitern/einpassen:

# Default GPU memory split, 76MB are needed for H264 decoder
gpu_mem=256
force_turbo=1

Wer dieses gerne per SSH machen möchte, muss das Volume /flash einmal schreibfähig mounten:

mount -o remount,rw /flash

Die Option gpu_mem setzt recht einfach den, für die Grafikkarte, reservierten Arbeitsspeicher fest auf 256MB. Dieses macht selbst bei der 4GB Raspberry PI 4 Version kein Problem.

force_turbo deaktiviert das dynamische, lastabhängige takten der CPU, GPU und des Arbeitsspeichers, sowie der Spannungen. Alles läuft daher auf Maximum, aber ohne zu übertakten. Dieses hat weniger Auswirkungen auf die Probleme bei der Wiedergabe, sorgt aber für ein allgemein „flüssigeres“ Verhalten. Dafür steigt die Stromaufnahme und die Temperatur. Da wir hier über einen Raspberry sprechen, ist es wohl für die Meisten zu vernachlässigen.

3. Um Temperatur und Geräuschpegel im Zaum zu halten, empfiehlt sich ein gutes passiv gekühltes Gehäuse. Folgendes kann ich empfehlen: https://amzn.to/3qF61pe

Das mitgelieferte Netzteil hat ausreichend Power, man kommt noch an „alles“ ran, das Gehäuse ist sehr massiv und selbst bei großer Last/langem Betrieb, wird alles nur handwarm.


Update Februar 2026 — Kodi 21 (Omega) / LibreELEC 12.x

Seit Kodi 21 (Omega), das mit LibreELEC 12.x ausgeliefert wird, funktioniert die oben beschriebene Cache-Konfiguration über die advancedsettings.xml nicht mehr korrekt. Die <cache> Sektion wird zwar noch eingelesen und im Log angezeigt — die tatsächlich aktiven Werte kommen aber aus den GUI-Settings (guisettings.xml). Im Kodi-Log erkennt man das an dieser Zeile:

New Cache GUI Settings (replacement of cache in advancedsettings.xml) are:
    Buffer Mode: 4
    Memory Size: 20 MB
    Read Factor: 4.00 x

Das bedeutet: Trotz konfigurierter 500 MB in der advancedsettings.xml läuft Kodi mit nur 20 MB Puffer — dem Default. Nicht gerade ideal.

Die neue Methode

Die Cache-Werte müssen jetzt direkt in /storage/.kodi/userdata/guisettings.xml gesetzt werden. Dafür Kodi stoppen, Datei bearbeiten, Kodi starten:

systemctl stop kodi
sleep 3

In der guisettings.xml diese drei Zeilen suchen und anpassen. Wichtig: Das default="true" muss entfernt werden, damit Kodi die Werte als benutzerdefiniert erkennt.

Vorher:

<setting id="filecache.buffermode" default="true">4</setting>
<setting id="filecache.memorysize" default="true">20</setting>
<setting id="filecache.readfactor" default="true">400</setting>

Nachher (Beispiel für 8 GB RAM):

<setting id="filecache.buffermode">1</setting>
<setting id="filecache.memorysize">500</setting>
<setting id="filecache.readfactor">600</setting>

Dann Kodi wieder starten:

systemctl start kodi

Was die Werte bedeuten

buffermode = 1

Legt fest, welche Quellen gepuffert werden:

WertBedeutung
0Nur Internet-Streams (HTTP, FTP…)
1Alles (Internet + LAN + lokal) ← empfohlen
2Nur „echte“ Internet-Streams
3Kein Puffer
4Alle Netzwerk-Quellen (Default in Kodi 21)

Wir setzen 1 statt 4, damit NFS-Quellen garantiert gepuffert werden — egal wie Kodi die Quelle intern klassifiziert.

memorysize = 500 (bzw. 250)

Die Größe des Puffers in MB. Das ist der Speicher, den Kodi im RAM reserviert, um Film-Daten vorauszulesen.

Praktisches Beispiel: Ein typischer 4K-Film hat ~80 Mbit/s Bitrate (ca. 10 MB/s).

  • 20 MB (Default): Nur ~2 Sekunden Film im Puffer. Wenn das Netzwerk kurz schwankt, stockt die Wiedergabe sofort.
  • 500 MB: Ca. 50 Sekunden Film im Puffer. Selbst wenn NFS mehrere Sekunden hängt, läuft die Wiedergabe weiter.

Empfohlene Werte nach verfügbarem RAM:

  • 8 GB RAM: memorysize = 500
  • 4 GB RAM: memorysize = 250

readfactor = 600 (= 6×)

Der Wert wird intern durch 100 geteilt, also 600 = 6,0×. Kodi liest Daten mit der 6-fachen Geschwindigkeit der benötigten Bitrate voraus. Bei einem 80 Mbit/s Film liest Kodi also mit ~480 Mbit/s vom NFS, bis der Puffer voll ist. Danach drosselt es auf die tatsächlich benötigte Rate. Das sorgt dafür, dass der Puffer sich schnell füllt und möglichst voll bleibt.

Verifizierung

Nach dem Neustart im Kodi-Log prüfen, ob die neuen Werte aktiv sind:

grep "New Cache GUI Settings" -A4 /storage/.kodi/temp/kodi.log

Hinweis zum Raspberry Pi 5

Die config.txt Anpassungen (gpu_mem=256 und force_turbo=1) gelten weiterhin für den Raspberry Pi 4. Beim Raspberry Pi 5 sind diese nicht nötig — er nutzt eine andere GPU-Architektur (VideoCore VII) und gpu_mem hat dort keine Wirkung.

Jetzt mit HTTP/3 und QUIC: Schnelleres Surfen leicht gemacht

Von QUIC habt ihr sicher alle schon gehört, seit knapp Mitte 2021 ist dieser neue Standard fertig und in einem recht einfach zu merkendem RFC 9000 beschrieben.

Im Grunde geht es darum, HTTP-Verbindungen schneller zu machen und dabei sogar UDP zum Einsatz zu bringen. Nicht ganz korrekt ist es einfach eine Weiterentwicklung von SPDY.

Um zu testen, ob eine Webseite bereits HTTP/3 also QUIC unterstützt, kann ich euch http3check.net ans Herz legen. Diese gibt, wenn gewünscht, sogar noch ein paar Detailinformationen aus.

Wer sehen möchte, ob sein Browser QUIC „macht“, kann auch nginx.org nutzen. Steht oben „Congratulations! You’re connected over QUIC.“ Dann ist man ein Gewinner.

Die Konfiguration am Nginx ist wie immer sehr einfach und ein sehr gutes Beispiel findet sich direkt von nginx.

Mein Nginx spricht dieses nun ebenfalls, mal sehen ob es Probleme gibt.


Update März 2026: Drei Jahre HTTP/3 im Betrieb

Es sind jetzt gut drei Jahre vergangen und ich kann sagen: Es gab keine Probleme. Kein einziges. HTTP/3 läuft hier seit 2022 auf allen vHosts und ich habe nie einen Fehler gesehen, der auf QUIC zurückzuführen war. Auch in den Logfiles nichts Auffälliges.

Illustration zu HTTP/3 und QUIC: schneller Web-Transport über UDP mit moderner Verschlüsselung und Browser-Support

Was sich geändert hat: Damals war HTTP/3 in Nginx noch experimentell und brauchte einen separaten Build mit dem quiche-Patch oder BoringSSL. Seit Nginx 1.25.0 (Mai 2023) ist HTTP/3 offiziell im Mainline-Branch enthalten und wird mit dem normalen --with-http_v3_module Build-Flag aktiviert. Kein Patch mehr, kein BoringSSL mehr, einfach OpenSSL 3.x und fertig. Mein aktueller Stack: Nginx 1.29.4 mit OpenSSL 3.5.4 auf FreeBSD 15.

Was bringt HTTP/3 in der Praxis?

Der größte Vorteil von QUIC gegenüber TCP ist die Verbindungsaufbauzeit. Bei TCP+TLS braucht ihr mindestens zwei Roundtrips, bevor Daten fließen (TCP Handshake + TLS Handshake). QUIC macht das in einem einzigen Roundtrip. Bei einem Wiederverbindungsversuch sogar in null Roundtrips (0-RTT).

Auf einer Glasfaserleitung mit 5 ms Latenz merkt ihr das kaum. Aber auf einem Smartphone im Zug mit 80 ms Latenz und gelegentlichem Paketverlust macht das einen spürbaren Unterschied. Dazu kommt, dass QUIC auf UDP basiert und damit das Head-of-Line-Blocking Problem von TCP löst: Ein verlorenes Paket blockiert nicht mehr alle Streams, sondern nur den einen betroffenen.

Konfiguration 2026

Die Konfiguration hat sich seit 2022 etwas verändert. Hier mein aktuelles Setup für den Blog-vHost:

server {
    listen [::]:443 ssl;
    listen [::]:443 quic;

    http2 on;

    server_name  www.kernel-error.de;

    # TLS (wird per include eingebunden)
    include tls-default.conf;
    ssl_certificate      /path/to/fullchain.pem;
    ssl_certificate_key  /path/to/privkey.pem;

    # ...
}

Wichtig sind zwei Dinge. Erstens: Der quic Listener läuft auf demselben Port 443 wie der SSL-Listener, nur eben über UDP statt TCP. Zweitens: Die Clients müssen wissen, dass HTTP/3 verfügbar ist. Das passiert über den Alt-Svc Header:

add_header Alt-Svc 'h3=":443"; ma=86400' always;

Dieser Header sagt dem Browser: „Ich spreche auch h3 auf Port 443, merk dir das für 24 Stunden.“ Beim nächsten Besuch nutzt der Browser dann direkt QUIC. Ohne diesen Header bleibt alles bei HTTP/2 über TCP.

Optional könnt ihr auch einen HTTPS-DNS-Record (SVCB) setzen, damit der Browser schon beim DNS-Lookup weiß, dass HTTP/3 verfügbar ist:

$ dig +short HTTPS www.kernel-error.de
1 . alpn="h3,h2" ipv4hint=148.251.30.200 ipv6hint=2a01:4f8:262:4716::443

Mit alpn="h3,h2" im HTTPS-Record kann der Browser die QUIC-Verbindung schon beim allerersten Besuch aufbauen, ohne erst auf den Alt-Svc Header warten zu müssen.

Firewall nicht vergessen

Ein Klassiker, der mich 2022 kurz stolpern ließ: QUIC braucht UDP Port 443. Wenn eure Firewall nur TCP 443 durchlässt, sehen die Clients den Alt-Svc Header, versuchen QUIC und laufen ins Timeout. Auf FreeBSD mit pf:

pass in quick on $ext_if proto udp to $jail_nginx port 443

Post-Quantum-Kryptografie inklusive

QUIC verwendet intern TLS 1.3 für die Verschlüsselung. Das heißt: Wenn ihr in eurer Nginx-TLS-Konfiguration X25519MLKEM768 als Key-Exchange-Gruppe konfiguriert habt, gilt das automatisch auch für QUIC-Verbindungen. Kein extra Aufwand. Euer HTTP/3 Traffic ist dann ebenfalls mit hybridem Post-Quantum-Schlüsselaustausch abgesichert.

Browser-Support

2022 war HTTP/3 noch ein Feature für Early Adopter. 2026 ist es Standard. Chrome, Firefox, Safari und Edge unterstützen QUIC seit Jahren. Laut den Logfiles dieses Blogs nutzen inzwischen gut 40% der Besucher HTTP/3. Tendenz steigend, weil immer mehr Mobilgeräte von dem schnelleren Verbindungsaufbau profitieren.

Wer es noch nicht aktiviert hat: Der Aufwand ist minimal, die Vorteile real und das Risiko gleich null. Drei Jahre Betrieb ohne ein einziges Problem sprechen für sich.

Siehe auch: HTTPS RR und SVCB Records — per DNS-Record signalisieren, dass HTTP/3 verfügbar ist. Damit können Clients direkt mit QUIC starten, ohne vorher TCP zu probieren.

Wechsel zur WordPress

Ich nutze Joomla seit 2006, als es aus Mambo hervorging. Die Datenbank und einige Plugins/Teils sind inzwischen also sehr alt und machen bei Versionssprüngen immer mehr Probleme. Den Wechsel vom CMS habe ich dennoch immer von mir weggeschoben, denn es macht viel Arbeit und für eine solch kleine Spielerei lohnt der Aufwand kaum. Da ich es inzwischen eh nur noch für einfache Blogeinträge nutze… Nun ich werde wohl in der nächsten Zeit auf WordPress wechseln. Damit wird es ebenfalls einen neuen RSS Feed geben und sicher wird ebenfalls viel Content verschwinden.

Fragen? Einfach melden.

MikroTik CRS305-1G-4S+IN: Der 10-Gbit-Switch für 130 Euro im Langzeittest

Den MikroTik CRS305-1G-4S+IN habe ich Ende 2020 in meinem Arbeitszimmer in Betrieb genommen. Workstation, Storage, Hauptswitch und Windows-PC hängen seitdem an seinen vier SFP+ Ports. Fünf Jahre später läuft das Teil immer noch ohne Ausfall und ich denke es ist Zeit für ein ehrliches Update.

Damals lag der Preis bei knapp über 100 Euro, aktuell zeigt mir Amazon 131,62 Euro an. Klingt nach Inflation, ist für einen managebaren 4-Port 10-Gbit-Switch trotzdem immer noch ein Witz.

Was bekommt man fürs Geld?

An der Hardware hat sich seit dem Erstkauf nichts geändert: Vier SFP+ Ports mit 10 Gbit/s, dazu ein Gigabit-Port für Out-of-Band-Management. Komplett lüfterlos, das Metallgehäuse dient als Kühlkörper. Stromversorgung wahlweise per Steckernetzteil, über den zweiten Netzteil-Anschluss redundant oder per PoE-In über den Management-Port. Wer es ernst meint mit Hochverfügbarkeit, packt drei verschiedene Quellen drauf.

Auf der Unterseite sind Bohrungen für die Wandmontage. Bei mir hängt der Switch seit Jahren an genau dieser Stelle.

RouterOS, jetzt in Version 7

Auf dem Gerät läuft echtes RouterOS, kein abgespecktes SwitchOS-Light. Vor ein paar Jahren bin ich von Version 6 auf 7 umgestiegen, der Sprung lief schmerzfreier als befürchtet. Container-Support, eine neue Routing-Engine, in-kernel WireGuard, ROSE-storage. Für den CRS305 in reiner Switch-Funktion ist das meiste davon Overkill, aber der gleiche Software-Stack läuft auf allen MikroTik-Geräten und das einmal gelernte Wissen ist übertragbar.

Wer den Switch wirklich nur als Switch braucht und das volle Routing-Featureset nicht will, kann alternativ SwOS einspielen. Ich bleibe bei RouterOS, weil ich VLANs, VRRP und gelegentlich mal eine kleine Bridge mit eigenen Regeln nutze.

Härtung: das was 2021 noch keiner gesagt hat

In den letzten Jahren ist MikroTik mehrfach unangenehm aufgefallen. Botnetze auf nicht aktualisierten RouterOS-Geräten (Mēris, TrickBot, VPNFilter), mehrere Webfig-Authentifizierungs-Bypässe, dazu der Klassiker: Default-User „admin“ ohne Passwort. Wer so ein Gerät ohne Härtung ans Internet hängt oder auch nur ohne Trennung ins LAN, hat sich selbst beschenkt.

Mein Standardvorgehen direkt nach dem Auspacken sieht ungefähr so aus:

/user add name=adminneu group=full password=...
/user remove admin
/ip service disable telnet,ftp,www,api,api-ssl
/ip service set winbox address=192.168.X.0/24
/ip service set ssh address=192.168.X.0/24
/system clock set time-zone-name=Europe/Berlin
/system ntp client set enabled=yes servers=pool.ntp.org
/system package update check-for-updates
/system routerboard upgrade

Webfig und API mögen bequem sein, ich brauche beides nicht. Telnet und FTP haben auf einem Gerät von 2026 nichts mehr verloren. Wer trotzdem das Webinterface nutzen will, sollte zumindest auf HTTPS umstellen und die Zugriffe auf das Management-Subnetz beschränken.

Auto-Update gibt es bei MikroTik leider noch immer nicht out-of-the-box. Ich habe mir einen kleinen Cron-Job gebaut, der einmal im Monat anpingt ob ein Update da ist. Manuell einspielen muss ich es dann selbst, weil mir bei einem zentralen Netzwerkgerät ein automatisches Reboot mitten in der Nacht zu heikel ist.

SFP+ Module und DAC-Kabel

MikroTik ist da angenehm tolerant: vendor-locked Module von Cisco, Juniper oder HPE fliegen in der Regel ohne Murren rein. In meinen vier Ports stecken aktuell zwei FS.com-Module mit LWL, ein generisches Kupfer-DAC zur Workstation und ein 10GBASE-T-Adapter ans Storage. Alles funktioniert, kein Kabel oder Modul zickt.

Vorsicht bei 10GBASE-T-Adaptern: die werden warm. Sehr warm. Bei zwei oder mehr Stück im engen Gehäuse kann der Switch im Sommer am thermischen Limit kratzen. Wer kann, sollte LWL oder DAC bevorzugen, das ist effizienter und kühler. DACs sparen außerdem die Modul-Kosten und bringen Latenzen, die mit aktiven Modulen schlicht nicht zu erreichen sind.

Stromverbrauch

Bei mir liegt der Verbrauch im Mittel zwischen 8 und 12 Watt, je nach Bestückung. Mit zwei 10GBASE-T-Adaptern kann es Richtung 14 bis 16 Watt gehen. Für einen 24/7 laufenden Switch ist das vertretbar, in Zeiten von Stromrechnungen die jedes Jahr neue Höchststände erreichen sollte man es zumindest wissen.

Markt 2026: Alternativen

2021 war der CRS305 in seiner Preisklasse fast konkurrenzlos. 2026 sieht das anders aus. Ein paar Optionen, die ich heute mit auf die Liste setzen würde:

  • MikroTik CRS309-1G-8S+IN: Doppelte Portzahl, gleicher Aufbau, etwa 250 bis 300 Euro. Wenn vier Ports knapp werden, der logische Schritt nach oben.
  • MikroTik CRS310-1G-5S-4S+IN: Mischmasch aus 1G/2.5G und 4× SFP+, brauchbar wenn das eigene Netz nicht komplett auf 10G migriert ist.
  • TP-Link TL-SX3008F: 8× SFP+ JetStream-Smart-Switch, kein RouterOS aber gepflegtes Webinterface, etwa 250 Euro. Für reines Switchen oft entspannter als RouterOS.
  • QNAP QSW-M408S: 4× SFP+ plus 4× 1G, simples Web-UI, knapp 280 Euro. Gut für Leute, die kein RouterOS lernen wollen.
  • Ubiquiti USW-Aggregation: 8× SFP+ mit Web-Controller, etwa 280 Euro. Wer schon im UniFi-Ökosystem unterwegs ist, will sowieso nichts anderes.

Für den absoluten Einstieg in 10G zuhause, mit kleinem Budget und der Bereitschaft sich kurz mit RouterOS zu beschäftigen, ist der CRS305 weiterhin der beste Deal. Wer mehr Ports braucht oder das Routing-Featureset gar nicht erst anfassen will, sollte einen der oben genannten in die engere Wahl nehmen.

Fazit nach 5 Jahren

Das Gerät ist seit 2021 ohne einen einzigen Ausfall durchgelaufen, hat zwei Wohnungswechsel und mehrere RouterOS-Major-Updates überlebt. Bei aktuell 131,62 Euro auf Amazon oder direkt von MikroTik bleibt es eine klare Empfehlung. Mit der Einschränkung, dass man die paar Minuten in Härtung investieren sollte, sonst wird aus dem schönen kleinen Switch schnell ein Botnet-Knoten.

Ich mache ja eher selten Werbung für ein Produkt, aber dieser Switch hat sich nach fünf Jahren als die beste Empfehlung herausgestellt, die ich in dieser Preisklasse jemals geben konnte.

Siehe auch: IPv6 Prefix Delegation: FritzBox und MikroTik und Ist mein Netzwerk kompromittiert? Warum das kaum jemand merkt.

Fragen? Einfach melden.

Rspamd: Automatisches Spam/Ham-Lernen mit Dovecot und IMAPSieve

Rspamd hat ein Webinterface. Da kann man E-Mails reinkopieren und als Spam oder Ham markieren. Klingt erstmal praktisch. Ist es aber nicht. Niemand kopiert ernsthaft den Quellcode jeder fehlklassifizierten Mail in ein Webformular. Das macht man einmal zum Testen und dann nie wieder.

Automatisches Spam-Training mit Rspamd über Dovecot IMAPSieve – Mail wird zwischen Inbox und Junk verschoben

Was man eigentlich will: Wenn ein Benutzer eine Mail in den Junk-Ordner verschiebt, soll rspamd das automatisch als Spam lernen. Und wenn eine Mail aus dem Junk-Ordner rausgeholt wird, soll rspamd sie als Ham lernen. Kein Webinterface, kein manueller Eingriff. Der Benutzer sortiert einfach seine Mails — und rspamd lernt mit.

Genau das geht mit Dovecot und IMAPSieve. Hier beschreibe ich, wie ich das bei mir eingerichtet habe. Die Konfiguration läuft seit Mai 2020 unverändert — über sechs Jahre, ohne eine einzige Anpassung. Das darf man ruhig als stabil bezeichnen.

Was passiert da eigentlich

Der Datenfluss ist simpel:

  • Benutzer verschiebt eine Mail in den Ordner „Junk“
  • Dovecot erkennt die Verschiebung per IMAPSieve
  • IMAPSieve startet ein Sieve-Script
  • Das Sieve-Script ruft ein Shell-Script auf
  • Das Shell-Script übergibt die Mail per rspamc an rspamd
  • Rspamd lernt die Mail als Spam (Bayes-Klassifikator)

In die andere Richtung genauso: Mail raus aus Junk, Dovecot erkennt es, rspamd lernt Ham. Egal ob der Benutzer über Thunderbird, Roundcube, ein Smartphone oder was auch immer sortiert — solange es IMAP ist, greift das.

Voraussetzungen

  • Dovecot mit Sieve-Support (dovecot-pigeonhole unter FreeBSD, dovecot-sieve unter Debian/Ubuntu)
  • Rspamd mit laufendem Controller-Worker
  • rspamc CLI-Tool (kommt mit rspamd mit)

Mein Setup läuft auf FreeBSD. Die Pfade beginnen daher mit /usr/local/. Unter Linux ist es /etc/dovecot/ statt /usr/local/etc/dovecot/ und /usr/lib/dovecot/ statt /usr/local/libexec/dovecot/. Ansonsten ist alles identisch.

Mein rspamd läuft in einer eigenen Jail und lauscht auf 127.0.0.3:11334. Wer rspamd lokal auf dem gleichen System hat, nimmt stattdessen 127.0.0.1:11334 oder den Unix-Socket.

Dovecot konfigurieren

Zuerst muss das Sieve-Plugin für IMAP aktiviert werden.

20-imap.conf:

protocol imap {
  mail_plugins = $mail_plugins sieve
}

Dann die IMAPSieve-Konfiguration. Hier wird festgelegt, welche Ordner-Aktionen welches Sieve-Script auslösen.

90-plugin.conf:

plugin {
  sieve_plugins = sieve_imapsieve sieve_extprograms

  # Wenn eine Mail in den Junk-Ordner kopiert oder dort ein Flag geaendert wird
  imapsieve_mailbox1_name = Junk
  imapsieve_mailbox1_causes = COPY FLAG
  imapsieve_mailbox1_before = file:/usr/local/etc/dovecot/sieve/report-spam.sieve

  # Wenn eine Mail AUS dem Junk-Ordner woanders hin verschoben wird
  imapsieve_mailbox2_name = *
  imapsieve_mailbox2_from = Junk
  imapsieve_mailbox2_causes = COPY
  imapsieve_mailbox2_before = file:/usr/local/etc/dovecot/sieve/report-ham.sieve

  sieve_pipe_bin_dir = /usr/local/libexec/dovecot

  sieve_global_extensions = +vnd.dovecot.pipe
}

Zwei Trigger: Einer für „Mail landet im Junk“ (→ Spam lernen), einer für „Mail verlässt Junk“ (→ Ham lernen). COPY deckt Verschieben ab, FLAG fängt den Fall ab, dass ein Mail-Client den Junk-Status per Flag statt per Verschieben setzt.

Sieve-Scripts

Jetzt die beiden Sieve-Scripts, die von IMAPSieve aufgerufen werden.

report-spam.sieve — wird ausgelöst, wenn eine Mail im Junk-Ordner landet:

require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "imap4flags"];

if environment :is "imap.cause" "COPY" {
    pipe :copy "sa-learn-spam.sh";
}

# Beantworteten oder weitergeleiteten Spam ebenfalls lernen
elsif anyof (allof (hasflag "\\Answered",
                    environment :contains "imap.changedflags" "\\Answered"),
             allof (hasflag "$Forwarded",
                    environment :contains "imap.changedflags" "$Forwarded")) {
    pipe :copy "sa-learn-spam.sh";
}

Der erste Block fängt das normale Verschieben ab. Der zweite Block ist für einen Sonderfall: Wenn jemand auf eine Mail im Junk-Ordner antwortet oder sie weiterleitet, ändert sich das Flag — und auch das sollte als Spam gelernt werden.

report-ham.sieve — wird ausgelöst, wenn eine Mail den Junk-Ordner verlässt:

require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];

if environment :matches "imap.mailbox" "*" {
  set "mailbox" "${1}";
}

if string "${mailbox}" [ "Trash", "train_ham", "train_prob", "train_spam" ] {
  stop;
}

pipe :copy "sa-learn-ham.sh";

Hier passiert etwas Wichtiges: Bevor die Mail als Ham gelernt wird, prüfen wir wohin sie verschoben wurde. Wenn sie im Papierkorb landet, war das vermutlich kein „Das ist kein Spam“ sondern ein „Ich lösche den Spam“. Deshalb: stop; für Trash und die Trainingsordner. Nur wenn die Mail in einen echten Ordner verschoben wird, ist es ein Ham-Signal.

Beide Scripts müssen kompiliert werden:

sievec /usr/local/etc/dovecot/sieve/report-spam.sieve
sievec /usr/local/etc/dovecot/sieve/report-ham.sieve

Shell-Scripts für rspamc

Die Sieve-Scripts rufen Shell-Scripts auf, die die Mail per rspamc an rspamd übergeben. Simpel — jeweils ein Einzeiler.

/usr/local/libexec/dovecot/sa-learn-spam.sh:

#!/bin/sh
exec /usr/local/bin/rspamc -h 127.0.0.3:11334 learn_spam

/usr/local/libexec/dovecot/sa-learn-ham.sh:

#!/bin/sh
exec /usr/local/bin/rspamc -h 127.0.0.3:11334 learn_ham

Die Dateinamen sa-learn-* kommen historisch von SpamAssassin. Verwirrend, wenn man rspamd nutzt. Man könnte sie auch rspamd-learn-spam.sh nennen — funktional ist es egal. Ich habe sie so gelassen, weil man funktionierende Dinge nicht anfasst.

Beide ausführbar machen:

chmod +x /usr/local/libexec/dovecot/sa-learn-spam.sh /usr/local/libexec/dovecot/sa-learn-ham.sh

Wer rspamd lokal laufen hat, ersetzt 127.0.0.3 durch 127.0.0.1 oder nutzt den Unix-Socket (-h /var/run/rspamd/rspamd.sock). Unter Linux liegen die Scripts in /usr/lib/dovecot/ statt /usr/local/libexec/dovecot/. Der Pfad in sieve_pipe_bin_dir muss natürlich dazu passen.

Wichtig: Damit rspamc ohne Passwort trainieren darf, muss die IP im rspamd Controller-Worker als vertrauenswürdig eingetragen sein. In /usr/local/etc/rspamd/local.d/worker-controller.inc (FreeBSD) bzw. /etc/rspamd/local.d/worker-controller.inc (Linux):

secure_ip = "127.0.0.0/8";
secure_ip = "::1";

Ohne das schlägt rspamc learn_spam mit einem Authentifizierungsfehler fehl. Bei Jail-Setups wie meinem muss die Jail-IP (127.0.0.3) in der Liste stehen.

Testen

Dovecot neu laden:

service dovecot reload

Dann eine beliebige Mail in den Junk-Ordner verschieben und im rspamd-Log nachschauen:

rspamd_controller_learn_fin_task: <127.0.0.3> learned message as spam: MESSAGE-ID

Mail wieder raus aus Junk in den Posteingang:

rspamd_controller_learn_fin_task: <127.0.0.3> learned message as ham: MESSAGE-ID

Wenn das im Log steht, funktioniert alles. Kein Neustart nötig, kein Cache-Flush, kein Warten.

Wie viel Training braucht rspamd

Rspamd nutzt einen Bayes-Klassifikator. Der braucht eine Mindestmenge an gelernten Nachrichten, bevor er aktiv wird. Die Standardeinstellung ist 200 — also mindestens 200 Spam-Mails und 200 Ham-Mails. Vorher ignoriert rspamd die Bayes-Ergebnisse komplett.

Das klingt nach viel, geht aber schneller als man denkt. Wer ein paar Dutzend Benutzer auf dem Server hat, kommt da in wenigen Wochen hin. Und danach wird rspamd mit jeder sortierten Mail ein bisschen besser.

Den aktuellen Stand kann man jederzeit prüfen:

rspamc stat

Unter Statfile sieht man wie viele Nachrichten rspamd bereits gelernt hat.

Rspamd trainiert standardmäßig einen globalen Bayes-Klassifikator — alle Benutzer lernen in denselben Pool. Wer das pro Benutzer trennen will, setzt in der classifier-bayes.conf:

per_user = true;

Für die meisten Setups mit einer Handvoll Domains ist der globale Pool sinnvoller — mehr Trainingsdaten, schneller gute Ergebnisse.

Hinweise

Die Konfiguration ist stabil — Dovecot-Updates, rspamd-Updates, FreeBSD-Upgrades, alles durchgelaufen ohne Anpassung.

Wer rspamd danach noch eine Stufe weiter bringen will: Ich habe einen eigenen Beitrag geschrieben, wie man GPT-basierte Spam-Erkennung in rspamd integriert. Das läuft zusätzlich zum Bayes-Klassifikator und fängt die Mails ab, die durch das statistische Netz rutschen.

Siehe auch: Postfix und AMaViS: content_filter oder smtpd_proxy_filter?, DMARC-Prüfung in Postfix: OpenDMARC und rspamd im Vergleich

Fragen? Schreib mir über die Kontaktseite.

Von RSA zu ECDSA: Zertifikate für Nginx und Postfix umstellen

ECDSA-Zertifikate (Elliptic Curve Digital Signature Algorithm) bieten bei 256 Bit das gleiche Sicherheitsniveau wie RSA mit 3072 Bit. Die Schlüssel sind deutlich kleiner, der TLS-Handshake ist schneller und die Signaturen kürzer. Für Webserver ist die Umstellung trivial. Bei Mailservern gibt es eine Besonderheit: Nicht alle sendenden Server können ECDSA. Postfix löst das elegant mit Dual-Zertifikaten.

ECDSA-Schlüssel erstellen

# EC-Schlüssel mit P-256 erzeugen
openssl ecparam -genkey -name prime256v1 | openssl ec -out ec-server.key

# CSR erstellen (für CA-signierte Zertifikate)
openssl req -new -key ec-server.key -out ec-server.csr

# Oder gleich ein selbstsigniertes Zertifikat (z.B. für Tests)
openssl req -new -x509 -key ec-server.key -out ec-server.pem -days 365

P-256 (prime256v1) ist die gängige Kurve. Let’s Encrypt, DigiCert und andere CAs signieren ECDSA-CSRs problemlos. Bei Let’s Encrypt/certbot: certbot certonly --key-type ecdsa.

Nginx

Beim Webserver einfach den neuen Schlüssel und das Zertifikat hinterlegen. An der Cipher-Konfiguration muss nichts geändert werden, solange ECDSA-Ciphers enthalten sind:

ssl_certificate     /path/to/ec-server.pem;
ssl_certificate_key /path/to/ec-server.key;
ssl_ciphers         TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256;

Alle modernen Browser unterstützen ECDSA seit Jahren. Probleme gibt es nur noch mit sehr alten Clients (Android 2.x, IE 6). Wer die nicht bedienen muss, kann RSA komplett aus der Cipher-Liste streichen.

Postfix: Dual-Zertifikate

Bei E-Mail sieht es anders aus. Manche Mailserver (ältere Exchange-Installationen, schlecht gewartete Systeme) können kein ECDSA. Postfix bietet dafür eine saubere Lösung: Man hinterlegt sowohl ein EC-Zertifikat als auch ein RSA-Zertifikat. Der Server bietet dem Client beide an, der Client wählt das passende.

# ECDSA (bevorzugt)
smtpd_tls_eckey_file = /usr/local/etc/postfix/ec-postfix.key
smtpd_tls_eccert_file = /usr/local/etc/postfix/ec-postfix.pem

# RSA (Fallback)
smtpd_tls_key_file = /usr/local/etc/postfix/postfix.key
smtpd_tls_cert_file = /usr/local/etc/postfix/postfix.pem

Die Cipher-Reihenfolge entscheidet, was bevorzugt wird. ECDSA-Ciphers sollten vor den RSA-Ciphers stehen. Mit tls_preempt_cipherlist = yes bestimmt der Server die Reihenfolge:

tls_preempt_cipherlist = yes
tls_high_cipherlist = TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256

Die TLS-1.3-Ciphers (TLS_AES_*) stehen ganz vorn. Danach kommen ECDHE-ECDSA für TLS 1.2 mit EC-Zertifikat, dann ECDHE-RSA als Fallback für Clients die kein ECDSA können. Alle Ciphers haben Perfect Forward Secrecy durch ECDHE.

Dovecot

Dovecot unterstützt seit Version 2.3.15 ebenfalls mehrere Zertifikate. In 10-ssl.conf:

ssl_cert = </path/to/ec-dovecot.pem
ssl_key = </path/to/ec-dovecot.key
ssl_alt_cert = </path/to/rsa-dovecot.pem
ssl_alt_key = </path/to/rsa-dovecot.key

Verifizieren

Im Postfix-Log erkennt man am Cipher, welches Zertifikat verwendet wurde:

# ECDSA-Verbindung
TLS connection established: TLSv1.3 with cipher TLS_AES_256_GCM_SHA384

# RSA-Fallback (TLS 1.2)
TLS connection established: TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384

Mit openssl s_client lässt sich gezielt prüfen:

# ECDSA-Zertifikat anfordern
openssl s_client -starttls smtp -connect smtp.kernel-error.de:25 2>/dev/null | grep "Server public key\|Cipher"

# Ergebnis: Server public key is 256 bit (EC)

Wer den nächsten Schritt gehen will: Mit Post-Quantum-Schlüsselaustausch (X25519MLKEM768) lässt sich zusätzlich der Key Exchange gegen Quantencomputer absichern. Das Zertifikat bleibt dabei ECDSA, nur der Schlüsselaustausch wird hybrid. Und natürlich gehört zu jedem Zertifikat ein DANE/TLSA-Record im DNS. Fragen? Einfach melden.

« Ältere Beiträge Neuere Beiträge »

© 2026 -=Kernel-Error=-RSS

Theme von Anders NorénHoch ↑