MTA-STS, TLS-RPT und DMARC-Berichte: den eigenen Mailserver absichern und auswerten
Teil 12 der Reihe Server im Netz: MTA-STS mit Caddy ausliefern, TLS-RPT und DMARC-Berichte empfangen und mit einem Skript lesen, dann von testing auf enforce und von p=none auf eine strengere DMARC-Stufe. Mit echten Abrufen durch Microsoft und Google und dem ersten Bericht von Google.
Werbung Mit * markierte Links sind Werbelinks: Bei einem Kauf erhalten wir eine Provision, der Preis bleibt gleich. Mehr dazu

Mit SPF, DKIM und DMARC aus Teil 11 weiß ein fremder Mailserver, ob eine Mail wirklich von Ihrer Domain kommt. Ob die Verbindung zu Ihrem Server verschlüsselt sein muss, weiß er damit noch nicht, und ob irgendwo Mails mit Ihrer Domain scheitern, erfahren Sie auch nicht. Dafür gibt es MTA-STS, TLS-RPT und die Berichte von DMARC. Diese Anleitung richtet MTA-STS mit Caddy ein, zeigt, wer die Policy wirklich abruft, und wertet den ersten echten Bericht aus, er kam von Google. Erst danach geht es von „testing“ auf „enforce“ und bei DMARC von „none“ auf eine strengere Stufe.
Dies ist Teil 12 der Reihe „Server im Netz“. Als Beispiel dient der Mailserver Stalwart aus Teil 11 auf einem VPS mit Caddy.
Was MTA-STS, TLS-RPT und DMARC-Berichte leisten
- MTA-STS sagt anderen Mailservern: Liefere an diese Domain nur über TLS mit gültigem Zertifikat an die genannten MX-Server. Im Modus
testingwird nur berichtet, im Modusenforcewird bei Fehlern nicht zugestellt. - TLS-RPT nennt die Adresse, an die sendende Server einmal am Tag melden, wie viele Verbindungen mit TLS geklappt haben und welche nicht.
- DMARC-Aggregatberichte kommen von Empfängern Ihrer Mails. Sie zeigen, von welchen Adressen Mails mit Ihrer Domain kamen und ob SPF und DKIM gepasst haben.
DANE aus DANE für den eigenen Mailserver verfolgt dasselbe Ziel wie MTA-STS, setzt aber DNSSEC voraus. MTA-STS braucht stattdessen HTTPS.
Voraussetzungen
- Ein Mailserver mit gültigem Zertifikat für den MX-Namen, hier
mail.sedv-lab.de, wie in Teil 11 - Caddy für HTTPS wie in Teil 4, mit einem DNS-Eintrag, der
mta-sts.ihre-domainauf den Server zeigt. Im Beispiel deckt ihn ein Wildcard-Eintrag schon ab. - Zugriff auf die DNS-Zone
- Eine zustellbare Adresse für die Berichte, im Beispiel
postmaster@sedv-lab.de
Schritt 1: Die Policy über Caddy ausliefern
Die Policy muss unter genau diesem Namen per HTTPS erreichbar sein. Eine eigene Website braucht es dafür nicht, Caddy liefert den Text direkt aus. Ergänzen Sie im Caddyfile:
mta-sts.sedv-lab.de {
log {
output file /log/mta-sts.json
format json
}
handle /.well-known/mta-sts.txt {
header Content-Type "text/plain; charset=utf-8"
respond <<ENDE
version: STSv1
mode: testing
mx: mail.sedv-lab.de
max_age: 86400
ENDE 200
}
respond 404
}Das eigene Log zeigt später, wer die Policy abruft. Auf dem Host liegt es dort, wo Ihr Container /log einbindet, im Beispiel /root/sin/web/log/. Im Beispiel stehen in dem Block außerdem die Schutzregeln aus den vorigen Teilen. Der Modus testing ist der sichere Anfang, max_age legt fest, wie lange Sender die Policy zwischenspeichern dürfen, hier ein Tag.
Wählen Sie für das Heredoc eine Endmarke, die im Text nicht vorkommt. STS scheiterte im Test an STSv1.
$ docker exec caddy caddy validate --config /etc/caddy/Caddyfile && docker exec caddy caddy reload --config /etc/caddy/Caddyfile
$ curl -s https://mta-sts.sedv-lab.de/.well-known/mta-sts.txtDer Abruf liefert die vier Zeilen mit HTTP 200. Das Zertifikat für den neuen Namen holte Caddy im Test innerhalb von Sekunden.
Schritt 2: Die DNS-Einträge setzen
Zwei TXT-Einträge gehören dazu. Der erste kündigt die Policy an. Seine id ändern Sie bei jeder Änderung der Policy, sonst holen Sender die neue Fassung erst nach Ablauf von max_age. Ein Zeitstempel eignet sich gut:
_mta-sts.sedv-lab.de. 3600 IN TXT "v=STSv1; id=20261106T1338"
_smtp._tls.sedv-lab.de. 3600 IN TXT "v=TLSRPTv1; rua=mailto:postmaster@sedv-lab.de"Der DMARC-Eintrag aus Teil 11 nennt dieselbe Adresse:
_dmarc.sedv-lab.de. 3600 IN TXT "v=DMARC1; p=none; rua=mailto:postmaster@sedv-lab.de"Fragen Sie nach dem Speichern zuerst den autoritativen Nameserver. Im Test war der Eintrag dort nach knapp fünf Minuten sichtbar. Eine Abfrage bei öffentlichen Resolvern davor rächte sich: Zwei von drei merkten sich die leere Antwort für die Dauer aus dem SOA-Eintrag, bei uns bis zu 3600 Sekunden.
$ dig +short TXT _mta-sts.sedv-lab.de @$(dig +short NS sedv-lab.de | head -1)Der autoritative Server liefert den Eintrag mit Ihrer id.
Schritt 3: Von außen prüfen
Der MTA-STS-Test von MXToolbox prüft Eintrag, Abruf, Zertifikat, Syntax der Policy und den MX-Server. Im Test waren alle acht Prüfungen grün.

Alle Prüfungen sind grün. Meldet der Test einen Fehler beim Abruf, prüfen Sie Zertifikat und Content-Type der Policy.
Schritt 4: Wer die Policy wirklich abruft
Das Log aus Schritt 1 zeigt es. Im Test schickten wir Mails aus Microsoft 365 und von outlook.de an den Server. In beiden Fällen holte Microsoft die Policy in derselben Sekunde, in der die SMTP-Verbindung kam, über IPv6 und ohne User-Agent. Die Mail kam dann mit TLS 1.3 an. Das zweite Microsoft-System holte die Policy erneut, obwohl das erste sie anderthalb Minuten vorher schon geladen hatte. Google holte die Policy bei einer Testmail aus Gmail ebenfalls während der Zustellung, aber über IPv4, die Mail selbst kam über IPv6.
$ sudo jq -r 'select(.request.uri=="/.well-known/mta-sts.txt") | [.ts, .request.remote_ip, .status] | @tsv' /root/sin/web/log/mta-sts.jsonIn den ersten 18 Stunden kamen auf den neuen Namen außerdem 1.244 andere Anfragen, von Scannern auf die Startseite und auf Pfade wie /.env. Die ersten im Log trafen etwa zehn Minuten nach dem Zertifikat ein. Der Block antwortet darauf mit 404, die Schutzregeln sperren einen Teil mit 403.
Nach einer Testmail von einem großen Anbieter steht ein Abruf mit Status 200 im Log.
Schritt 5: Sofort prüfen, statt auf Berichte zu warten
Die Berichte kommen meist einmal am Tag, für den Vortag. Anfordern lassen sie sich nicht. Ob SPF, DKIM und DMARC für eine einzelne Mail stimmen, zeigt ein Prüfdienst sofort. Auf learndmarc.com erscheint eine Empfangsadresse, an die Sie eine Mail von Ihrem Server schicken. Die Auswertung erscheint danach auf der Seite.

Im Test bestanden SPF, die RSA-Signatur und die Ed25519-Signatur, DMARC damit auch. Die ältere Prüfadresse check-auth@verifier.port25.com taugte nicht: Ihr Zertifikat war abgelaufen, Stalwart verweigerte die Zustellung mit certificate expired und behielt die Mail zum erneuten Versuch in der Warteschlange. Löschen Sie solche Mails dort, sonst versucht der Server es weiter.
Wie ein großer Anbieter die Mail sieht, zeigt Gmail unter „Original anzeigen“. Bei unserer Antwort auf eine Gmail-Mail stand dort:
dkim=neutral (no key) header.s=v1-ed25519-20261105
dkim=pass header.s=v1-rsa-20261105
spf=pass (google.com: domain of lab@sedv-lab.de designates 62.83.99.6 as permitted sender)
dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=sedv-lab.deTrotz bestandener Prüfungen landete die Mail im Spam, auch eine zweite Antwort, nachdem die erste als „Kein Spam“ markiert war. Die IP stand auf keiner der fünf geprüften Sperrlisten. Ob sie im Posteingang landet, entscheidet der Empfänger nach seinem eigenen Urteil über Domain und Server.
Der Prüfdienst zeigt für SPF, DKIM und DMARC jeweils PASS.
Schritt 6: Berichte empfangen und lesen
Prüfen Sie zuerst, ob die Berichtsadresse Mails annimmt. Im Beispiel ist postmaster@ ein Alias des Postfachs, eine Testmail landete sofort im Posteingang. Fehlt die Adresse, kommen nie Berichte an, und niemand merkt es.
Die Berichte kommen als Mail mit Anhang: DMARC als XML, gepackt als .zip oder .xml.gz, TLS-RPT als JSON in .json.gz. Im Test traf der erste Bericht gegen Mittag des Folgetags ein. Absender war Google, Betreff Report domain: sedv-lab.de Submitter: google.com. Er deckte den Vortag von 0 bis 24 Uhr UTC ab. Microsoft und TLS-RPT-Berichte waren bis dahin nicht dabei.
Lesen lässt sich das XML direkt, übersichtlicher wird es mit einem kleinen Skript. Es entpackt den Anhang selbst und gibt je Absender-IP eine Zeile aus. Speichern Sie es als auswert.py und übergeben Sie die gespeicherten Anhänge:
#!/usr/bin/env python3
"""DMARC- (XML) und TLS-RPT-Berichte (JSON) lesen, auch gepackt. Aufruf: auswert.py DATEI..."""
import gzip, io, json, sys, zipfile
import xml.etree.ElementTree as ET
from datetime import datetime, timezone
def roh(p):
b = open(p, "rb").read()
if b[:2] == b"\x1f\x8b": return gzip.decompress(b)
if b[:2] == b"PK":
z = zipfile.ZipFile(io.BytesIO(b)); return z.read(z.namelist()[0])
return b
def zeit(s): return datetime.fromtimestamp(int(s), timezone.utc).strftime("%d.%m. %H:%M")
def dmarc(x):
r = ET.fromstring(x)
md = r.find("report_metadata"); pol = r.find("policy_published")
print(f"DMARC von {md.findtext('org_name')}, Zeitraum {zeit(md.findtext('date_range/begin'))} bis {zeit(md.findtext('date_range/end'))} UTC, Policy p={pol.findtext('p')}")
for rec in r.findall("record"):
row = rec.find("row"); pe = row.find("policy_evaluated"); au = rec.find("auth_results")
dk = ",".join(f"{d.findtext('domain')}/{d.findtext('selector')}={d.findtext('result')}" for d in au.findall("dkim")) or "-"
sp = ",".join(f"{d.findtext('domain')}={d.findtext('result')}" for d in au.findall("spf")) or "-"
print(f" Quelle {row.findtext('source_ip')} Mails {row.findtext('count')} Ergebnis dkim={pe.findtext('dkim')} spf={pe.findtext('spf')} disposition={pe.findtext('disposition')} Header-From {rec.findtext('identifiers/header_from')} DKIM {dk} SPF {sp}")
def tlsrpt(x):
d = json.loads(x)
dr = d.get("date-range", {})
print(f"TLS-RPT von {d.get('organization-name')}, Zeitraum {dr.get('start-datetime')} bis {dr.get('end-datetime')}")
for p in d.get("policies", []):
pol = p.get("policy", {}); s = p.get("summary", {})
print(f" Policy {pol.get('policy-type')} {pol.get('policy-domain')} erfolgreich {s.get('total-successful-session-count')} fehlgeschlagen {s.get('total-failure-session-count')}")
for f in p.get("failure-details", []) or []:
print(f" Fehler {f.get('result-type')} von {f.get('sending-mta-ip')} an {f.get('receiving-mx-hostname')}: {f.get('failed-session-count')}")
for p in sys.argv[1:]:
x = roh(p)
print("==", p.split("/")[-1])
(tlsrpt if x.lstrip()[:1] == b"{" else dmarc)(x)$ python3 auswert.py ~/Downloads/*sedv-lab.de*.zip== 2026-11-07T104516Z_google.com!sedv-lab.de!1793923200!1794009599.zip
DMARC von google.com, Zeitraum 06.11. 00:00 bis 06.11. 23:59 UTC, Policy p=none
Quelle 62.83.99.6 Mails 2 Ergebnis dkim=pass spf=pass disposition=none Header-From sedv-lab.de DKIM sedv-lab.de/v1-ed25519-20261105=fail,sedv-lab.de/v1-rsa-20261105=pass SPF sedv-lab.de=passSo lesen Sie die Zeile:
- Quelle ist die IP, von der die Mails kamen. Steht hier eine Adresse, die Sie nicht kennen, verschickt jemand Mails mit Ihrer Domain, oder ein Dienst wie ein Newsletter-Anbieter fehlt noch im SPF-Eintrag. Auch weitergeleitete Mails, etwa über eine Mailingliste, tauchen so auf.
- Ergebnis ist die Bewertung für DMARC. Für
passgenügt eine der beiden Prüfungen, wenn ihre Domain zur Absenderdomain passt. - DKIM listet jede Signatur einzeln mit ihrem Selektor.
Hier steht eine Überraschung: Google meldete die Ed25519-Signatur als fail. Im Header der späteren Antwort an Gmail stand für denselben Selektor neutral (no key). learndmarc hatte dieselbe Signatur als gültig bestätigt. Google prüft Ed25519 also nicht. Das Ergebnis für DMARC blieb pass, weil die RSA-Signatur trug. Signieren Sie deshalb nie nur mit Ed25519.
Im Bericht stehen nur Ihre eigenen Server-IPs, und bei jeder steht unter Ergebnis mindestens einmal pass.
Schritt 7: Von testing auf enforce
Mit enforce liefert ein Sender, der MTA-STS beachtet, nur noch zu, wenn TLS und Zertifikat passen. Stellen Sie erst um, wenn die Prüfung bei MXToolbox grün ist und die TLS-RPT-Berichte Ihrer wichtigsten Absender keine Fehler zeigen. Im Test haben wir nach einem Tag umgestellt, obwohl noch kein TLS-RPT-Bericht da war, weil die Domain nur Testmails bekommt und alle Abrufe sauber waren. Ändern Sie im Caddyfile die Zeilen mode und max_age:
mode: enforce
max_age: 604800Sieben Tage für max_age sind ein Mittelweg. So lange halten Sender die Policy fest, auch wenn sie die Datei zwischendurch nicht laden können. Einen neuen MX-Namen tragen Sie in die Policy ein und ändern die id, dann holen Sender sie neu. Wollen Sie MTA-STS abschalten, veröffentlichen Sie mode: none mit neuer id. Den TXT-Eintrag nur zu löschen genügt nicht. Halten Sie den Wert klein, solange sich am Mailserver noch etwas ändert.
$ docker exec caddy caddy validate --config /etc/caddy/Caddyfile && docker exec caddy caddy reload --config /etc/caddy/Caddyfile
$ docker exec caddy grep -n "mode:" /etc/caddy/Caddyfile
$ curl -s https://mta-sts.sedv-lab.de/.well-known/mta-sts.txtIm Test lieferte Caddy nach dem Reload noch mode: testing: sed -i legt eine neue Datei an, der Container sah über den Einzeldatei-Mount weiter die alte. Die zweite Zeile oben zeigt das. Abhilfe ist docker restart caddy, die Website war dafür rund 14 Sekunden weg. Binden Sie besser den Ordner statt der Datei ein.
Dann bekommt der Eintrag _mta-sts eine neue id, sonst holen Sender die Policy erst nach Ablauf des alten max_age neu:
_mta-sts.sedv-lab.de. 3600 IN TXT "v=STSv1; id=20261107T1147"In der CloudDNS-API von netcup gibt es kein Ändern, nur Löschen und Anlegen. Beides geht in einem Changeset mit delete und create, beides wird mit einem Aufruf übernommen.
Die Policy zeigt mode: enforce, der autoritative Nameserver die neue id, und der Test bei MXToolbox bleibt grün.
Schritt 8: DMARC strenger stellen
Mit p=none berichten Empfänger nur. Mit p=quarantine bitten Sie, Mails, die DMARC nicht bestehen, als verdächtig zu behandeln, meist landen sie im Spam. Mit p=reject sollen Empfänger sie ablehnen. Subdomains ohne eigenes sp= erben die Stufe. Voraussetzung für den Schritt: In den Berichten stehen nur eigene Quellen, und alle bestehen. Im Test lag ein Bericht vor, von Google, und darin stand nur der Server selbst.
_dmarc.sedv-lab.de. 3600 IN TXT "v=DMARC1; p=quarantine; rua=mailto:postmaster@sedv-lab.de"Den Eintrag haben wir im selben Changeset wie die neue id getauscht. Bleiben die Berichte danach einige Wochen sauber, folgt p=reject. Verschickt ein weiterer Dienst Mails mit Ihrer Domain, etwa ein Shop oder ein Newsletter, richten Sie vorher SPF oder besser DKIM mit Ihrer Domain für ihn ein. Sonst behandeln Empfänger genau diese Mails als verdächtig.
$ dig +short TXT _dmarc.sedv-lab.de @$(dig +short NS sedv-lab.de | head -1)Der autoritative Server liefert p=quarantine. Im nächsten Bericht steht unter Policy p=quarantine.
Typische Fehler
- Das Heredoc mit der Endmarke
STSscheitert Die Endmarke steht schon in
STSv1. Wählen Sie eine Endmarke, die im Text nicht vorkommt, im BeispielENDE.- Öffentliche Resolver liefern den neuen TXT-Eintrag nicht
Sie haben dort gefragt, bevor der Eintrag beim autoritativen Nameserver sichtbar war. Zwei von drei Resolvern merkten sich die leere Antwort für die Dauer aus dem SOA-Eintrag, im Test bis zu 3600 Sekunden. Fragen Sie zuerst den autoritativen Nameserver.
- MXToolbox meldet einen Fehler beim Abruf
Prüfen Sie Zertifikat und Content-Type der Policy.
- Nach dem Reload liefert Caddy noch
mode: testing sed -ilegt eine neue Datei an, der Container sieht über den Einzeldatei-Mount weiter die alte. Abhilfe istdocker restart caddy. Binden Sie besser den Ordner statt der Datei ein.- Sender holen die geänderte Policy nicht sofort
Die
idim Eintrag_mta-stsist gleich geblieben. Sender holen die neue Fassung dann erst nach Ablauf vonmax_age. Ändern Sie dieidbei jeder Änderung der Policy.certificate expiredDie Prüfadresse
check-auth@verifier.port25.comhatte ein abgelaufenes Zertifikat, Stalwart behielt die Mail in der Warteschlange. Löschen Sie die Mail dort und nehmen Sie einen anderen Prüfdienst wie learndmarc.com.dkim=neutral (no key)für die Ed25519-SignaturGoogle prüft Ed25519 nicht und meldete die Signatur im DMARC-Bericht als
fail. DMARC blieb im Test pass, weil die RSA-Signatur trug. Signieren Sie nie nur mit Ed25519.
Häufige Fragen
Wie lange sollte MTA-STS auf testing bleiben?
So lange, bis die TLS-RPT-Berichte Ihrer wichtigsten Absender einige Tage ohne Fehler kommen, oft ein bis zwei Wochen.
Brauche ich MTA-STS, wenn DANE schon läuft?
Ein Sender nutzt, was er unterstützt. Im Test holte Microsoft die MTA-STS-Policy vor jeder Zustellung, obwohl für den Server auch DANE eingerichtet war. Sender, die DANE nicht prüfen, haben ohne MTA-STS keine Vorgabe für TLS.
Wer bekommt die DMARC-Berichte über meine Mails?
Die Adresse aus rua im eigenen DMARC-Eintrag. Ihr eigener Server verschickt umgekehrt Berichte an andere: Stalwart bereitete im Test kurz nach Mitternacht einen Bericht für die Domain vor, von der am Vortag eine Mail eingegangen war.
Fazit
MTA-STS ist schnell eingerichtet: eine Textdatei über Caddy und zwei TXT-Einträge. Microsoft holte die Policy im Test bei jeder Zustellung, Google bei der einen Testmail ebenfalls. Die Berichte brauchen Geduld, für einzelne Mails hilft ein Prüfdienst sofort. Der erste Bericht von Google zeigte, dass erst die zweite Signatur mit RSA DKIM bestehen ließ. Bestandene Prüfungen belegen, dass eine Mail echt ist. Ob sie im Posteingang landet, entscheidet der Empfänger.


