01Zielbild & Vertrauenszonen
Ziel ist ein öffentlicher Mail-Relay auf Debian mit dem Hostnamen mail.example.com. Er übernimmt zwei Aufgaben:
- Ausgehend (Smarthost): Anwendungen und der interne Mailserver liefern authentifiziert per SASL über TLS auf Port 587 ein; der Relay signiert per DKIM und stellt an die Ziel-MX im Internet zu.
- Eingehend (Gateway): Der Relay ist MX für
example.com, nimmt auf Port 25 nur Mail für existierende Empfänger an und reicht sie über einen WireGuard-Tunnel an den internen Mailserver (10.10.0.2) weiter.
Vertrauenszonen und ihre Durchsetzung
Auf einer Postfix-Instanz treffen sehr unterschiedliche Gegenstellen zusammen. Jede Zone erhält deshalb einen eigenen Listener mit eigener Policy, und jede Policy wird zusätzlich per Firewall und Negativtest abgesichert:
| Zone / Listener | Wer darf | Hauptrisiko | Durchsetzung |
|---|---|---|---|
*:25 SMTP | beliebige fremde MX, nur an gültige Empfänger in example.com | Open Relay, Spam, Backscatter, Last | kein SASL, reject_unauth_destination, relay_recipient_maps, Anvil-Limits |
öffentl. IP:587 Submission | Anwendungs-Konten | gestohlene Zugangsdaten, Absenderfälschung | TLS + SASL Pflicht, Envelope- und Header-Absenderbindung, Kontingent pro Konto, Kontosperre |
10.10.0.1:587 Tunnel-Submission | nur das Konto des internen MTA | Kompromittierung des internen MTA | nur über wg0 erreichbar, SASL Pflicht, Absender nur @example.com, eigenes Kontingent, Konto von außen gesperrt |
| WireGuard | genau ein Peer | seitliche Bewegung über den Tunnel | AllowedIPs /32 + Host-Firewall auf beiden Seiten, kein Routing |
| OpenDKIM | nur Postfix (Loopback) | Missbrauch des Signierschlüssels | Socket auf 127.0.0.1, Signatur nur für authentifizierte Einlieferung |
| SSH / Administration | Management-IP bzw. Admin-VPN | Übernahme des Hosts | Firewall-Quellbeschränkung, nur Schlüssel, kein Root-Login |
Die wichtigste Designentscheidung: Identität statt Netzwerkvertrauen. Kein Netz außer localhost steht in mynetworks. Relay-Rechte hängen ausschließlich an SASL-Konten, und die Konten sind an Absender und Kontingente gebunden.
Die Anleitung zielt auf kleine und mittlere Umgebungen mit einem überschaubaren Kreis absendender Anwendungen. Für geschäftskritische oder regulierte Umgebungen empfehlen wir zusätzlich: Eingangs-Gateway und Ausgangs-Relay auf getrennten Systemen (zwei Postfix-Instanzen auf demselben Host trennen nur die Konfiguration, nicht das Betriebssystem), eine formale Bedrohungsanalyse und einen dokumentierten Abnahmetest nach Abschnitt 15.
Voraussetzungen
- Ein Server mit Debian 13 (Trixie), statischer öffentlicher IPv4 und
root-Zugang. Debian 12 erhält seit Juni 2026 nur noch LTS-Sicherheitsunterstützung; für Neuinstallationen ist Debian 13 die richtige Basis. - Zugriff auf die DNS-Zone von
example.comund die Möglichkeit, den PTR/Reverse-DNS der IP beim Hoster zu setzen. - Eine feste Management-IP oder ein Admin-VPN für SSH.
Platzhalter: Domain example.com, Relay mail.example.com mit öffentlicher IP 203.0.113.10 auf Interface eth0, Management-IP 198.51.100.20, Tunnelnetz 10.10.0.0/24 (Relay 10.10.0.1, interner MTA 10.10.0.2). Ersetzen Sie alle Werte durch Ihre realen Angaben.
02Grundsystem & Härtung
Ein korrekter FQDN und eine korrekte Uhrzeit sind für TLS, DKIM und Logauswertung Voraussetzung.
apt update && apt -y full-upgrade
# FQDN setzen
hostnamectl set-hostname mail.example.com
echo "127.0.1.1 mail.example.com mail" >> /etc/hosts
# Zeitsynchronisation
apt -y install systemd-timesyncd
timedatectl set-ntp trueKernel-Parameter
Der Relay routet nicht. Außerdem soll Postfix den Tunnel-Listener auf 10.10.0.1 auch dann binden können, wenn wg0 beim Start noch nicht aktiv ist. Andernfalls bricht der Postfix-Master beim Booten ab.
# kein Routing zwischen eth0 und wg0
net.ipv4.ip_forward = 0
# strikte Rückwegprüfung gegen gespoofte Quelladressen
net.ipv4.conf.all.rp_filter = 1
# Bind auf 10.10.0.1 erlauben, bevor wg0 existiert
net.ipv4.ip_nonlocal_bind = 1sysctl --systemFirewall: nur was gebraucht wird
ufw dient als schlanke Front für nftables. Grundsatz: Standard ist deny; jeder offene Port ist begründet. SSH nur von der Management-IP. Port 465 bleibt zu, solange Port 587 genügt, und Port 80 wird bei DNS-01-Validierung nie geöffnet.
apt -y install ufw
ufw default deny incoming
ufw default allow outgoing
ufw default deny routed
# Tunneladressen dürfen NICHT über das öffentliche Interface erreichbar sein
ufw deny in on eth0 to 10.10.0.0/24
ufw allow from 198.51.100.20 to any port 22 proto tcp # SSH nur von Management-IP
ufw allow 25/tcp # SMTP eingehend (MX)
ufw allow in on eth0 to 203.0.113.10 port 587 proto tcp # Submission für Apps
ufw allow in on wg0 from 10.10.0.2 to 10.10.0.1 port 587 proto tcp # Submission interner MTA
ufw allow 51820/udp # WireGuard (bei fester Gegenstellen-IP: "from <IP>" ergänzen)
ufw --force enable
ufw status numberedWenn alle Anwendungen von bekannten IPs einliefern, Port 587 zusätzlich auf diese Quellen beschränken (ufw allow from <APP_IP> to 203.0.113.10 port 587 proto tcp). Das ist die wirksamste Einzelmaßnahme gegen gestohlene Zugangsdaten.
IPv6: ganz oder gar nicht
Diese Anleitung betreibt den Relay nur mit IPv4 (inet_protocols = ipv4 in Abschnitt 3). Wird IPv6 benötigt, muss es vollständig mitgezogen werden: ufw-Regeln für IPv6 (IPV6=yes in /etc/default/ufw), AAAA und passender PTR, SPF-Eintrag, IPv6-spezifische Listener in der master.cf, Monitoring und Reputation. Eine IPv4-Firewall ohne IPv6-Pendant ist keine Absicherung.
SSH härten
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
AllowUsers admin
X11Forwarding no
MaxAuthTries 3Erst einen SSH-Schlüssel hinterlegen und die Anmeldung testen, dann PasswordAuthentication no aktivieren und systemctl reload ssh. Sonst sperren Sie sich aus. Für administrative Aufgaben ein persönliches Konto mit sudo verwenden, kein geteiltes Konto.
Patchprozess statt nur Auto-Updates
unattended-upgrades spielt Sicherheitsupdates zeitnah ein. Ein Patchprozess braucht aber mehr: Neustarts betroffener Dienste, eine Funktionsprüfung danach und die Kontrolle, dass das Update wirkt.
apt -y install unattended-upgrades apt-listchanges needrestart
dpkg-reconfigure -plow unattended-upgrades
# AppArmor: Status UND tatsächlich durchgesetzte Profile prüfen
apt -y install apparmor apparmor-utils
aa-status
# nur benötigte Dienste aktiv
systemctl list-unit-files --state=enabled
ss -tulpn- Frist: kritische Sicherheitsupdates für exponierte Dienste (Postfix, OpenSSL, OpenSSH, WireGuard, Kernel) innerhalb von 72 Stunden, übrige im monatlichen Wartungsfenster.
- Nach jedem Update:
needrestart -r lprüfen, Kernel-Updates per geplantem Reboot aktivieren, danach den Selbsttest aus Abschnitt 15 laufen lassen. - Rückweg: Snapshot (bei VMs/LXC) bzw. Konfigurationsbackup (Abschnitt 16) vor dem Wartungsfenster.
Postfix installieren
DEBIAN_FRONTEND=noninteractive apt -y install postfix postfix-pcre
systemctl enable postfix
cp /etc/postfix/main.cf{,.orig}
cp /etc/postfix/master.cf{,.orig}Unter Debian läuft Postfix als systemd-Instanz postfix@-.service. postfix.service ist nur eine Hülle. Logs daher mit journalctl -u postfix@- lesen, nicht mit -u postfix.
03Postfix-Konfiguration: main.cf
Die main.cf definiert Identität, Relay-Ziele, globale Restriktionen, TLS, Ressourcengrenzen und die Milter-Anbindung. Dienstspezifisches (Submission) folgt in der master.cf.
# ===== Identität =====
myhostname = mail.example.com
mydomain = example.com
myorigin = $mydomain
compatibility_level = 3.6
# ===== Netzwerk / keine lokale Zustellung =====
inet_interfaces = all
inet_protocols = ipv4
mydestination =
local_recipient_maps =
local_transport = error:local delivery is disabled
# Nur localhost ist vertraut. Kein Tunnel-, LAN- oder Hosternetz!
mynetworks = 127.0.0.0/8
# ===== Eingehend: nur example.com, nur existierende Empfänger =====
relay_domains = example.com
transport_maps = hash:/etc/postfix/transport
relay_recipient_maps = hash:/etc/postfix/relay_recipients
# ===== Globale Restriktionen (gelten für Port 25) =====
smtpd_helo_required = yes
smtpd_relay_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
reject_unauth_destination
smtpd_recipient_restrictions =
permit_mynetworks,
reject_unauth_destination,
reject_non_fqdn_recipient,
reject_unknown_recipient_domain
smtpd_helo_restrictions =
permit_mynetworks,
reject_invalid_helo_hostname,
reject_non_fqdn_helo_hostname
# ===== SASL: global AUS, nur in den Submission-Diensten an =====
smtpd_sasl_auth_enable = no
smtpd_sasl_type = cyrus
smtpd_sasl_path = smtpd
smtpd_sasl_security_options = noanonymous, noplaintext
smtpd_sasl_tls_security_options = noanonymous
smtpd_sasl_local_domain = $myhostname
smtpd_sender_login_maps = hash:/etc/postfix/sender_login
# ===== TLS (Zertifikate via acme.sh, Abschnitt 10) =====
smtpd_tls_cert_file = /etc/postfix/tls/mail.example.com/fullchain.cer
smtpd_tls_key_file = /etc/postfix/tls/mail.example.com/private.key
# Port 25: opportunistisch (TLS anbieten, nicht erzwingen)
smtpd_tls_security_level = may
smtpd_tls_auth_only = yes
smtpd_tls_mandatory_protocols = >=TLSv1.2
smtpd_tls_protocols = >=TLSv1.2
smtp_tls_security_level = may
smtp_tls_mandatory_protocols = >=TLSv1.2
smtp_tls_protocols = >=TLSv1.2
smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt
tls_preempt_cipherlist = yes
smtpd_tls_loglevel = 1
smtp_tls_loglevel = 1
# ===== DKIM / Milter (Abschnitt 9) =====
# tempfail: Fällt OpenDKIM aus, wird Mail mit 4xx zurückgestellt statt
# unsigniert ausgeliefert. Gilt auch eingehend (fremde MX stellen erneut zu).
milter_default_action = tempfail
milter_protocol = 6
smtpd_milters = inet:127.0.0.1:8891
non_smtpd_milters = inet:127.0.0.1:8891
# ===== Policy-Dienst (postfwd, Abschnitt 7): bei Ausfall 4xx =====
smtpd_policy_service_default_action = 451 4.3.5 Policy-Dienst nicht verfügbar
# ===== Hygiene =====
disable_vrfy_command = yes
smtpd_delay_reject = yes
biff = no
append_dot_mydomain = no
# ===== Ressourcen & Queue =====
message_size_limit = 26214400 # 25 MB, an realen Bedarf anpassen
queue_minfree = 1073741824 # unter 1 GB frei: Annahme mit 4xx pausieren
maximal_queue_lifetime = 5d # überbrückt Ausfall des internen MTA
bounce_queue_lifetime = 1d
smtpd_recipient_limit = 100
smtpd_hard_error_limit = 10
default_process_limit = 100
# ===== Rate-Limits (anvil, pro Client-IP) =====
anvil_rate_time_unit = 60s
smtpd_client_connection_count_limit = 20
smtpd_client_connection_rate_limit = 30
smtpd_client_message_rate_limit = 100
smtpd_client_recipient_rate_limit = 200
smtpd_client_auth_rate_limit = 10
smtpd_client_event_limit_exceptions = $mynetworksSASL ist global abgeschaltet und wird nur in den Submission-Diensten aktiviert. Port 25 bietet damit gar kein AUTH an. In smtpd_recipient_restrictions steht kein permit_sasl_authenticated mehr, denn die Submission-Dienste setzen ihre Restriktionen vollständig selbst. Die globale Liste beschreibt also ausschließlich das Verhalten von Port 25. noplaintext verhindert PLAIN/LOGIN ohne TLS zusätzlich auf SASL-Ebene.
Anvil zählt pro Client-IP. Das schützt Port 25 vor Überlast, begrenzt aber kein einzelnes kompromittiertes Konto. Kontingente pro SASL-Konto regelt Abschnitt 7. Alle Zahlen sind Startwerte; nach einigen Wochen Betrieb an die real gemessenen Mengen anpassen.
Auf Port 25 (ein- wie ausgehend) bleibt TLS opportunistisch, sonst scheitert die Zustellung an Gegenstellen ohne TLS. Verbindliches TLS für bestimmte Partner regeln MTA-STS/DANE und TLS-Policy-Maps (Abschnitt 8). Auf Port 587 ist TLS Pflicht (Abschnitt 4).
Transport- und Empfänger-Maps
example.com smtp:[10.10.0.2]:25Die relay_recipients-Map ist Pflicht: Nur real existierende Empfänger werden bei der SMTP-Annahme akzeptiert. Ohne sie nimmt der Relay alles an und erzeugt später Unzustellbarkeitsmeldungen an gefälschte Absender (Backscatter).
info@example.com OK
kontakt@example.com OK
dmarc@example.com OK
tls-reports@example.com OKDie Empfängerliste muss dem internen Mailserver entsprechen. Bei Postfix intern lässt sie sich per Skript aus dessen Maps erzeugen, bei Exchange per Export der Proxy-Adressen. Ein Platzhalter @example.com OK hebelt den Backscatter-Schutz aus und ist nur als Übergang vertretbar.
postmap /etc/postfix/transport /etc/postfix/relay_recipients
postfix check && systemctl reload postfix04Postfix-Konfiguration: master.cf
Die master.cf setzt die Vertrauenszonen aus Abschnitt 1 um: Port 25 ohne SASL, zwei getrennte Submission-Listener (öffentlich für Anwendungen, Tunnel für den internen MTA) mit jeweils eigenem cleanup-Dienst für die Header-Absenderprüfung.
Override-Zeilen (-o …) mit mindestens einem Leerzeichen einrücken, keine Leerzeichen um das =. Listen innerhalb eines -o mit Kommas ohne Leerzeichen trennen.
# service type private unpriv chroot wakeup maxproc command
# --- Port 25: fremde MX, kein AUTH ---
smtp inet n - y - - smtpd
-o smtpd_sasl_auth_enable=no
# --- Port 587, öffentliche IP: Anwendungen ---
203.0.113.10:submission inet n - y - - smtpd
-o syslog_name=postfix/submission
-o smtpd_tls_security_level=encrypt
-o smtpd_tls_auth_only=yes
-o smtpd_sasl_auth_enable=yes
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
-o smtpd_sender_restrictions=check_sasl_access,pcre:/etc/postfix/sasl_access_public,reject_sender_login_mismatch
-o smtpd_relay_restrictions=permit_sasl_authenticated,reject
-o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
-o smtpd_end_of_data_restrictions=check_policy_service,inet:127.0.0.1:10040
-o cleanup_service_name=cleanup-apps
-o milter_macro_daemon_name=ORIGINATING
# --- Port 587, Tunneladresse: nur interner MTA ---
10.10.0.1:submission inet n - y - - smtpd
-o syslog_name=postfix/submission-wg
-o smtpd_tls_security_level=encrypt
-o smtpd_tls_auth_only=yes
-o smtpd_sasl_auth_enable=yes
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
-o smtpd_sender_restrictions=check_sasl_access,pcre:/etc/postfix/sasl_access_tunnel,reject_sender_login_mismatch
-o smtpd_relay_restrictions=permit_sasl_authenticated,reject
-o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
# Kontingent internal-mta: einziger Client = 10.10.0.2, Anvil zählt hier pro Konto
-o smtpd_client_message_rate_limit=60
-o smtpd_client_recipient_rate_limit=300
-o cleanup_service_name=cleanup-intern
-o milter_macro_daemon_name=ORIGINATING
# --- eigene cleanup-Instanzen für die Header-From-Prüfung (Abschnitt 5) ---
cleanup-apps unix n - y - 0 cleanup
-o header_checks=pcre:/etc/postfix/header_from_apps
cleanup-intern unix n - y - 0 cleanup
-o header_checks=pcre:/etc/postfix/header_from_internAuf beiden Submission-Listenern erzwingt smtpd_tls_security_level=encrypt zusammen mit smtpd_tls_auth_only=yes, dass AUTH erst nach erfolgreichem STARTTLS angeboten wird. Nicht authentifizierte Clients werden schon auf Client-Ebene abgewiesen. Die Reihenfolge in smtpd_sender_restrictions ist gewollt: erst Kontosperre (check_sasl_access), dann Absenderbindung.
Das Konto des internen MTA darf für die ganze Domain senden und ist damit das mächtigste Konto. Es wird am öffentlichen Listener per sasl_access_public gesperrt und ist nur über den Tunnel nutzbar. Werden seine Zugangsdaten gestohlen, sind sie aus dem Internet wertlos. Umgekehrt lässt der Tunnel-Listener nur dieses eine Konto zu.
Optional: SMTPS auf Port 465
Nur aktivieren, wenn eine Anwendung kein STARTTLS beherrscht. Dann zusätzlich ufw allow in on eth0 to 203.0.113.10 port 465 proto tcp.
203.0.113.10:submissions inet n - y - - smtpd
-o syslog_name=postfix/smtps
-o smtpd_tls_wrappermode=yes
-o smtpd_sasl_auth_enable=yes
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
-o smtpd_sender_restrictions=check_sasl_access,pcre:/etc/postfix/sasl_access_public,reject_sender_login_mismatch
-o smtpd_relay_restrictions=permit_sasl_authenticated,reject
-o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
-o smtpd_end_of_data_restrictions=check_policy_service,inet:127.0.0.1:10040
-o cleanup_service_name=cleanup-apps
-o milter_macro_daemon_name=ORIGINATINGOptional: postscreen vor Port 25
postscreen filtert Bots, bevor ein smtpd-Prozess gebunden wird, und entlastet den öffentlichen Eingang bei Spam-Wellen. Dazu die smtp-Zeile ersetzen:
smtp inet n - y - 1 postscreen
smtpd pass - - y - - smtpd
-o smtpd_sasl_auth_enable=no
dnsblog unix - - y - 0 dnsblog
tlsproxy unix - - y - 0 tlsproxypostscreen_access_list = permit_mynetworks
postscreen_greet_action = enforce
# DNSBLs nur mit gültigem Nutzungsrecht (z. B. Spamhaus DQS bei kommerzieller Nutzung)Vollständige Datei prüfen, nicht nur den Auszug
Die Debian-Vorlage enthält weitere Dienste. Vor der Freigabe die tatsächlich aktiven Dienste und Listener auflisten und bewusst bestätigen:
postfix check && systemctl restart postfix
postconf -M | grep -E ' inet ' # alle Netzwerk-Listener
postconf -n # alle vom Default abweichenden Parameter
ss -tlnp | grep master # erwartet: 0.0.0.0:25, 203.0.113.10:587, 10.10.0.1:587Interne unix-Dienste der Vorlage (local, virtual, pipe-Transporte wie maildrop oder uucp) sind nicht von außen erreichbar. Nicht benötigte Pipe-Transporte können trotzdem auskommentiert werden. Die Ausgabe von postconf -n und postconf -M gehört in die Betriebsdokumentation.
05SASL & Absenderidentität
Für einen Relay mit wenigen Absende-Konten ist die Cyrus-SASL-Datenbank (sasldb2) die schlankste Lösung: kein zusätzlicher Daemon, Verwaltung per saslpasswd2. Bei vielen Konten oder vorhandenem IMAP ist Dovecot-SASL die bessere Wahl (siehe Hinweis unten).
apt -y install libsasl2-modules sasl2-binpwcheck_method: auxprop
auxprop_plugin: sasldb
mech_list: PLAIN LOGINPLAIN und LOGIN übertragen das Passwort lediglich base64-kodiert. Dreifach abgesichert: noplaintext außerhalb von TLS (main.cf), smtpd_tls_auth_only=yes und encrypt auf den Submission-Listenern. Der Negativtest T6 in Abschnitt 15 belegt das.
Konten: eins pro Anwendung
Jede Anwendung erhält ein eigenes Konto mit eigenem, zufälligem Passwort (mindestens 32 Zeichen). Interaktive Benutzerpasswörter werden nie wiederverwendet. Nur so lassen sich Missbrauch zuordnen und ein Konto gezielt sperren oder rotieren.
# zufälliges Passwort erzeugen und direkt setzen
openssl rand -base64 33
saslpasswd2 -c -u mail.example.com relay-app
saslpasswd2 -c -u mail.example.com internal-mta
chown root:postfix /etc/sasldb2
chmod 640 /etc/sasldb2
sasldblistusers2Postfix läuft unter Debian im chroot (/var/spool/postfix). Statt sasldb2 dorthin zu kopieren (was auseinanderlaufen kann), wird die Datei per Bind-Mount eingebunden:
install -d /var/spool/postfix/etc
touch /var/spool/postfix/etc/sasldb2
echo '/etc/sasldb2 /var/spool/postfix/etc/sasldb2 none bind 0 0' >> /etc/fstab
systemctl daemon-reload && mount -a
systemctl restart postfixFür jedes Konto wird ein Kontenregister geführt (z. B. in der Betriebsdokumentation): Konto, Anwendung, verantwortliche Person, erlaubte Absender, Kontingent, letzte Rotation. Einmal im Quartal werden ungenutzte Konten gelöscht (saslpasswd2 -d -u mail.example.com <konto>).
Je nach SASL-Backend und Realm meldet Postfix den Login als relay-app oder als relay-app@mail.example.com. Maßgeblich ist der Wert hinter sasl_username= im Log nach der ersten erfolgreichen Anmeldung (journalctl -u postfix@- | grep sasl_username). Damit die Absenderbindung nicht an der Schreibweise scheitert (Anmeldung erfolgreich, Versand trotzdem abgelehnt), führt sender_login unten beide Formen als berechtigte Logins; die PCRE-Maps für Sperren akzeptieren ebenfalls beide. Nach der ersten Anmeldung kann die nicht benötigte Form entfernt werden.
Envelope-Absender an das Konto binden
reject_sender_login_mismatch (bereits in der master.cf) lehnt jede Einlieferung ab, deren MAIL FROM nicht dem angemeldeten Konto gehört. Spezifische Adressen haben Vorrang vor dem Domain-Eintrag.
# Rechte Seite: Liste erlaubter Logins. Beide Schreibweisen (mit/ohne Realm)
# eintragen – dann passt die Map unabhängig davon, wie Postfix den Login meldet.
# Anwendungen: ausschließlich ihre eigenen Adressen
app1@example.com relay-app, relay-app@mail.example.com
noreply@example.com relay-app, relay-app@mail.example.com
# Interner MTA: alle übrigen Adressen der Domain
@example.com internal-mta, internal-mta@mail.example.compostmap /etc/postfix/sender_login && systemctl reload postfixSichtbaren Absender (Header-From) prüfen
Die Envelope-Bindung allein reicht nicht: Ein Konto könnte mit korrektem MAIL FROM einen fremden From:-Header setzen. Diesen Header sieht der Empfänger, nach ihm richten sich DMARC und die DKIM-Signatur. Die beiden cleanup-Instanzen aus Abschnitt 4 prüfen ihn deshalb gegen eine Positivliste:
# Mehrere Adressen im From: grundsätzlich ablehnen
/^From:.*,/ REJECT 5.7.1 Mehrere Absender im From nicht zulaessig
# Erlaubte sichtbare Absender = Adressen der App-Konten aus sender_login
/^From:(.*<|\s*)(app1|noreply)@example\.com>?\s*$/ DUNNO
/^From:/ REJECT 5.7.1 Header-From nicht freigegeben/^From:.*,/ REJECT 5.7.1 Mehrere Absender im From nicht zulaessig
/^From:(.*<|\s*)[^@<>\s]+@example\.com>?\s*$/ DUNNO
/^From:/ REJECT 5.7.1 Header-From ausserhalb von example.comEin Anzeigename mit Komma ("Müller, Hans" <…>) wird abgelehnt. Für Anwendungsmails ist das vertretbar. Automatische Weiterleitungen fremder Mails über den internen MTA nach außen sind damit ebenfalls unterbunden; sie würden ohnehin an DMARC scheitern. Reply-To: und Sender: bleiben frei wählbar. Die Prüfung arbeitet zeilenweise. Zwei getrennte From:-Zeilen, die beide einzeln erlaubt sind, erkennt sie nicht. Solche Nachrichten verstoßen gegen RFC 5322 und werden über RequiredHeaders yes in OpenDKIM abgelehnt (Abschnitt 9, Test T8b). Restrisiko: Die Positivliste gilt für alle App-Konten gemeinsam. Eine strikte Bindung „Header-From gehört genau diesem Konto" erfordert einen Milter mit Zugriff auf den SASL-Login (z. B. Rspamd mit eigener Regel). Für Umgebungen mit vielen Anwendungen unterschiedlicher Vertrauensstufe empfehlen wir das.
Konten sperren und auf Zonen beschränken
# Konto des internen MTA ist aus dem Internet nicht nutzbar
/^internal-mta(@mail\.example\.com)?$/ REJECT 5.7.1 Konto nur ueber den Tunnel zugelassen
# Sperrliste für den Missbrauchsfall (Abschnitt 7), z. B.:
# /^relay-app(@mail\.example\.com)?$/ REJECT 5.7.1 Konto gesperrt/^internal-mta(@mail\.example\.com)?$/ DUNNO
/./ REJECT 5.7.1 Konto am Tunnel-Listener nicht zugelassenPCRE-Maps brauchen kein postmap; nach Änderungen systemctl reload postfix.
Bei vielen Konten oder vorhandenem IMAP smtpd_sasl_type = dovecot und smtpd_sasl_path = private/auth setzen und den Dovecot-Auth-Socket in den chroot einbinden. Absenderbindung, Header-Prüfung und Kontosperren bleiben unverändert.
06Interner MTA: authentifiziert und verifiziert
Der interne Mailserver ist der zweite Endpunkt im Tunnel. Er sendet über den Tunnel-Listener 10.10.0.1:587 und meldet sich mit dem Konto internal-mta an. Er nimmt von außen ausschließlich Verbindungen des Relays auf Port 25 an.
Ausgehend: TLS mit geprüfter Gegenstelle
Verschlüsselung ohne Identitätsprüfung schützt nicht vor einer untergeschobenen Gegenstelle, und an diese gingen die SASL-Zugangsdaten. Der interne MTA verbindet sich deshalb zwar zur Tunnel-IP, prüft aber Kette und Namen des Relay-Zertifikats (mail.example.com):
relayhost = [10.10.0.1]:587
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_sasl_security_options = noanonymous
smtp_sasl_tls_security_options = noanonymous
# TLS-Policy: Zertifikat MUSS gültig sein und auf mail.example.com lauten
smtp_tls_policy_maps = hash:/etc/postfix/tls_policy
smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt
smtp_tls_loglevel = 1
# Nur LAN-intern und am Tunnel lauschen, nie auf einer öffentlichen Adresse
inet_interfaces = 127.0.0.1, 10.10.0.2, 192.168.10.5[10.10.0.1]:587 secure match=mail.example.com[10.10.0.1]:587 internal-mta:<LANGES-ZUFALLSPASSWORT>postmap /etc/postfix/sasl_passwd /etc/postfix/tls_policy
chmod 600 /etc/postfix/sasl_passwd /etc/postfix/sasl_passwd.db
systemctl reload postfix
# Erwartet nach dem ersten Versand: "Verified TLS connection established to 10.10.0.1"
journalctl -u postfix@- | grep -E 'TLS connection established to .*10\.10\.0\.1'Dann ist die Prüfung nicht wirksam (falsches CA-Bundle, falscher Name oder Policy-Map nicht übersetzt), und der Versand muss scheitern. Das ist mit secure das gewollte Verhalten. Niemals auf encrypt zurückfallen, um eine Zertifikatsstörung zu „lösen".
Sendeconnector mit Smarthost 10.10.0.1, Port 587, Basic-Authentifizierung über TLS mit dem internal-mta-Konto. Die Zertifikatsprüfung erzwingt: Set-SendConnector "Relay" -RequireTLS $true -TlsAuthLevel DomainValidation -TlsDomain mail.example.com.
Eingehend: nur der Relay darf auf Port 25
AllowedIPs in WireGuard begrenzt das Routing, ersetzt aber keine Firewall. Auf dem internen MTA lässt das Tunnel-Interface nur SMTP vom Relay zu. Am Internet-Router gibt es keine Portweiterleitung auf Port 25 des internen Servers.
table inet wg0filter {
chain input {
type filter hook input priority filter; policy accept;
iifname "wg0" ct state established,related accept
iifname "wg0" ip saddr 10.10.0.1 tcp dport 25 ct state new accept
iifname "wg0" counter drop
}
chain forward {
type filter hook forward priority filter; policy accept;
iifname "wg0" counter drop # Tunnel führt nicht ins LAN
}
}nft -f /etc/nftables.d/wg0.nft
# dauerhaft: in /etc/nftables.conf per include "/etc/nftables.d/*.nft" einbinden
systemctl enable nftables07Kontingente pro Konto & Missbrauchsfall
Das größte praktische Risiko eines Relays sind gestohlene Zugangsdaten: Damit wird legitim authentifiziert Spam versendet, die IP landet auf Blocklisten und die Domain verliert Reputation. Authentifizierung allein verhindert das nicht. Wirksam sind vier Schichten zusammen:
| Ebene | Kontrolle | Wo |
|---|---|---|
| Netzwerk | Quell-IP-Beschränkung, Verbindungslimits | ufw (Abschnitt 2) |
| SMTP | Anvil-Limits pro IP, Empfänger pro Nachricht | main.cf (Abschnitt 3) |
| Identität | Kontingente pro SASL-Konto, Absenderbindung, Sperrliste | postfwd, Abschnitt 5 |
| Betrieb | Versandvolumen pro Konto überwachen und alarmieren | Abschnitt 14 |
Kontingente mit postfwd
postfwd ist ein schlanker Policy-Dienst. Er wird nur am öffentlichen Submission-Listener nach dem Nachrichtenende befragt (siehe master.cf) und zählt dort pro sasl_username.
apt -y install postfwd# Werte an den realen Bedarf der jeweiligen Anwendung anpassen
id=MSG_STUNDE ; action=rate(sasl_username/100/3600/450 4.7.1 Versandlimit Nachrichten pro Stunde erreicht)
id=RCPT_TAG ; action=rcpt(sasl_username/1000/86400/450 4.7.1 Versandlimit Empfaenger pro Tag erreicht)# Paketvorgaben prüfen (Konfigurationspfad, Port 10040, Autostart)
cat /etc/default/postfwd
systemctl enable --now postfwd
ss -tlnp | grep 10040
postfwd --file /etc/postfix/postfwd.cf --showconfigIst postfwd nicht erreichbar, antwortet Postfix mit 451 4.3.5 (smtpd_policy_service_default_action). Anwendungen stellen dann zurück, aber nichts geht unkontrolliert raus („fail closed"). postfwd gehört deshalb ins Dienst-Monitoring (Abschnitt 14). Die Limits vor der Freigabe mit absichtlich niedrigen Werten testen (Abschnitt 15, T9).
postfwd hält seine Zähler im Arbeitsspeicher. Ein Neustart (Update, Reboot) setzt laufende Kontingente zurück. Ein Angreifer könnte dann kurzzeitig erneut das volle Kontingent ausschöpfen. Das Restrisiko ist begrenzt, weil der Volumen-Alarm (Abschnitt 14) unabhängig von postfwd aus den Postfix-Logs berechnet wird. Nach jedem postfwd-Neustart wird T9 wiederholt (T9b).
Kontingent für das Konto des internen MTA
postfwd arbeitet nur am öffentlichen Listener. Das mächtigste Konto, internal-mta, wird am Tunnel-Listener über die Anvil-Limits begrenzt. Anvil zählt pro Client-IP. Am Tunnel-Listener gibt es genau einen möglichen Client (10.10.0.2), deshalb wirkt das Limit dort faktisch pro Konto, ganz ohne zusätzlichen Dienst. Die Startwerte in der master.cf (60 Nachrichten bzw. 300 Empfänger pro Minute) sind bewusst großzügig für den regulären Mailverkehr eines Unternehmens und bremsen Massenversand.
Die Aussage hat klare Grenzen:
- Sie gilt nur, solange
10.10.0.2der einzige Client des Tunnel-Listeners ist. Dafür sorgenAllowedIPs = 10.10.0.2/32, die ufw-Regel und die Kontenbeschränkung übersasl_access_tunnel. Kommt ein weiterer Peer hinzu, teilen sich alle Konten hinter derselben IP das Limit, und es zählt nicht mehr pro Konto. - Anvil kennt nur ein Zeitfenster (
anvil_rate_time_unit, global 60 s). Das ist eine Bremse gegen Massenversand pro Minute, kein Stunden- oder Tageskontingent. - Die Zähler liegen im Speicher und beginnen nach
postfix reloadbzw. einem Neustart neu. smtpd_client_event_limit_exceptionsdarf10.10.0.2nicht enthalten (Standard hier: nur$mynetworks, also localhost).
Wird der interne Mailserver selbst kompromittiert, kann ein Angreifer innerhalb dieser Grenzen versenden. Das Konto ist von außen gesperrt, schützt also nur gegen gestohlene Zugangsdaten, nicht gegen ein übernommenes internes System. Ausgeglichen wird das durch den Volumen-Alarm pro Konto (Abschnitt 14) und die Sperrmöglichkeit über sasl_access_tunnel. Wer ein hartes Tageslimit braucht, bindet postfwd zusätzlich am Tunnel-Listener ein. Die Entscheidung gehört ins Kontenregister.
Notfallablauf: kompromittiertes Konto
Der Ablauf liegt griffbereit in der Betriebsdokumentation und wird einmal geübt. Wichtig: nicht pauschal die Queue löschen, sondern gezielt nach Konto bzw. Absender vorgehen.
- Konto sperren: Zeile in
/etc/postfix/sasl_access_publicergänzen,systemctl reload postfix. Die Wirkung ist sofort da, die Zugangsdaten bleiben für die Analyse erhalten. - Nachrichten des Kontos anhalten (Hold statt Löschen):
Shell · Queue gezielt behandeln apt -y install jq # alle wartenden Nachrichten der betroffenen Absenderadresse anhalten postqueue -j | jq -r 'select(.sender=="app1@example.com") | .queue_id' | postsuper -h - # nach Prüfung: legitime freigeben ... # postsuper -H <queue_id> # ... missbräuchliche gezielt löschen postqueue -j | jq -r 'select(.queue_name=="hold" and .sender=="app1@example.com") | .queue_id' | postsuper -d - - Umfang feststellen: Versandmenge, Zeitraum und Empfänger aus dem Log (
journalctl -u postfix@- --since … | grep 'sasl_username=relay-app'), Quell-IPs der Anmeldungen. - Zugangsdaten rotieren:
saslpasswd2 -u mail.example.com relay-app, neues Passwort nur an die verantwortliche Person der Anwendung. - Reputation prüfen: Blocklisten-Status der Relay-IP, DMARC-Reports der folgenden Tage. Ggf. Delisting beantragen.
- Ursache beheben (Leck in der Anwendung, Konfigurationsdatei, Repository), dann Sperre zurücknehmen und Kontingent für einige Tage enger setzen.
- Dokumentieren: Zeitlinie, Auswirkung, Maßnahmen. Bei personenbezogenen Daten prüfen, ob eine Meldepflicht nach Art. 33 DSGVO besteht.
08Öffentliche DNS-Einträge
| Typ | Name | Wert (Beispiel) | Zweck |
|---|---|---|---|
A | mail.example.com | 203.0.113.10 | Adresse des Relays |
MX | example.com | 10 mail.example.com. | Eingang für die Domain |
PTR | (Rückwärtszone der IP) | mail.example.com | Reverse-DNS, beim Hoster setzen |
TXT | example.com | v=spf1 a:mail.example.com -all | SPF |
TXT | mail2026._domainkey | v=DKIM1; k=rsa; p=… | DKIM (Abschnitt 9) |
TXT | _dmarc.example.com | v=DMARC1; p=none; … | DMARC, stufenweise verschärfen |
CAA | example.com | 0 issue "letsencrypt.org" | zulässige Zertifizierungsstelle |
Der PTR der Relay-IP muss exakt mail.example.com ergeben, und mail.example.com muss wieder auf dieselbe IP zeigen. Ein fehlender oder unpassender PTR ist der häufigste Grund für Spam-Einstufung.
SPF: die gesamte Versandarchitektur abbilden
SPF prüft die Domain des Envelope-Absenders (MAIL FROM), nicht den sichtbaren From:. -all ist nur richtig, wenn wirklich jeder legitime Versandweg erfasst ist. Versendet die Domain zusätzlich über Microsoft 365, einen Newsletter-Dienst oder ein Ticketsystem, gehören deren Mechanismen in den Eintrag:
# nur der Relay
example.com. IN TXT "v=spf1 a:mail.example.com -all"
# Relay + Microsoft 365 (Beispiel)
# example.com. IN TXT "v=spf1 a:mail.example.com include:spf.protection.outlook.com -all"DMARC: auf Basis realer Daten verschärfen
Strikte Alignment-Werte und quarantine/reject sind das Ziel, aber erst, wenn die Reports zeigen, dass alle legitimen Quellen sauber authentifizieren:
# Stufe 1 (2–4 Wochen): nur beobachten, Reports sammeln
_dmarc.example.com. IN TXT "v=DMARC1; p=none; rua=mailto:dmarc@example.com; fo=1"
# Stufe 2: Quarantäne, nachdem alle Quellen SPF- bzw. DKIM-aligned sind
_dmarc.example.com. IN TXT "v=DMARC1; p=quarantine; rua=mailto:dmarc@example.com; fo=1; adkim=s; aspf=s"
# Stufe 3: Ablehnung
_dmarc.example.com. IN TXT "v=DMARC1; p=reject; rua=mailto:dmarc@example.com; fo=1; adkim=s; aspf=s"Die Report-Adresse dmarc@example.com muss existieren (siehe relay_recipients), und jemand muss die Reports auswerten, z. B. mit einem DMARC-Report-Parser. Ungelesene Reports bringen keinen Erkenntnisgewinn.
dig +short MX example.com
dig +short TXT example.com
dig +short TXT mail2026._domainkey.example.com
dig +short TXT _dmarc.example.com
dig +short -x 203.0.113.10 && dig +short A mail.example.comErzwungene Transportsicherheit: MTA-STS, DANE, TLS-RPT
MTA-STS und DANE sind keine austauschbaren Varianten: DANE setzt DNSSEC voraus und bindet das Zertifikat kryptografisch an DNS. MTA-STS kommt ohne DNSSEC aus, braucht aber eine per HTTPS ausgelieferte Policy. Beide schützen eingehende Zustellung zu Ihrem MX gegen STARTTLS-Downgrade.
| Mechanismus | Voraussetzung | Betriebsaufgabe |
|---|---|---|
| MTA-STS | HTTPS unter mta-sts.example.com + TXT _mta-sts | Policy-id bei jeder Änderung hochzählen; max_age bestimmt, wie lange Sender die Policy cachen |
| DANE (TLSA) | DNSSEC für die Zone, TLSA-Record | Bei jedem Schlüsselwechsel TLSA vorher veröffentlichen (Rollover), sonst Zustellausfall |
| TLS-RPT | TXT _smtp._tls | Reports über fehlgeschlagene TLS-Zustellungen auswerten |
_mta-sts.example.com. IN TXT "v=STSv1; id=20261009T000000;"
_smtp._tls.example.com. IN TXT "v=TLSRPTv1; rua=mailto:tls-reports@example.com"version: STSv1
mode: testing
mx: mail.example.com
max_age: 86400Mit mode: testing starten und die TLS-RPT-Reports einige Wochen beobachten. Erst dann auf mode: enforce und ein längeres max_age (z. B. 604800) wechseln.
Für einzelne Partner mit vereinbarter Verschlüsselung: smtp_tls_policy_maps mit partner.example secure. Fremde MTA-STS-Policies setzt postfix-mta-sts-resolver als Policy-Map durch; DANE-Prüfung ausgehend aktiviert smtp_tls_security_level = dane mit einem DNSSEC-validierenden lokalen Resolver (z. B. unbound).
09DKIM mit OpenDKIM
OpenDKIM signiert ausgehende und prüft eingehende Mail. Postfix spricht den Milter über inet:127.0.0.1:8891 an; der Socket ist nur auf Loopback erreichbar. Der Selector trägt das Jahr (mail2026), damit eine Rotation ohne Namenskonflikt möglich ist.
apt -y install opendkim opendkim-tools
install -d -o opendkim -g opendkim -m 750 /etc/opendkim/keys/example.com
opendkim-genkey -b 2048 -d example.com -D /etc/opendkim/keys/example.com -s mail2026 -v
chown opendkim:opendkim /etc/opendkim/keys/example.com/mail2026.*
chmod 600 /etc/opendkim/keys/example.com/mail2026.privateSyslog yes
UMask 007
Mode sv
Canonicalization relaxed/simple
Socket inet:8891@127.0.0.1
OversignHeaders From
# RFC 5322: genau ein From:, höchstens ein Subject: usw. – sonst Ablehnung
RequiredHeaders yes
KeyTable /etc/opendkim/KeyTable
SigningTable refile:/etc/opendkim/SigningTable
# Signieren nur für Einlieferungen über die Submission-Listener
# (milter_macro_daemon_name=ORIGINATING) bzw. lokal erzeugte Mail
MTA ORIGINATING
InternalHosts /etc/opendkim/TrustedHosts
ExternalIgnoreList /etc/opendkim/TrustedHosts# KeyTable
mail2026._domainkey.example.com example.com:mail2026:/etc/opendkim/keys/example.com/mail2026.private
# SigningTable
*@example.com mail2026._domainkey.example.com
# TrustedHosts: nur der Relay selbst
127.0.0.1
::1Signiert wird nur Mail, die über einen der Submission-Listener authentifiziert eingeliefert wurde (MTA ORIGINATING) oder lokal entsteht, und auch dann nur mit Absender @example.com (SigningTable). Mail fremder MX auf Port 25 wird ausschließlich geprüft, auch wenn sie einen gefälschten From: …@example.com trägt. Der interne MTA steht bewusst nicht in TrustedHosts: Sein Signaturrecht ergibt sich aus der Anmeldung, nicht aus der IP. Belegt wird das durch die Negativtests T11 und T12 in Abschnitt 15.
systemctl enable --now opendkim
systemctl restart postfix
ss -tlnp | grep 8891 # erwartet: nur 127.0.0.1:8891
cat /etc/opendkim/keys/example.com/mail2026.txt # TXT-Inhalt für die DNS-Zone
# nach DNS-Propagation:
opendkim-testkey -d example.com -s mail2026 -vvv # erwartet: "key OK"Verhalten bei Ausfall
RequiredHeaders yes prüft die von RFC 5322 geforderte Anzahl an Kopfzeilen, etwa genau ein From:. Das gilt auch eingehend; große Anbieter weisen solche Mails ohnehin zurück.
Laut Handbuchseite (man opendkim.conf) werden nicht konforme Nachrichten abgewiesen. Dieselbe Seite nennt bei IgnoreMalformedMail aber als Standard, solche Nachrichten durchzulassen und nur ein Authentication-Results-Feld zu ergänzen. Welche Regel greift, entscheidet die installierte OpenDKIM-Version. Maßgeblich ist deshalb Test T8b. Wird die Nachricht dort nicht abgewiesen, bleibt die doppelte From-Zeile auf Kombinationen bereits erlaubter Adressen beschränkt. Strikt verhindern lässt sie sich dann nur mit einem Milter, der Kopfzeilen zählt (z. B. Rspamd, Symbol MULTIPLE_FROM).
Mit milter_default_action = tempfail gilt: Ist OpenDKIM nicht erreichbar, beantwortet Postfix jede Einlieferung mit 451 4.7.1. Ausgehend geht damit nie unsignierte Mail raus. Eingehend stellen fremde MX später erneut zu, ein Verlust entsteht nicht. Der Preis ist Verfügbarkeit: OpenDKIM wird überwacht und bei Bedarf automatisch neu gestartet.
[Service]
Restart=on-failure
RestartSec=5sSchlüsselrotation
Jährlich sowie sofort bei Verdacht auf Kompromittierung:
- Neuen Schlüssel mit neuem Selector erzeugen (z. B.
mail2027), TXT-Record veröffentlichen, TTL abwarten,opendkim-testkeyprüfen. KeyTableundSigningTableauf den neuen Selector umstellen,systemctl restart opendkim, Testmail prüfen (dkim=pass,s=mail2027).- Alten Selector im DNS noch etwa 7 Tage stehen lassen (Mails in Zustellung), dann entfernen. Bei Kompromittierung stattdessen sofort mit leerem Schlüssel widerrufen:
"v=DKIM1; p=". - Alten privaten Schlüssel sicher löschen, auch aus Backups gemäß Aufbewahrungsrichtlinie.
10TLS-Zertifikate mit acme.sh
acme.sh ist ein schlanker ACME-Client ohne Python-Abhängigkeiten. Für einen Mailserver ist die DNS-01-Validierung ideal, denn Port 80 bleibt dauerhaft geschlossen.
apt -y install curl socat git
git clone --depth 1 https://github.com/acmesh-official/acme.sh.git /root/acme.sh-src
cd /root/acme.sh-src && ./acme.sh --install -m admin@example.com
acme.sh --set-default-ca --server letsencryptDNS-API-Token minimal berechtigen
Das DNS-API-Token kann die Zone verändern und damit MX, SPF und DKIM. Es ist ein hochwertiges Geheimnis:
- Dediziertes Token nur für ACME, beschränkt auf DNS-Bearbeitung genau dieser Zone (bei Cloudflare: Zone → DNS → Edit für
example.com); wenn der Anbieter es kann, zusätzlich auf die IP des Relays. - Nicht in
.bashrcoder geteilten Shell-Profilen speichern. acme.sh legt es nach dem ersten Lauf in/root/.acme.sh/account.confab; Rechte600prüfen. - Bei Verdacht auf Abfluss: Token beim DNS-Anbieter widerrufen und Zone auf unerwartete Änderungen prüfen.
read -rs CF_Token && export CF_Token # Token nicht in die Shell-History
acme.sh --issue --dns dns_cf -d mail.example.com --keylength 2048 # RSA: breiteste Kompatibilität
unset CF_Token
chmod 600 /root/.acme.sh/account.confinstall -d -m 750 /etc/postfix/tls/mail.example.com
acme.sh --install-cert -d mail.example.com \
--key-file /etc/postfix/tls/mail.example.com/private.key \
--fullchain-file /etc/postfix/tls/mail.example.com/fullchain.cer \
--reloadcmd "chmod 600 /etc/postfix/tls/mail.example.com/private.key && systemctl reload postfix"Erneuerung ist erst erledigt, wenn Postfix das neue Zertifikat ausliefert
acme.sh erneuert per Cron-Job automatisch. Geprüft wird aber, was tatsächlich ausgeliefert wird: Fingerprint auf der Leitung und Fingerprint der Datei müssen übereinstimmen.
openssl s_client -starttls smtp -connect mail.example.com:587 -servername mail.example.com </dev/null 2>/dev/null \
| openssl x509 -noout -fingerprint -sha256 -enddate
openssl x509 -in /etc/postfix/tls/mail.example.com/fullchain.cer -noout -fingerprint -sha256 -enddateBei DANE gilt zusätzlich: acme.sh behält den privaten Schlüssel bei Erneuerung standardmäßig bei. Das passt zu einem TLSA-Record vom Typ 3 1 1 (Hash des öffentlichen Schlüssels). --always-force-new-domain-key darf dann nicht gesetzt sein; ein bewusster Schlüsselwechsel erfordert vorher einen zusätzlichen TLSA-Record.
11WireGuard-Tunnel & Tunnel-Firewall
Der Tunnel verbindet Relay und internen MTA verschlüsselt, ohne den internen Server im Internet zu exponieren. Der Relay hat die öffentliche IP und ist Endpoint; der interne Server baut die Verbindung aktiv auf.
apt -y install wireguard
umask 077
wg genkey | tee /etc/wireguard/privatekey | wg pubkey > /etc/wireguard/publickey
chmod 600 /etc/wireguard/privatekey
cat /etc/wireguard/publickey # nur den ÖFFENTLICHEN Schlüssel weitergeben[Interface]
Address = 10.10.0.1/24
ListenPort = 51820
PrivateKey = <RELAY_PRIVATE_KEY>
[Peer]
# interner Mailserver – genau eine Adresse
PublicKey = <INTERN_PUBLIC_KEY>
AllowedIPs = 10.10.0.2/32[Interface]
Address = 10.10.0.2/24
PrivateKey = <INTERN_PRIVATE_KEY>
[Peer]
# öffentlicher Relay
PublicKey = <RELAY_PUBLIC_KEY>
Endpoint = mail.example.com:51820
AllowedIPs = 10.10.0.1/32
PersistentKeepalive = 25chmod 600 /etc/wireguard/wg0.conf
systemctl enable --now wg-quick@wg0
wg show # "latest handshake" muss aktuell sein
nc -zv -w3 10.10.0.2 25 # vom Relay: offen
nc -zv -w3 10.10.0.2 22 # vom Relay: muss scheiternWer darf was im Tunnel
| Richtung | Erlaubt | Durchgesetzt durch |
|---|---|---|
| Relay → interner MTA | TCP 25 (eingehende Mail) | nftables auf dem internen MTA (Abschnitt 6) |
| Interner MTA → Relay | TCP 587 auf 10.10.0.1 (ausgehende Mail) | ufw auf dem Relay (Abschnitt 2) |
| Alles andere im Tunnel | verworfen | Default-Deny auf beiden Seiten |
Tunneladressen über eth0 | verworfen | ufw deny in on eth0 to 10.10.0.0/24, rp_filter |
| Routing Tunnel ↔ Internet/LAN | keins | ip_forward = 0 (Relay), Forward-Drop (intern) |
Private Schlüssel verlassen ihren Host nicht: nicht in Git, nicht in unverschlüsselten Backups. Bei Verlust oder Kompromittierung eines Endpunkts den Peer auf der Gegenseite sofort entfernen (wg set wg0 peer <PUBKEY> remove und Eintrag aus wg0.conf löschen), auf beiden Seiten neue Schlüsselpaare erzeugen und das SASL-Passwort von internal-mta rotieren. Ist die öffentliche IP des internen Standorts fest, Port 51820/udp in ufw auf diese IP beschränken.
12fail2ban: zusätzliche Schicht, kein Zugriffskonzept
fail2ban sperrt IPs nach wiederholten Fehlanmeldungen und reduziert so Lärm und Brute-Force-Last. Es schützt aber nicht vor verteilten Angriffen, nicht vor bereits gestohlenen Zugangsdaten (die melden sich ja erfolgreich an) und kann hinter NAT legitime Clients treffen. Die eigentliche Kontrolle leisten Authentifizierung, Absenderbindung, Kontingente und Monitoring.
apt -y install fail2ban[DEFAULT]
bantime = 1h
bantime.increment = true
findtime = 10m
maxretry = 5
# nur der Relay selbst und der interne MTA – nicht das ganze Tunnelnetz
ignoreip = 127.0.0.1/8 10.10.0.2/32
backend = systemd
[sshd]
enabled = true
# Debian: Postfix loggt unter der Unit postfix@-.service
[postfix]
enabled = true
mode = aggressive
journalmatch = _SYSTEMD_UNIT=postfix@-.service
[postfix-sasl]
enabled = true
journalmatch = _SYSTEMD_UNIT=postfix@-.servicesystemctl enable --now fail2ban
fail2ban-client status postfix-sasl
# Nach Negativtest T5 (falsches Passwort) muss "Total failed" steigen.
# Bleibt der Zähler bei 0, liest das Jail die falsche Logquelle.Ohne den journalmatch auf postfix@-.service sucht der Postfix-Filter unter Debian in der falschen Unit. Das Jail ist dann aktiv, sperrt aber nie. Deshalb gehört die Wirksamkeitsprüfung zur Abnahme.
13Logs & Aufbewahrung
Debian 13 protokolliert standardmäßig ausschließlich ins systemd-Journal. Die Aufbewahrung wird dort festgelegt. Sie dient der Fehleranalyse und Missbrauchserkennung und ist zugleich datenschutzrechtlich begrenzt (Abschnitt 18).
[Journal]
Storage=persistent
MaxRetentionSec=30day
SystemMaxUse=2Gsystemctl restart systemd-journald
journalctl --disk-usageOptional: klassische Logdateien mit rsyslog
Wer /var/log/mail.log bevorzugt, installiert rsyslog und begrenzt die Dateien ebenfalls auf 30 Tage:
/var/log/mail.log /var/log/mail.err /var/log/mail.warn {
daily
rotate 30
missingok
notifempty
compress
delaycompress
sharedscripts
postrotate
/usr/lib/rsyslog/rsyslog-rotate 2>/dev/null || systemctl kill -s HUP rsyslog.service
endscript
}Debian bringt in /etc/logrotate.d/rsyslog eigene Regeln für die Mail-Logs mit. Dort die /var/log/mail.*-Zeilen entfernen. Prüfen mit logrotate -d /etc/logrotate.d/postfix-relay. Gelten zwei Speicherorte (Journal und Dateien), gilt die 30-Tage-Grenze für beide.
14Monitoring & Alarmierung
Monitoring erkennt zwei Dinge: Betriebsstörungen und Missbrauch. Ein Alarm ist nur wirksam, wenn er unabhängig vom Relay zugestellt wird und eine verantwortliche Person ihn bearbeitet.
| Metrik | Alarm (Startwert) | Hinweis auf |
|---|---|---|
Dienste postfix@-, opendkim, postfwd, wg-quick@wg0, fail2ban | nicht aktiv | Ausfall; bei OpenDKIM/postfwd stockt jede Einlieferung |
| Queue-Länge | > 200 Nachrichten | Zustellproblem oder Missbrauch |
| Alter der ältesten Nachricht | > 2 h | interner MTA/Tunnel unten, Blockierung bei Empfängern |
| Versand pro SASL-Konto | > 3× Tagesdurchschnitt | kompromittiertes Konto |
| SASL-Fehlversuche | > 50 / h | Brute-Force, falsch konfigurierte Anwendung |
| Anteil 4xx/5xx bei Zustellung | > 10 % über 1 h | Reputationsproblem, Blocklisteneintrag |
Freier Speicher /var/spool/postfix | < 20 % | Queue läuft voll (bei queue_minfree nimmt Postfix nichts mehr an) |
| Zertifikat | < 21 Tage gültig oder Fingerprint-Abweichung | Erneuerung fehlgeschlagen oder nicht ausgeliefert |
| WireGuard-Handshake | älter als 5 min | Tunnel unterbrochen |
| Blocklisten der Relay-IP | neuer Eintrag | Reputationsverlust |
# Queue-Länge
postqueue -j | wc -l
# Alter der ältesten Nachricht in Sekunden
postqueue -j | jq -s 'if length==0 then 0 else (now - (map(.arrival_time)|min)) | floor end'
# Nachrichten pro SASL-Konto in der letzten Stunde: genau eine Zeile
# "<QUEUE-ID>: client=…, sasl_method=…, sasl_username=…" je eingelieferter Nachricht
journalctl -u postfix@- --since "1 hour ago" -o cat \
| grep -E '^[0-9A-Za-z]+: client=.*, sasl_username=' \
| grep -o 'sasl_username=[^, ]*' | sort | uniq -c | sort -rn
# Empfänger pro Konto: Zustellzeilen über die Queue-ID dem Konto zuordnen
journalctl -u postfix@- --since "1 hour ago" -o cat | awk '
/: client=.*sasl_username=/ { id=$1; sub(/:$/,"",id); match($0,/sasl_username=[^, ]*/)
user[id]=substr($0,RSTART+14,RLENGTH-14) }
/: to=.*status=sent/ { id=$1; sub(/:$/,"",id); if (id in user) n[user[id]]++ }
END { for (u in n) print n[u], u }' | sort -rnDie erste Zahl misst Einlieferungen, auch solche, die später an Header-Prüfung oder Kontingent scheitern. Für den Missbrauchsalarm ist das gewollt, denn auch abgewiesene Versuche zeigen einen Angriff. Die zweite misst tatsächlich zugestellte Empfänger. Für den Alarm wird der Mittelwert der vergangenen 14 Tage je Konto als Vergleich herangezogen.
# SASL-Fehlversuche der letzten Stunde
journalctl -u postfix@- --since "1 hour ago" -o cat | grep -c 'SASL .* authentication failed'
# Sekunden seit letztem WireGuard-Handshake
echo $(( $(date +%s) - $(wg show wg0 latest-handshakes | awk '{print $2}') ))
# Zertifikat: Restlaufzeit < 21 Tage?
openssl x509 -checkend $((21*86400)) -noout -in /etc/postfix/tls/mail.example.com/fullchain.cerDrei Regeln für wirksame Alarme
- Unabhängiger Alarmweg: Alarme dürfen nicht nur per Mail über diesen Relay laufen. Geeignet sind ein externer Überwachungsdienst, der den Relay von außen prüft, und ein „Dead-Man-Switch": Der Relay meldet sich regelmäßig, und bleibt die Meldung aus, alarmiert der externe Dienst per Push oder SMS.
- Verantwortlichkeit: Für jeden Alarm sind Empfänger, Vertretung und Reaktionszeit festgelegt. Ein Alarm ohne Zuständigen schützt nicht.
- Handlungsorientierung: Ein Volumen-Alarm nennt Konto, Zeitraum, Menge und die verantwortliche Anwendung laut Kontenregister. So kann der Notfallablauf aus Abschnitt 7 sofort starten.
Für dauerhaftes Monitoring eignen sich prometheus-postfix-exporter mit Alertmanager oder eine schlanke Cron-Lösung, die obige Werte gegen Schwellen prüft und über einen externen Kanal meldet. Für die IP-Reputation zusätzlich die Postmaster-Portale großer Mailanbieter nutzen.
15Abnahme & Negativtests
Ein erfolgreicher Versandtest beweist nur, dass Erlaubtes funktioniert. Für die Freigabe zählt ebenso, dass Verbotenes zuverlässig scheitert. Die Tests laufen vor der Produktivnahme, nach jeder Konfigurationsänderung und nach Updates. Externe Tests von einem Host außerhalb von Management-Netz und Tunnel ausführen.
| # | Test | Erwartet |
|---|---|---|
| T1 | Extern, Port 25, an fremde Domain | 554 5.7.1 … Relay access denied |
| T2 | Extern, Port 25, an nicht existierenden Empfänger in example.com | 550 5.1.1 … User unknown in relay recipient table |
| T3 | Port 25: EHLO nach STARTTLS | kein AUTH angeboten |
| T4 | Port 587 mit TLS, ohne Anmeldung | 554 5.7.1 … Access denied |
| T5 | Port 587, falsches Passwort | 535 5.7.8; fail2ban-Zähler steigt |
| T6a | Port 587 ohne STARTTLS: EHLO | kein AUTH angeboten |
| T6b | Port 587 ohne STARTTLS: MAIL FROM | 530 5.7.0 Must issue a STARTTLS command first |
| T7 | Gültiges App-Konto, fremder Envelope-Absender | 553 5.7.1 … not owned by user |
| T8 | Gültiges App-Konto, eigener Envelope-, fremder Header-Absender | 550 5.7.1 Header-From nicht freigegeben |
| T8b | Gültiges App-Konto, zwei getrennte, jeweils erlaubte From:-Zeilen | Ablehnung (5xx) durch OpenDKIM RequiredHeaders |
| T9 | Kontingent testweise auf 3/h setzen, 4 Nachrichten senden | 4. Nachricht 450 4.7.1 Versandlimit … |
| T9b | Wie T9, dazwischen systemctl restart postfwd | Verhalten dokumentieren (Zähler zurückgesetzt); Volumen-Alarm greift trotzdem |
| T9c | Interner MTA: mehr als 60 Nachrichten in einer Minute | Überschuss 450 4.7.1 Error: too much mail from 10.10.0.2 bzw. bei Empfängern … too many recipients from …; interner MTA stellt später zu, Log: warning: Message delivery request rate limit exceeded |
| T10 | Konto internal-mta am öffentlichen Listener | 554 5.7.1 Konto nur ueber den Tunnel zugelassen |
| T11 | Extern über Port 25 an info@example.com mit gefälschtem From: chef@example.com | angenommen, aber ohne DKIM-Signature d=example.com; dmarc=fail im Header |
| T12 | Regulärer App-Versand an externes Testpostfach | spf=pass, dkim=pass (s=mail2026), dmarc=pass |
| T13 | systemctl stop opendkim, dann App-Versand | 451 4.7.1, nach Start wird zugestellt; kein unsignierter Versand |
| T14 | Vom Relay: nc -zv 10.10.0.2 22; aus dem Internet: Port 25 der internen Standort-IP | beides geschlossen bzw. Timeout |
| T15 | Versand vom internen MTA | Log intern: Verified TLS connection established to 10.10.0.1 |
| T16 | Bounce des internen MTA (leerer Absender <>) über den Tunnel | wird angenommen und zugestellt |
| T17 | Portscan von fremder IP: nmap -Pn -p 22,25,80,465,587,8891,10040 mail.example.com | nur 25 und 587 offen |
Automatisierter Selbsttest (extern)
Die rein protokollbasierten Tests laufen als Skript. Bis auf T8 und T8b bricht es vor dem Versand ab. T8 und T8b senden an ein eigenes, kontrolliertes Testpostfach (TEST_RCPT). Bei korrekter Konfiguration kommt dort nichts an; kommt doch etwas an, ist der Test fehlgeschlagen und die Mail bleibt im eigenen Haus. Die übrigen Tests (T9–T17) sind manuell und bleiben Teil der Abnahme.
#!/usr/bin/env bash
# Aufruf: APP_PASS='…' TEST_RCPT='selftest@eigene-testdomain.example' ./relay-selftest.sh
# Benötigt swaks. TEST_RCPT: eigenes Testpostfach außerhalb von example.com
set -u
: "${APP_PASS:?APP_PASS fehlt}" "${TEST_RCPT:?TEST_RCPT fehlt}"
H=mail.example.com; APP=relay-app; FAIL=0
X=probe@example.net # fremde Domain, wird nie erreicht
expect() { local n=$1 p=$2; shift 2
if swaks --server "$H" "$@" 2>&1 | grep -Eq -- "$p"; then echo "OK $n"
else echo "FEHLER $n (erwartet: $p)"; FAIL=1; fi; }
expect_no() { local n=$1 p=$2; shift 2
if swaks --server "$H" "$@" 2>&1 | grep -Eq -- "$p"; then echo "FEHLER $n (unerwartet: $p)"; FAIL=1
else echo "OK $n"; fi; }
expect "T1 kein Open Relay" 'Relay access denied' --port 25 --from "$X" --to ziel@example.org --quit-after RCPT
expect "T2 Empfaengerpruefung" '<\*\* +550' --port 25 --from "$X" --to gibtsnicht@example.com --quit-after RCPT
expect_no "T3 kein AUTH auf 25" '250[- ]AUTH' --port 25 -tls --quit-after EHLO
expect "T4 587 ohne Login" '<\*\* +554' --port 587 -tls --from app1@example.com --to "$X" --quit-after RCPT
expect "T5 falsches Passwort" '<\*\* +535' --port 587 -tls --auth LOGIN --auth-user "$APP" --auth-password falsch --quit-after AUTH
expect_no "T6a kein AUTH ohne TLS" '250[- ]AUTH' --port 587 --quit-after EHLO
expect "T6b kein Versand ohne TLS" '<\*\* +530' --port 587 --from app1@example.com --to "$X" --quit-after RCPT
expect "T7 Envelope-Bindung" 'not owned by user' --port 587 -tls --auth LOGIN --auth-user "$APP" --auth-password "$APP_PASS" \
--from info@example.com --to "$X" --quit-after RCPT
expect "T8 Header-From-Pruefung" 'Header-From nicht' --port 587 -tls --auth LOGIN --auth-user "$APP" --auth-password "$APP_PASS" \
--from app1@example.com --to "$TEST_RCPT" --header 'From: chef@example.com'
expect "T8b nur ein From erlaubt" '<\*\* +5[0-9][0-9]' --port 587 -tls --auth LOGIN --auth-user "$APP" --auth-password "$APP_PASS" \
--from app1@example.com --to "$TEST_RCPT" \
--header 'From: app1@example.com' --add-header 'From: noreply@example.com'
exit $FAILAuf einem Listener mit smtpd_tls_security_level=encrypt prüft Postfix den TLS-Zwang schon beim Kommando MAIL FROM und antwortet 530 5.7.0. Die Client-Restriktion greift wegen smtpd_delay_reject = yes erst bei RCPT TO, also später. T6a prüft unabhängig davon, dass vor STARTTLS gar kein AUTH angeboten wird.
Die Ausgabe des Selbsttests und die Ergebnisse der manuellen Tests T9–T17 mit Datum ablegen. Das ist der Nachweis für die Abnahme und für jede spätere Änderung.
Abnahmecheckliste
| Bereich | Kriterium | Nachweis |
|---|---|---|
| Sicherheit | Kein Open Relay, keine anonyme Submission, AUTH nur über TLS | T1, T3–T6b |
| Envelope- und Header-Absender gebunden, genau ein From: | T7, T8, T8b | |
| Kontingente (auch internal-mta) und Kontosperre wirksam | T9–T9c, T10 | |
| Interner MTA nicht öffentlich, Tunnel minimal | T14, T17 | |
| Interne TLS-Verbindung mit geprüfter Gegenstelle | T15 | |
| Nur 25 und 587 öffentlich; IPv6 aus oder vollständig abgesichert | T17, ss -tlnp | |
| Zustellung | PTR/Forward konsistent, SPF vollständig, DKIM und DMARC pass | Abschnitt 8, T12 |
| Eingehende Fälschungen nicht signiert | T11 | |
| Backscatter-Schutz durch Empfängerprüfung | T2 | |
| Betrieb | Milter-Ausfall definiert (tempfail) | T13 |
| Monitoring alarmiert extern, Zuständigkeit benannt | Testalarm ausgelöst | |
| Zertifikatserneuerung geprüft (ausgeliefertes Zertifikat) | Fingerprint-Vergleich, Abschnitt 10 | |
| Backup vorhanden und Wiederherstellung erprobt | Abschnitt 16 | |
| Notfallablauf für kompromittierte Konten dokumentiert und geübt | Abschnitt 7 |
16Backup, Wiederherstellung & Ausfallszenarien
Was gesichert wird
/etc/postfix/ # main.cf, master.cf, Maps, Header-Checks, postfwd.cf, TLS-Policy
/etc/sasldb2 # SASL-Konten (Geheimnis)
/etc/opendkim/ /etc/opendkim.conf # inkl. privater DKIM-Schlüssel (Geheimnis)
/etc/wireguard/ # inkl. privatem Schlüssel (Geheimnis)
/root/.acme.sh/ # ACME-Konto und DNS-Token (Geheimnis)
/etc/ufw/ /etc/sysctl.d/ /etc/ssh/sshd_config.d/ /etc/fail2ban/jail.local
/etc/systemd/journald.conf.d/ /etc/systemd/system/*.d/
# plus: Ausgabe von "postconf -n" und "postconf -M", Paketliste (dpkg --get-selections)- Backups sind verschlüsselt und zugriffsbeschränkt, denn sie enthalten sämtliche Geheimnisse des Relays. Die Schlüssel für die Backup-Verschlüsselung werden getrennt vom Backup verwahrt.
- Die Mail-Queue wird nicht gesichert. Sie ist flüchtig, und nach einem Ausfall stellen einliefernde Server erneut zu.
- Bei VMs/LXC zusätzlich Snapshots (z. B.
vzdump) vor Wartungsfenstern.
Wiederherstellung erproben
Mindestens jährlich ein Ersatzsystem aus dem Backup aufsetzen (isoliert, mit Test-IP) und belegen: Die erwarteten Listener lauschen, TLS und Anmeldung funktionieren, der Selbsttest aus Abschnitt 15 läuft fehlerfrei, DKIM signiert, der Tunnel steht. Die benötigte Zeit wird notiert; sie ist die realistische Wiederherstellungszeit.
Ausfallszenarien
| Szenario | Auswirkung | Reaktion |
|---|---|---|
| Relay fällt aus | Kein Ein- und Ausgang. Fremde MX stellen bis zu mehreren Tagen erneut zu, interner MTA und Apps stellen zurück | Wiederherstellung aus Backup; MX-TTL niedrig halten (z. B. 3600 s), um auf Ersatz-IP umzuschwenken (PTR beachten) |
| Interner MTA / Tunnel fällt aus | Eingang sammelt sich in der Relay-Queue (bis maximal_queue_lifetime = 5 d) | Queue-Alter überwachen; nach Wiederkehr postqueue -f |
| OpenDKIM fällt aus | Alle Einlieferungen 4xx | Automatischer Neustart, Alarm; kein Umstellen auf accept |
| postfwd fällt aus | App-Submission 4xx | Neustart, Alarm |
| Zertifikat abgelaufen | Submission bricht ab; interner MTA verweigert (secure) | Erneuern, Fingerprint prüfen; Ursache (Token, DNS) beheben |
| Dateisystem voll | Postfix nimmt mit 4xx nichts mehr an (queue_minfree) | Ursache (Missbrauch? Zustellstau?) klären, gezielt bereinigen |
| SASL-Konto kompromittiert | Spam, Reputationsverlust | Notfallablauf Abschnitt 7 |
| Admin-Zugang verloren | Keine Wartung möglich | Notfallzugang über die Konsole des Hosters, hinterlegter Zweitschlüssel |
17Troubleshooting
| Symptom | Wahrscheinliche Ursache | Ansatz |
|---|---|---|
Relay access denied | Einlieferung auf Port 25 statt 587 | Client auf 587 mit STARTTLS und Anmeldung umstellen |
SASL authentication failed | sasldb2 im chroot nicht lesbar, falscher Realm | Bind-Mount prüfen (findmnt /var/spool/postfix/etc/sasldb2); Realm = myhostname |
Sender address rejected: not owned by user | Absender fehlt in sender_login oder Login-Schreibweise falsch | sasl_username= im Log mit Map vergleichen; postmap |
Header-From nicht freigegeben | App setzt anderen sichtbaren Absender | App korrigieren oder Adresse bewusst in header_from_apps aufnehmen |
451 4.3.5 Policy-Dienst nicht verfügbar | postfwd läuft nicht | systemctl status postfwd, ss -tlnp | grep 10040 |
451 4.7.1 Service unavailable | OpenDKIM nicht erreichbar | systemctl status opendkim, ss -tlnp | grep 8891 |
| Postfix startet nach Reboot nicht | Bind auf 10.10.0.1 vor wg0 | sysctl net.ipv4.ip_nonlocal_bind muss 1 sein |
Interner MTA: Server certificate not verified | CA-Bundle fehlt, Name passt nicht, Zertifikat abgelaufen | tls_policy (match=mail.example.com), smtp_tls_CAfile, Zertifikat prüfen |
| Mails landen im Spam | PTR-Mismatch, DKIM/DMARC fehlerhaft, IP gelistet | dig -x, opendkim-testkey, Header, Blocklisten |
| Eingang stockt | Tunnel unten oder interner MTA nicht erreichbar | wg show, nc -zv 10.10.0.2 25, Queue-Alter |
journalctl -u postfix@- --since "10 min ago" | grep -Ei 'warning|error|reject|sasl|tls'
postqueue -p # Queue anzeigen
postqueue -f # Zustellung anstoßen
# Löschen nur gezielt per Queue-ID (siehe Abschnitt 7), nie pauschal "postsuper -d ALL"18Datenschutz & Betrieb (DSGVO)
- Datenminimierung: Mail-Logs enthalten Absender-, Empfänger- und IP-Daten. Die Aufbewahrung (hier 30 Tage, Abschnitt 13) muss dem dokumentierten Zweck entsprechen und für Journal, Logdateien und Backups gleichermaßen gelten.
- Auftragsverarbeitung: Läuft der Relay bei einem Hoster, gehört dieser mit AVV, bei Standort außerhalb der EU mit passendem Übermittlungsmechanismus, ins Verzeichnis der Verarbeitungstätigkeiten. Dasselbe gilt für DNS-Anbieter und externe Monitoring-Dienste.
- Transportverschlüsselung: Submission und Tunnel sind verpflichtend verschlüsselt und authentifiziert. Der Internetverkehr auf Port 25 ist opportunistisch verschlüsselt und für vereinbarte Partner per TLS-Policy verbindlich.
- Zugangsdaten: SASL-Passwörter, DKIM-, WireGuard- und ACME-Schlüssel sind Geheimnisse: restriktive Dateirechte, Kontenregister, Rotation, verschlüsselte Backups.
- Sicherheitsvorfälle: Der Notfallablauf (Abschnitt 7) enthält die Prüfung einer Meldepflicht nach Art. 33 DSGVO.
Umsetzungsreihenfolge
| Phase | Inhalt | Wann |
|---|---|---|
| 1 · Grundlage | Abschnitte 2–6, 8–11: Zonen, Policies, Absenderbindung, verifizierte interne TLS, Tunnel-Firewall | vor Produktivnahme |
| 2 · Missbrauchsschutz | Kontingente, Kontenregister, Notfallablauf, Volumen-Alarm (Abschnitte 7, 14) | vor Produktivnahme |
| 3 · Abnahme | Negativtests und Checkliste (Abschnitt 15), Backup und Restore-Test (Abschnitt 16) | vor Produktivnahme, danach bei jeder Änderung |
| 4 · Ausbau | DMARC reject, MTA-STS enforce/DANE, postscreen, konto-genaue Header-Prüfung per Milter, getrennte Ein-/Ausgangssysteme | nach Auswertung realer Betriebsdaten |
Diese Dokumentation beschreibt einen technischen Aufbau und ersetzt weder eine Sicherheitsprüfung der konkreten Umgebung noch rechtliche Beratung. Konfigurationswerte sind Startwerte und an den realen Betrieb anzupassen.