Was ist header_checks.pcre überhaupt?

header_checks ist eine Postfix-Funktion, mit der jede einzelne Header-Zeile einer Mail gegen eine Liste von regulären Ausdrücken geprüft wird. Passt eine Zeile auf ein Muster, führt Postfix die dazu hinterlegte Aktion aus. Die Endung .pcre sagt nur, welche Regex-Engine benutzt wird, Perl Compatible Regular Expressions, bereitgestellt vom Paket postfix-pcre.

Eingebunden wird die Datei in der main.cf:

header_checks = pcre:/etc/postfix/header_checks.pcre

Der Aufbau ist denkbar simpel – ein Muster, eine Aktion, optional ein Text:

/^From:.*@tradcosmos\.live/          REJECT 5.7.1 Spam detected ID-1123681
/^Subject:.*Ihre Rechnung Nr\. \d+/  REJECT 5.7.1 Spam detected ID-1123999
/^X-Mailer: EvilBulk/                DISCARD
/^Received:.*\[216\.189\.159\.207\]/ REJECT 5.7.1 Spam detected ID-1123681

Wichtig zum Verständnis: Die Prüfung passiert im cleanup-Daemon, also nachdem die Mail per DATA übertragen wurde, aber bevor sie in die Queue geht. Bei eingehendem SMTP bekommt der einliefernde Server damit noch eine saubere 5xx-Antwort auf das DATA-Kommando, die Mail wird also abgelehnt und nicht angenommen und später gebounct. Genau das ist der Punkt, an dem der Ablehnungsgrund beim Gegenüber im Log landet.

Was man damit machen kann

Die gängigen Aktionen:

Aktion Wirkung
REJECT <text> Mail ablehnen, Text geht als SMTP-Antwort an den Absender-Server
DISCARD Mail stillschweigend wegwerfen (Absender sieht ein "OK")
HOLD Mail in die Hold-Queue zur manuellen Prüfung
IGNORE Nur diese Header-Zeile entfernen, Mail durchlassen
WARN Nur ins Log schreiben, ideal zum Testen neuer Regeln
REPLACE / PREPEND Header ersetzen bzw. neuen Header voranstellen
FILTER / REDIRECT Umleiten an Content-Filter oder andere Adresse

Typische Einsätze: interne Header wie X-Originating-IP beim Ausgang strippen, Mails eines bekannten Bulk-Mailers ablehnen, Received-Ketten bestimmter Relays blocken, oder erst einmal alles mit WARN beobachten.

Drei Stolperfallen

  1. header_checks gilt für alle Mails, auch die eigenen ausgehenden. Wer nur eingehend filtern will, braucht einen separaten cleanup-Service in der master.cf mit eigener -o header_checks=-Option.
  2. Kein postmap nötig. PCRE- und Regexp-Tabellen werden zur Laufzeit gelesen, nicht kompiliert. Nach Änderungen reicht postfix reload.
  3. Jede Regel kostet CPU. Postfix testet jede logische Header-Zeile gegen jede Regel, der Reihe nach. Bei ein paar Dutzend Regeln egal, bei mehreren Tausend sollte man ein Auge auf die Laufzeit haben und Muster nach Trefferwahrscheinlichkeit sortieren.

Und genau da fängt das Problem an

Regeln schreiben ist einfach. Die richtigen Regeln schreiben ist es nicht. Bei vielen Spam-Mails wird die Pflege schnell aufwendig, und jede Mail einzeln auszuwerten kostet richtig Zeit. Für jede Mail will man wissen:

  • Was steht im From: und passt das zum Envelope-From bzw. Return-Path:?
  • Weicht Reply-To: vom From: ab?
  • Welcher Server hat tatsächlich zugestellt?
  • Ist dieser Server für die Domain überhaupt zuständig (SPF)?
  • Ist ein DKIM-Header vorhanden, und war die Signatur gültig?

Dazu kommt der Versand, der sich hinter fremder Infrastruktur tarnt: Google Groups, Amazon SES, SparkPost und Konsorten. Für die darf man bewusst keine Received-IP-Regel erzeugen, die IP gehört Google, Amazon oder SparkPost, und ein IP-Block würde massenhaft legitime Mails mitnehmen.

Ich habe das jahrelang manuell gemacht, wie vermutlich die meisten. Später mit Skripten. Aber selbst das bleibt aufwendig, weil es immer wieder Fälle gibt, die nicht eindeutig sind, oder die einen selbst nach all den Jahren Mailserver-Betrieb noch überraschen. Spammer und Phisher sind erschreckend einfallsreich, und offensichtlich verdienen sie damit Geld: Der Haufen an Domains, die Server und die ganze Infrastruktur wollen schließlich bezahlt werden. Selbst wenn es gekaperte Server und Accounts sind, steckt da eine Menge Arbeit, Zeit und am Ende auch Geld drin.

Das sollte man 2026 doch einfacher hinbekommen können. Warum nicht einfach eine Anweisung schreiben, die eine AI versteht, ausführt und daraus in kürzester Zeit Regex-Filter für Postfix plus eine Domain-Blocklist für rspamd erzeugt?

Der Task

Also so etwas hier:

Zweck

Nutzer verschieben Spam manuell in den Spam-Ordner, der Mailserver lernt daraus (Bayes). Trotzdem landen gleiche oder ähnliche Mails weiterhin in den Inboxen. Dieser Task wertet solche Mails headerbasiert aus und erzeugt daraus deterministische Blockregeln für Postfix und rspamd, als Vorschlag, nicht zum automatischen Ausrollen.

Ordnerstruktur

new/        Eingang: noch nicht ausgewertete Mails
done/       Archiv: fertig ausgewertete Mails
postfix/    header_checks.pcre-<YYYY-MM-dd-hh-mm-ss>
rspamd/     blacklist.sender.domain.map-<YYYY-MM-dd-hh-mm-ss>
reports/    spam-id-<NUMBER>.md  (intern, vollständig)
posts/      spam-id-<NUMBER>.md  (öffentlich, reduziert)
serial      laufende ID

Ablauf

  1. new/ prüfen. Keine Mails → Task beendet, Meldung „keine neuen Mails".
  2. Pro Mail einzeln:
    1. serial lesen, um 1 erhöhen, neuen Wert sofort zurückschreiben → das ist <NUMBER> für diese Mail.
    2. Header auswerten (siehe Analysekriterien).
    3. Regeln ableiten.
    4. Interner Report → reports/spam-id-<NUMBER>.md
    5. Öffentlicher Report → posts/spam-id-<NUMBER>.md
    6. Mail nach done/ verschieben.
  3. Nach Abarbeitung aller Mails: je eine neue kumulierte Regeldatei pro Zielsystem schreiben.

Analysekriterien (Header)

Auszuwerten sind mindestens:

  • From: (Header-From) vs. Envelope-From / Return-Path:
  • Reply-To: Abweichung zum From: ist ein starkes Signal
  • Received:-Kette, echter Einlieferungspfad, Relays, Group-/Listenserver
  • List-*, X-Google-Group-Id, Mailing-List, Missbrauch von Mailinglisten und Groups
  • Authentication-Results: SPF/DKIM/DMARC-Ergebnis
  • Subject: wiederkehrende Muster
  • Message-ID: Domain-Konsistenz zum From:

Sonderfälle

Dazu kommen kurz beschriebene Anweisungen zu den bekannten Sonderfällen, um Kollateralschäden zu vermeiden. Steht eine legitime, missbrauchte Domain im From: (Spoofing, kompromittiertes Group-Mitglied), wird diese Domain nicht geblockt. Geblockt wird der tatsächliche Zustellpfad: Group-Domain, Bounce-/Return-Path-Domain, Relay.

Reports

Für jede Mail, die geblockt wird und in den Filtern landet, entsteht ein ausführlicher interner Report. So weiß ich bei Rückfragen zu einem Block immer, warum, und kann es auch erklären.

Dazu ein kurzer öffentlicher Report. Dessen URL steht im REJECT-Text und landet damit im Mail-Log des einliefernden Servers, sodass ein Admin den Grund direkt nachlesen kann. Die Reports sind online unter der jeweiligen SPAM-ID abrufbar.

Bei missbrauchten Domains wird zusätzlich eine fertige Mail an den Betreiber der Website/Domain erzeugt, um ihn auf Spam und Phishing aufmerksam zu machen, je nach Befund mit einem Hinweis zu SPF, DKIM und Co. als kleine Hilfestellung.

Ausgabe

Damit das Ganze übersichtlich bleibt, gibt es die Anweisung, keine der erzeugten Dateien auszugeben, sondern sie nur im vorgegebenen Format am vorgegebenen Ort zu speichern. Das Einzige, was ich jedes Mal sehen will, ist ein kleiner Stats-Report: Anzahl der bearbeiteten Dateien, wie viele Regeln jeweils erstellt wurden und Sonderfälle oder unklare Mails, die ich besser noch einmal selbst prüfen sollte.

Was dabei herauskommt

Die Anweisungen knapp und sauber formuliert, den Ordner ./new mit den ersten Mails gefüttert, Analyse gestartet. Das kann AI wirklich gut, und sie behält über mehrere Sessions Informationen und gleicht sie ab. Mit der Zeit entstehen so neue Regeln, während bereits bekannte als Duplikate erkannt und nicht noch einmal aufgenommen werden.

Der eigentliche Gewinn sind aber die Muster: Was bei einer oder einer Handvoll Mails praktisch nicht auffällt, erkennt die AI über die Masse hinweg recht schnell und schlägt weitere Regeln vor. Und sie fragt nach, ob eine Regel so in Ordnung ist, oder ob man sie lieber erst einmal deaktiviert lässt.

Auf einmal sind eine Menge Mails ausgewertet und sehr viele Regeln erstellt:

6.917 Mails bearbeitet
7.735 aktive REJECT-Regeln
3.076 blockierte Absender-Domains

Bei dieser Größenordnung habe ich die Listen noch einmal ausführlich geprüft. Und sie sahen verdammt gut aus. Die bisherigen Regeln sind gesichert, die neuen aktiv, und man merkt sofort, wie ruhig es auf dem Mailserver geworden ist.

Der Blick ins mail.log bestätigt es: Rejects sind drin, und alle waren korrekt.

$ grep -i "Spam detected ID" /var/log/mail.log
...
2026-07-17T19:37:37 mail03 postfix/cleanup[2306654]: 5497640DB5: reject:
  header Received: from tradcosmos.live (tradcosmos.live [216.189.159.207])
  by mail01.9it.de (Postfix) with ESMTP id 5497640DB5 for <domain@9it.de>;
  from tradcosmos.live[216.189.159.207]; from=<> to=<domain@9it.de>
  proto=ESMTP helo=<tradcosmos.live>:
  5.7.1 Spam detected ID-1123681 see: https://spamreports.tytik.cloud/reports/1123681
...

Wie man sieht, ist auch der Link mit dem Hinweis enthalten.

Öffentlicher Report aus Link in der Reject Meldung

Das ist die Info für den Domainbetreiber bzw. Mailserver-Admin, der damit weiß, warum seine Mails nicht angenommen werden. Jeder Report ist genau auf die betreffende Mail zugeschnitten, und nicht, wie bei anderen Mailanbietern, nur eine sehr allgemeine Fehlernummer.

Fragt jemand nach, warum seine Mail geblockt wurde, rufe ich den internen Full-Report auf und sehe alle Gründe, die zum Block geführt haben.

Interner Full-Report mit ausführlichen Informationen

Ausblick

Um das Ganze unter Kontrolle zu halten und nicht doch einmal etwas Legitimes zu blocken, läuft es bewusst noch nicht vollautomatisch. Aktuell kopiere ich die gesammelten Mails manuell aus allen Postfächern nach ./new und starte die Analyse.

Wenn das eine Weile sauber funktioniert, wird auch das Einsammeln der Mails und die Analyse komplett automatisiert. Am Ende bleibt dann nur noch der Report über alle neuen Spam-Mails, den ich kontrollieren kann.