E-Mails sind nach wie vor das am weitesten verbreitete Angriffsziel im Internet. Phishing und Spam nutzen gefälschte Absenderadressen, um an Zugangsdaten oder Geld zu kommen. Damit empfangende Mailserver überprüfen können, ob eine E-Mail wirklich von der Domain stammt, für die sie sich ausgibt, gibt es drei etablierte Standards: SPF, DKIM und DMARC.
SPF (Sender Policy Framework) ist ein DNS-basierter Mechanismus, mit dem der Inhaber einer Domain festlegt, welche Server (IP-Adressen oder Hostnamen) berechtigt sind, E-Mails im Namen dieser Domain zu versenden.
Ein SPF-Record liegt als TXT-Record direkt in der DNS-Zone:
example.org. IN TXT "v=spf1 mx a include:_spf.provider.net -all"
v=spf1 – Version des Recordsmx / a – die Server hinter den MX- bzw. A-Records dürfen sendeninclude: – fremde Infrastruktur (z.B. ein Newsletter-Provider) einbinden-all – alles andere wird abgelehnt (Hard Fail), alternativ ~all (Soft Fail)Wichtig: Pro Domain gibt es nur einen SPF-Record, und dieser darf maximal 10 DNS-Lookups enthalten.
Der empfangende Mailserver vergleicht die IP des absendenden Servers mit der Policy im Record. Jeder Mechanismus (a, mx, ip4:, ip6:, include: …) liefert dabei ein Treffer/Nicht-Treffer, und das Präfix entscheidet, was daraus folgt:
+ (Pass) – Absender ist legitimiert (Standard, wenn kein Präfix gesetzt ist)- (Hard Fail) – Absender ist ausdrücklich nicht legitimiert → Ablehnung~ (Soft Fail) – verdächtig, aber toleriert → typischerweise Spam-Verdacht? (Neutral) – keine AussageIn der Praxis hat sich ~all während der Einführungsphase bewährt; für produktive Domains mit sauber dokumentierter Versandinfrastruktur ist -all das Ziel.
Die 10-Lookup-Grenze ist die häufigste Stolperfalle: include:, a, mx, ptr, exists und redirect= zählen jeweils als DNS-Abfrage. Verschachtelte include:s des Providers zählen ebenfalls mit. Wird das Limit überschritten, liefert die evaluation ein permerror – viele Empfänger behandeln das wie einen Fail. Abhilfe schafft das Kürzen auf wirklich genutzte Dienste oder „Flattening“ (Auflösen der Includes in IP-Listen) – letzteres bedeutet aber manuellen Pflegeaufwand, sobald sich Provider-IPs ändern.
Häufige Fehler, die man in der Wildnis sieht:
permerrorinclude:s nach einem Providerwechsel → legitime Mails failenFrom:-Header – genau diese Lücke schließt erst DMARCDKIM (DomainKeys Identified Mail) signiert jede ausgehende E-Mail kryptographisch. Der sendende Server legt eine Signatur in den Mailheader ab; der Empfänger holt den passenden öffentlichen Schlüssel aus dem DNS und prüft, ob die Mail auf dem Weg unverändert blieb.
Der öffentliche Schlüssel liegt als TXT-Record unter einem sogenannten Selector:
selector._domainkey.example.org. IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBg..."
Den privaten Schlüssel und den Selector generiert und konfiguriert man im eigenen Mailsystem (z.B. Postfix mit OpenDKIM, Rspamd oder Exchange).
Der Selector ist ein frei wählbarer Name, über den der Empfänger den passenden Public Key findet (selector1._domainkey.example.org). Mehrere Selektoren können parallel existieren – so betreibt man z.B. getrennte Schlüssel für Newsletter und Transaktionsmails oder rotiert Keys, ohne den Mailbetrieb anzuhalten: neuen Selector publishen, umschalten, alten nach einer Übergangszeit löschen.
Zur Schlüsselwahl:
Ein praktischer Nebeneffekt von DKIM: Die Signatur bleibt beim Weiterleiten (Forwarding) erhalten, während SPF am Weiterleitungsserver scheitert. Deshalb sollte DMARC so konfiguriert werden, dass DKIM-Alignment allein genügt, um eine Mail als legitim zu werten.
DMARC (Domain-based Message Authentication, Reporting and Conformance) verknüpft SPF und DKIM miteinander und fügt zwei Dinge hinzu:
Ein typischer DMARC-Record:
_dmarc.example.org. IN TXT "v=DMARC1; p=none; rua=mailto:dmarc@example.org; pct=100; adkim=r; aspf=r"
Die wichtigsten Felder:
p=none – nur überwachen (quarantine = Spam-Ordner, reject = ablehnen)rua= – Mailbox, in der die aggregierten Reports (XML) landenadkim / aspf – Alignment-Modus (relaxed oder strict)pct – Prozentsatz der Mails, auf den die Policy angewendet wirdEin einzelnes SPF- oder DKIM-Pass reicht für DMARC nicht aus: Entscheidend ist das Alignment, also ob die authentifizierte Domain zum sichtbaren From:-Header passt. Beim relaxed Modus (adkim=r / aspf=r, der Default) genügt die Übereinstimmung der organisatorischen Domain – eine Mail mit From: newsletter@example.org, versendet über bounce.provider.net mit SPF auf provider.net, aligned nicht. Korrekt Beispiel: DKIM-Signatur von example.org und From: @example.org → Alignment vorhanden. Im strict Modus (s) muss die Domain exakt übereinstimmen, was bei Nutzung externer Dienstleister oft nicht erreichbar ist.
Zusätzlich relevant:
sp= – eigene Policy für Subdomains, falls diese strenger oder lockerer sein soll als die Hauptdomainrua= liefert Aggregate Reports (Zusammenfassungen), ruf= die detaillierten Failure Reports – letztere unterstützen viele große Provider aus Datenschutzgründen gar nicht oder nur eingeschränktrua= auf eine fremde Domain, verlangt der Empfänger dort einen Verifizierungs-Record (example.org._report._dmarc.…), um Report-Missbrauch zu verhindernDie Reports werden als XML-Anhänge per E-Mail zugestellt – bei mehreren großen Providern kommen da schnell täglich Dutzende Mails zusammen. Genau hier setzt ein DMARC-Analyzer an.
Kommerzielle DMARC-Plattformen abrechnen meist pro Domain und Nachrichtenvolumen – teuer, wenn man viele Domains betreut. Mit DmarcAnalyzer (GitHub) gibt es eine freie Alternative (Apache-2.0):
p=none bis p=reject
Für die nächsten Schritte setzen wir ein Linux mit einem aktuell installierten Docker voraus – genau wie in unserem Vaultwarden-Artikel mit Traefik als Reverse Proxy, der automatisch Let’s Encrypt Zertifikate verwaltet.
DmarcAnalyzer besteht aus einem App-Container (ASP.NET Core + React, Konsole und Report-Abruf in einem Prozess) und einer PostgreSQL-Datenbank.
Zunächst braucht die Domain einen DMARC-Record, dessen rua= auf eine Mailbox zeigt, die der Analyzer später abrufen kann:
_dmarc.example.org. IN TXT "v=DMARC1; p=none; rua=mailto:dmarc-reports@example.org"
Außerdem muss der gewünschte Hostname (im Beispiel dmarc.example.org) per A/AAAA-Record auf den Server zeigen, damit Traefik das Zertifikat holen kann.
In diesem Beispiel betreiben wir DmarcAnalyzer hinter einem Reverse Proxy namens Traefik, der sich automatisch letsencrypt Zertifikate verwalten kann. Der Aufbau entspricht unserem Vaultwarden-Setup.
docker-compose.yaml:
services:
traefik:
container_name: traefik
image: traefik:latest
ports:
- 80:8080
- 443:8443
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./traefik-data:/letsencrypt
networks:
- default
command:
- --entrypoints.web.address=:8080
- --entrypoints.websecure.address=:8443
- --providers.docker
- --log.level=ERROR
- --certificatesresolvers.leresolver.acme.httpchallenge=true
- --certificatesresolvers.leresolver.acme.email=info@example.org
- --certificatesresolvers.leresolver.acme.storage=/letsencrypt/acme.json
- --certificatesresolvers.leresolver.acme.httpchallenge.entrypoint=web
labels:
- "traefik.http.routers.http-catchall.rule=hostregexp(`.+`)"
- "traefik.http.routers.http-catchall.entrypoints=web"
- "traefik.http.routers.http-catchall.middlewares=redirect-to-https"
- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
restart: always
dmarc-analyzer:
container_name: dmarc-analyzer
image: ghcr.io/dmarc-analyzer-net/dmarc-analyzer:latest
restart: unless-stopped
networks:
- default
depends_on:
postgres:
condition: service_healthy
environment:
# Konsole und Report-Abruf in einem Prozess
APP_MODE=all
ConnectionStrings__Default=Host=postgres;Port=5432;Database=dmarc_analyzer;Username=dmarc;Password=${POSTGRES_PASSWORD}
Database__MigrateOnStartup=true
# Verschluesselt die Zugangsdaten der Report-Mailbox at rest,
# siehe .env Datei weiter unten
Security__CredentialEncryptionKey=${DMARC_ENCRYPTION_KEY}
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://localhost:8080/api/v1/auth/setup > /dev/null"]
interval: 10s
timeout: 5s
retries: 20
start_period: 150s
labels:
- "traefik.enable=true"
- "traefik.http.routers.dmarc.rule=Host(`dmarc.example.org`)"
- "traefik.http.routers.dmarc.entrypoints=websecure"
- "traefik.http.routers.dmarc.tls=true"
- "traefik.http.routers.dmarc.tls.certresolver=leresolver"
- "traefik.http.routers.dmarc.service=dmarc"
- "traefik.http.services.dmarc.loadbalancer.server.port=8080"
postgres:
container_name: dmarc-postgres
image: postgres:18-alpine
restart: unless-stopped
networks:
- default
environment:
POSTGRES_DB=dmarc_analyzer
POSTGRES_USER=dmarc
POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
PGDATA=/var/lib/postgresql/data
volumes:
- ./dmarc-pgdata:/var/lib/postgresql/data
networks:
default:
internal: false
enable_ipv6: true
ipam:
driver: default
config:
- subnet: fd00:1::/64
gateway: fd00:1::1
Hinweise zum Setup:
8080, deshalb zeigt der Loadbalancer-Label darauf.start_period, weil beim ersten Start die Datenbankmigrationen durchlaufen werden.Neben der docker-compose.yaml wird eine .env Datei benötigt. Der Key verschlüsselt die Mailbox-Zugangsdaten in der Datenbank (AES-256-GCM). Er sollte sicher verwahrt werden – geht er verloren, müssen alle hinterlegten Mailbox-Zugänge neu eingegeben werden:
user@bar:~$ echo "DMARC_ENCRYPTION_KEY=$(openssl rand -base64 32)" >> .env
user@bar:~$ echo "POSTGRES_PASSWORD=$(openssl rand -base64 24)" >> .env
Als Erstes legen wir einen Benutzer und passende Verzeichnisse an:
root@bar:~# adduser -g docker dmarc
root@bar:~# su - dmarc
dmarc@bar:~$ mkdir traefik-data
dmarc@bar:~$ mkdir dmarc-pgdata
dmarc@bar:~$
Jetzt werden die obige docker-compose.yaml unter /home/dmarc/docker-compose.yaml sowie die .env Datei im selben Verzeichnis abgelegt.
Beide Dateien sollten entsprechend der lokalen Gegebenheiten angepasst werden (Hostnamen, ACME-E-Mail, Passwörter).
Nun kann man die Container herunterladen und die Anwendung starten:
dmarc@bar:~$ docker compose pull
dmarc@bar:~$ docker compose up -d
dmarc@bar:~$
Nachdem Traefik das Zertifikat geholt hat, steht die Konsole unter der konfigurierten URL zur Verfügung. Beim ersten Aufruf legt man das Admin-Konto an – danach ist die Registrierung gesperrt.

Anschließend richtet man in der Weboberfläche ein:
rua=-Reports ankommen (z.B. dmarc-reports@example.org)

Ab dann ruft ein Hintergrundprozess die Mailbox regelmäßig ab, entpackt die XML-Berichte und wertet SPF/DKIM-Alignment pro absendender Quelle aus.
Wenn die ersten Reports eingegangen sind, sieht man pro Domain, welche Quellen legitim senden und wo es hakt. Erst wenn alle legitimen Quellen sauber authentifizieren, sollte die Policy schrittweise verschärft werden:

p=none → p=quarantine → p=reject
Mit pct= lässt sich die Verschärfung zusätzlich stufenlos einführen.
DmarcAnalyzer kann Benutzer über einen beliebigen OpenID-Connect-Provider authentifizieren – die Projekt-Doku führt Guides für Zitadel, Keycloak, Authentik, Google und Microsoft Entra ID. Wichtig zu wissen: OIDC ersetzt nur das Login, nicht die Rechteverwaltung. Rollen und Client-Zuordnungen bleiben weiterhin in der Anwendung selbst; eine SSO-Sitzung ist intern identisch mit einer Passwort-Sitzung.
Eingeschaltet wird das Feature per Umgebungsvariablen, der Callback läuft unter /api/v1/auth/oidc/callback:
environment:
Auth__Oidc__Enabled=true
Auth__Oidc__Authority=https://id.example.org
Auth__Oidc__ClientId=dmarc-analyzer
Auth__Oidc__DisplayName=Example ID
Auth__Oidc__AutoProvision=false
Sobald Auth__Oidc__Enabled aktiv ist, zeigt die Login-Seite zusätzlich einen „Sign in with …“-Button. Mit AutoProvision=false (empfohlenes Produktions-Setting) legt ein Admin Benutzer vorher an, ohne Passwort; beim ersten SSO-Login wird das Konto dann über die verifizierte E-Mail-Adresse verknüpft.
SPF, DKIM und DMARC sind heute Pflichtprogramm für jede Domain, die E-Mail versendet. Mit DmarcAnalyzer bekommt man ein freies, selbst gehostetes Monitoring dazu, das ohne Lizenzkosten pro Domain auskommt und die Daten nicht an einen Cloud-Dienst übergibt. Kombiniert mit Docker und Traefik ist der Betrieb so einfach wie bei unseren anderen Self-Hosting-Stacks.
Sollten Sie Fragen zu SPF, DKIM und DMARC in Ihrer IT Umgebung haben oder Unterstützung benötigen, nehmen Sie bitte Kontakt zu uns auf.