Cantotec GmbH  ›  Leistungen  ›  Mailserver  ›  Postfix-Relay auf Debian einrichten und betreiben
← Zurück zur Website
// Praxis-Dokumentation

Postfix-Relay auf Debian einrichten und betreiben

Ein nachvollziehbarer Leitfaden für einen öffentlichen Mail-Relay: getrennte Vertrauenszonen, main.cf und master.cf, SASL mit Absenderbindung, Kontingente pro Konto, DNS (SPF/DKIM/DMARC), TLS mit acme.sh, WireGuard zum internen Mailserver, Monitoring, Negativtests sowie Backup und Wiederherstellung.

Basis: Debian 13 (Trixie) Relay-Host: mail.example.com Domain: example.com Postfix · Cyrus SASL · OpenDKIM · postfwd acme.sh · WireGuard · fail2ban Stand: Oktober 2026

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 / ListenerWer darfHauptrisikoDurchsetzung
*:25 SMTPbeliebige fremde MX, nur an gültige Empfänger in example.comOpen Relay, Spam, Backscatter, Lastkein SASL, reject_unauth_destination, relay_recipient_maps, Anvil-Limits
öffentl. IP:587 SubmissionAnwendungs-Kontengestohlene Zugangsdaten, AbsenderfälschungTLS + SASL Pflicht, Envelope- und Header-Absenderbindung, Kontingent pro Konto, Kontosperre
10.10.0.1:587 Tunnel-Submissionnur das Konto des internen MTAKompromittierung des internen MTAnur über wg0 erreichbar, SASL Pflicht, Absender nur @example.com, eigenes Kontingent, Konto von außen gesperrt
WireGuardgenau ein Peerseitliche Bewegung über den TunnelAllowedIPs /32 + Host-Firewall auf beiden Seiten, kein Routing
OpenDKIMnur Postfix (Loopback)Missbrauch des SignierschlüsselsSocket auf 127.0.0.1, Signatur nur für authentifizierte Einlieferung
SSH / AdministrationManagement-IP bzw. Admin-VPNÜbernahme des HostsFirewall-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.

Einordnung

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.com und die Möglichkeit, den PTR/Reverse-DNS der IP beim Hoster zu setzen.
  • Eine feste Management-IP oder ein Admin-VPN für SSH.
Konvention

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.

Shell · Grundsystem
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 true

Kernel-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.

/etc/sysctl.d/90-mailrelay.conf
# 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 = 1
Shell
sysctl --system

Firewall: 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.

Shell · ufw (Relay)
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 numbered
Feste Absender-IPs?

Wenn 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

/etc/ssh/sshd_config.d/10-hardening.conf
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
AllowUsers admin
X11Forwarding no
MaxAuthTries 3
Reihenfolge

Erst 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.

Shell · Updates, Neustart-Erkennung, AppArmor
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 l prü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

Shell · Postfix
DEBIAN_FRONTEND=noninteractive apt -y install postfix postfix-pcre
systemctl enable postfix
cp /etc/postfix/main.cf{,.orig}
cp /etc/postfix/master.cf{,.orig}
Debian-Eigenheit

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.

/etc/postfix/main.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 = $mynetworks
Was sich gegenüber üblichen Vorlagen geändert hat

SASL 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.

Grenze der Anvil-Limits

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.

„may" heißt opportunistisch

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

/etc/postfix/transport
example.com     smtp:[10.10.0.2]:25

Die 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).

/etc/postfix/relay_recipients
info@example.com        OK
kontakt@example.com     OK
dmarc@example.com       OK
tls-reports@example.com OK
Synchron halten

Die 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.

Shell · Maps übersetzen & prüfen
postmap /etc/postfix/transport /etc/postfix/relay_recipients
postfix check && systemctl reload postfix

04Postfix-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.

Formatierung

Override-Zeilen (-o …) mit mindestens einem Leerzeichen einrücken, keine Leerzeichen um das =. Listen innerhalb eines -o mit Kommas ohne Leerzeichen trennen.

/etc/postfix/master.cf · Dienste für Ein- und Ausgang
# 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_intern

Auf 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.

Warum zwei Listener?

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.

/etc/postfix/master.cf · nur bei Bedarf
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=ORIGINATING

Optional: 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:

/etc/postfix/master.cf · postscreen
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       tlsproxy
/etc/postfix/main.cf · postscreen
postscreen_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:

Shell · Ist-Zustand dokumentieren
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:587

Interne 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).

Shell · Pakete
apt -y install libsasl2-modules sasl2-bin
/etc/postfix/sasl/smtpd.conf
pwcheck_method: auxprop
auxprop_plugin: sasldb
mech_list: PLAIN LOGIN
PLAIN/LOGIN nur über TLS

PLAIN 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.

Shell · Konten anlegen
# 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
sasldblistusers2

Postfix läuft unter Debian im chroot (/var/spool/postfix). Statt sasldb2 dorthin zu kopieren (was auseinanderlaufen kann), wird die Datei per Bind-Mount eingebunden:

Shell · sasldb2 in den chroot bind-mounten
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 postfix

Fü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>).

Welcher Login-Name landet in den Maps?

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.

/etc/postfix/sender_login · Absender → berechtigtes Konto
# 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.com
Shell
postmap /etc/postfix/sender_login && systemctl reload postfix

Sichtbaren 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:

/etc/postfix/header_from_apps · öffentlicher Listener
# 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
/etc/postfix/header_from_intern · Tunnel-Listener
/^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.com
Bewusste Nebenwirkungen & Restrisiko

Ein 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

/etc/postfix/sasl_access_public
# 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
/etc/postfix/sasl_access_tunnel
/^internal-mta(@mail\.example\.com)?$/   DUNNO
/./                                      REJECT 5.7.1 Konto am Tunnel-Listener nicht zugelassen

PCRE-Maps brauchen kein postmap; nach Änderungen systemctl reload postfix.

Alternative: Dovecot-SASL

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):

/etc/postfix/main.cf · auf dem internen MTA
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
/etc/postfix/tls_policy · auf dem internen MTA
[10.10.0.1]:587    secure match=mail.example.com
/etc/postfix/sasl_passwd · auf dem internen MTA
[10.10.0.1]:587    internal-mta:<LANGES-ZUFALLSPASSWORT>
Shell · auf dem internen MTA
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'
„Untrusted" oder „Anonymous" im Log?

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".

Exchange als interner MTA

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.

/etc/nftables.d/wg0.nft · auf dem internen MTA
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
  }
}
Shell · auf dem internen MTA
nft -f /etc/nftables.d/wg0.nft
# dauerhaft: in /etc/nftables.conf per include "/etc/nftables.d/*.nft" einbinden
systemctl enable nftables

07Kontingente 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:

EbeneKontrolleWo
NetzwerkQuell-IP-Beschränkung, Verbindungslimitsufw (Abschnitt 2)
SMTPAnvil-Limits pro IP, Empfänger pro Nachrichtmain.cf (Abschnitt 3)
IdentitätKontingente pro SASL-Konto, Absenderbindung, Sperrlistepostfwd, Abschnitt 5
BetriebVersandvolumen pro Konto überwachen und alarmierenAbschnitt 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.

Shell
apt -y install postfwd
/etc/postfix/postfwd.cf
# 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)
Shell · starten & prüfen
# 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 --showconfig
Ausfallverhalten

Ist 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).

Zähler liegen im Speicher

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.2 der einzige Client des Tunnel-Listeners ist. Dafür sorgen AllowedIPs = 10.10.0.2/32, die ufw-Regel und die Kontenbeschränkung über sasl_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 reload bzw. einem Neustart neu.
  • smtpd_client_event_limit_exceptions darf 10.10.0.2 nicht enthalten (Standard hier: nur $mynetworks, also localhost).
Bewusst akzeptiertes Restrisiko

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.

  1. Konto sperren: Zeile in /etc/postfix/sasl_access_public ergänzen, systemctl reload postfix. Die Wirkung ist sofort da, die Zugangsdaten bleiben für die Analyse erhalten.
  2. 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 -
  3. Umfang feststellen: Versandmenge, Zeitraum und Empfänger aus dem Log (journalctl -u postfix@- --since … | grep 'sasl_username=relay-app'), Quell-IPs der Anmeldungen.
  4. Zugangsdaten rotieren: saslpasswd2 -u mail.example.com relay-app, neues Passwort nur an die verantwortliche Person der Anwendung.
  5. Reputation prüfen: Blocklisten-Status der Relay-IP, DMARC-Reports der folgenden Tage. Ggf. Delisting beantragen.
  6. Ursache beheben (Leck in der Anwendung, Konfigurationsdatei, Repository), dann Sperre zurücknehmen und Kontingent für einige Tage enger setzen.
  7. Dokumentieren: Zeitlinie, Auswirkung, Maßnahmen. Bei personenbezogenen Daten prüfen, ob eine Meldepflicht nach Art. 33 DSGVO besteht.

08Öffentliche DNS-Einträge

TypNameWert (Beispiel)Zweck
Amail.example.com203.0.113.10Adresse des Relays
MXexample.com10 mail.example.com.Eingang für die Domain
PTR(Rückwärtszone der IP)mail.example.comReverse-DNS, beim Hoster setzen
TXTexample.comv=spf1 a:mail.example.com -allSPF
TXTmail2026._domainkeyv=DKIM1; k=rsa; p=…DKIM (Abschnitt 9)
TXT_dmarc.example.comv=DMARC1; p=none; …DMARC, stufenweise verschärfen
CAAexample.com0 issue "letsencrypt.org"zulässige Zertifizierungsstelle
PTR und Forward-DNS konsistent

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:

DNS · SPF
# 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:

DNS · DMARC, stufenweise
# 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.

Shell · Verifikation
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.com

Erzwungene 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.

MechanismusVoraussetzungBetriebsaufgabe
MTA-STSHTTPS unter mta-sts.example.com + TXT _mta-stsPolicy-id bei jeder Änderung hochzählen; max_age bestimmt, wie lange Sender die Policy cachen
DANE (TLSA)DNSSEC für die Zone, TLSA-RecordBei jedem Schlüsselwechsel TLSA vorher veröffentlichen (Rollover), sonst Zustellausfall
TLS-RPTTXT _smtp._tlsReports über fehlgeschlagene TLS-Zustellungen auswerten
DNS · MTA-STS & TLS-RPT
_mta-sts.example.com.   IN TXT  "v=STSv1; id=20261009T000000;"
_smtp._tls.example.com. IN TXT  "v=TLSRPTv1; rua=mailto:tls-reports@example.com"
https://mta-sts.example.com/.well-known/mta-sts.txt
version: STSv1
mode: testing
mx: mail.example.com
max_age: 86400

Mit 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.

Ausgehend erzwingen

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.

Shell · Installation & Schlüssel
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.private
/etc/opendkim.conf · relevante Zeilen
Syslog                  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
/etc/opendkim/KeyTable · SigningTable · 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
::1
Signieren vs. Prüfen: wer bekommt die Domain-Signatur?

Signiert 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.

Shell · Start & DNS-Wert auslesen
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.

Verhalten im Test belegen

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.

systemctl edit opendkim
[Service]
Restart=on-failure
RestartSec=5s

Schlüsselrotation

Jährlich sowie sofort bei Verdacht auf Kompromittierung:

  1. Neuen Schlüssel mit neuem Selector erzeugen (z. B. mail2027), TXT-Record veröffentlichen, TTL abwarten, opendkim-testkey prüfen.
  2. KeyTable und SigningTable auf den neuen Selector umstellen, systemctl restart opendkim, Testmail prüfen (dkim=pass, s=mail2027).
  3. 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=".
  4. 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.

Shell · Installation (Skript vor Ausführung prüfen)
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 letsencrypt

DNS-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 .bashrc oder geteilten Shell-Profilen speichern. acme.sh legt es nach dem ersten Lauf in /root/.acme.sh/account.conf ab; Rechte 600 prüfen.
  • Bei Verdacht auf Abfluss: Token beim DNS-Anbieter widerrufen und Zone auf unerwartete Änderungen prüfen.
Shell · Zertifikat ausstellen (DNS-API, Beispiel Cloudflare)
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.conf
Shell · Deploy in Postfix
install -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.

Shell · ausgeliefertes vs. installiertes Zertifikat
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 -enddate

Bei 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.

Shell · Installation & Schlüssel (auf beiden Hosts)
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
/etc/wireguard/wg0.conf · auf dem Relay
[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
/etc/wireguard/wg0.conf · auf dem internen MTA
[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 = 25
Shell · Aktivieren & prüfen
chmod 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 scheitern

Wer darf was im Tunnel

RichtungErlaubtDurchgesetzt durch
Relay → interner MTATCP 25 (eingehende Mail)nftables auf dem internen MTA (Abschnitt 6)
Interner MTA → RelayTCP 587 auf 10.10.0.1 (ausgehende Mail)ufw auf dem Relay (Abschnitt 2)
Alles andere im TunnelverworfenDefault-Deny auf beiden Seiten
Tunneladressen über eth0verworfenufw deny in on eth0 to 10.10.0.0/24, rp_filter
Routing Tunnel ↔ Internet/LANkeinsip_forward = 0 (Relay), Forward-Drop (intern)
Schlüsselverwaltung

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.

Shell
apt -y install fail2ban
/etc/fail2ban/jail.local
[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@-.service
Shell · Start & Wirksamkeit prüfen
systemctl 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.
Häufiger Fehler

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).

/etc/systemd/journald.conf.d/90-retention.conf
[Journal]
Storage=persistent
MaxRetentionSec=30day
SystemMaxUse=2G
Shell
systemctl restart systemd-journald
journalctl --disk-usage

Optional: klassische Logdateien mit rsyslog

Wer /var/log/mail.log bevorzugt, installiert rsyslog und begrenzt die Dateien ebenfalls auf 30 Tage:

/etc/logrotate.d/postfix-relay
/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
}
Doppelrotation vermeiden

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.

MetrikAlarm (Startwert)Hinweis auf
Dienste postfix@-, opendkim, postfwd, wg-quick@wg0, fail2bannicht aktivAusfall; bei OpenDKIM/postfwd stockt jede Einlieferung
Queue-Länge> 200 NachrichtenZustellproblem oder Missbrauch
Alter der ältesten Nachricht> 2 hinterner MTA/Tunnel unten, Blockierung bei Empfängern
Versand pro SASL-Konto> 3× Tagesdurchschnittkompromittiertes Konto
SASL-Fehlversuche> 50 / hBrute-Force, falsch konfigurierte Anwendung
Anteil 4xx/5xx bei Zustellung> 10 % über 1 hReputationsproblem, 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-AbweichungErneuerung fehlgeschlagen oder nicht ausgeliefert
WireGuard-Handshakeälter als 5 minTunnel unterbrochen
Blocklisten der Relay-IPneuer EintragReputationsverlust
Shell · Kennzahlen (z. B. für Prometheus-Textfile oder Cron-Check)
# 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 -rn

Die 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.

Shell · weitere Kennzahlen
# 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.cer

Drei Regeln für wirksame Alarme

  1. 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.
  2. Verantwortlichkeit: Für jeden Alarm sind Empfänger, Vertretung und Reaktionszeit festgelegt. Ein Alarm ohne Zuständigen schützt nicht.
  3. Handlungsorientierung: Ein Volumen-Alarm nennt Konto, Zeitraum, Menge und die verantwortliche Anwendung laut Kontenregister. So kann der Notfallablauf aus Abschnitt 7 sofort starten.
Werkzeuge

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.

#TestErwartet
T1Extern, Port 25, an fremde Domain554 5.7.1 … Relay access denied
T2Extern, Port 25, an nicht existierenden Empfänger in example.com550 5.1.1 … User unknown in relay recipient table
T3Port 25: EHLO nach STARTTLSkein AUTH angeboten
T4Port 587 mit TLS, ohne Anmeldung554 5.7.1 … Access denied
T5Port 587, falsches Passwort535 5.7.8; fail2ban-Zähler steigt
T6aPort 587 ohne STARTTLS: EHLOkein AUTH angeboten
T6bPort 587 ohne STARTTLS: MAIL FROM530 5.7.0 Must issue a STARTTLS command first
T7Gültiges App-Konto, fremder Envelope-Absender553 5.7.1 … not owned by user
T8Gültiges App-Konto, eigener Envelope-, fremder Header-Absender550 5.7.1 Header-From nicht freigegeben
T8bGültiges App-Konto, zwei getrennte, jeweils erlaubte From:-ZeilenAblehnung (5xx) durch OpenDKIM RequiredHeaders
T9Kontingent testweise auf 3/h setzen, 4 Nachrichten senden4. Nachricht 450 4.7.1 Versandlimit …
T9bWie T9, dazwischen systemctl restart postfwdVerhalten dokumentieren (Zähler zurückgesetzt); Volumen-Alarm greift trotzdem
T9cInterner 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
T10Konto internal-mta am öffentlichen Listener554 5.7.1 Konto nur ueber den Tunnel zugelassen
T11Extern über Port 25 an info@example.com mit gefälschtem From: chef@example.comangenommen, aber ohne DKIM-Signature d=example.com; dmarc=fail im Header
T12Regulärer App-Versand an externes Testpostfachspf=pass, dkim=pass (s=mail2026), dmarc=pass
T13systemctl stop opendkim, dann App-Versand451 4.7.1, nach Start wird zugestellt; kein unsignierter Versand
T14Vom Relay: nc -zv 10.10.0.2 22; aus dem Internet: Port 25 der internen Standort-IPbeides geschlossen bzw. Timeout
T15Versand vom internen MTALog intern: Verified TLS connection established to 10.10.0.1
T16Bounce des internen MTA (leerer Absender <>) über den Tunnelwird angenommen und zugestellt
T17Portscan von fremder IP: nmap -Pn -p 22,25,80,465,587,8891,10040 mail.example.comnur 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.

relay-selftest.sh · auf einem externen Testhost
#!/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 $FAIL
Warum T6b mit 530 rechnen darf

Auf 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.

Ergebnis archivieren

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

BereichKriteriumNachweis
SicherheitKein Open Relay, keine anonyme Submission, AUTH nur über TLST1, T3–T6b
Envelope- und Header-Absender gebunden, genau ein From:T7, T8, T8b
Kontingente (auch internal-mta) und Kontosperre wirksamT9–T9c, T10
Interner MTA nicht öffentlich, Tunnel minimalT14, T17
Interne TLS-Verbindung mit geprüfter GegenstelleT15
Nur 25 und 587 öffentlich; IPv6 aus oder vollständig abgesichertT17, ss -tlnp
ZustellungPTR/Forward konsistent, SPF vollständig, DKIM und DMARC passAbschnitt 8, T12
Eingehende Fälschungen nicht signiertT11
Backscatter-Schutz durch EmpfängerprüfungT2
BetriebMilter-Ausfall definiert (tempfail)T13
Monitoring alarmiert extern, Zuständigkeit benanntTestalarm ausgelöst
Zertifikatserneuerung geprüft (ausgeliefertes Zertifikat)Fingerprint-Vergleich, Abschnitt 10
Backup vorhanden und Wiederherstellung erprobtAbschnitt 16
Notfallablauf für kompromittierte Konten dokumentiert und geübtAbschnitt 7

16Backup, Wiederherstellung & Ausfallszenarien

Was gesichert wird

Sicherungsumfang
/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

SzenarioAuswirkungReaktion
Relay fällt ausKein Ein- und Ausgang. Fremde MX stellen bis zu mehreren Tagen erneut zu, interner MTA und Apps stellen zurückWiederherstellung aus Backup; MX-TTL niedrig halten (z. B. 3600 s), um auf Ersatz-IP umzuschwenken (PTR beachten)
Interner MTA / Tunnel fällt ausEingang sammelt sich in der Relay-Queue (bis maximal_queue_lifetime = 5 d)Queue-Alter überwachen; nach Wiederkehr postqueue -f
OpenDKIM fällt ausAlle Einlieferungen 4xxAutomatischer Neustart, Alarm; kein Umstellen auf accept
postfwd fällt ausApp-Submission 4xxNeustart, Alarm
Zertifikat abgelaufenSubmission bricht ab; interner MTA verweigert (secure)Erneuern, Fingerprint prüfen; Ursache (Token, DNS) beheben
Dateisystem vollPostfix nimmt mit 4xx nichts mehr an (queue_minfree)Ursache (Missbrauch? Zustellstau?) klären, gezielt bereinigen
SASL-Konto kompromittiertSpam, ReputationsverlustNotfallablauf Abschnitt 7
Admin-Zugang verlorenKeine Wartung möglichNotfallzugang über die Konsole des Hosters, hinterlegter Zweitschlüssel

17Troubleshooting

SymptomWahrscheinliche UrsacheAnsatz
Relay access deniedEinlieferung auf Port 25 statt 587Client auf 587 mit STARTTLS und Anmeldung umstellen
SASL authentication failedsasldb2 im chroot nicht lesbar, falscher RealmBind-Mount prüfen (findmnt /var/spool/postfix/etc/sasldb2); Realm = myhostname
Sender address rejected: not owned by userAbsender fehlt in sender_login oder Login-Schreibweise falschsasl_username= im Log mit Map vergleichen; postmap
Header-From nicht freigegebenApp setzt anderen sichtbaren AbsenderApp korrigieren oder Adresse bewusst in header_from_apps aufnehmen
451 4.3.5 Policy-Dienst nicht verfügbarpostfwd läuft nichtsystemctl status postfwd, ss -tlnp | grep 10040
451 4.7.1 Service unavailableOpenDKIM nicht erreichbarsystemctl status opendkim, ss -tlnp | grep 8891
Postfix startet nach Reboot nichtBind auf 10.10.0.1 vor wg0sysctl net.ipv4.ip_nonlocal_bind muss 1 sein
Interner MTA: Server certificate not verifiedCA-Bundle fehlt, Name passt nicht, Zertifikat abgelaufentls_policy (match=mail.example.com), smtp_tls_CAfile, Zertifikat prüfen
Mails landen im SpamPTR-Mismatch, DKIM/DMARC fehlerhaft, IP gelistetdig -x, opendkim-testkey, Header, Blocklisten
Eingang stocktTunnel unten oder interner MTA nicht erreichbarwg show, nc -zv 10.10.0.2 25, Queue-Alter
Shell · gezielt in den Logs suchen
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

PhaseInhaltWann
1 · GrundlageAbschnitte 2–6, 8–11: Zonen, Policies, Absenderbindung, verifizierte interne TLS, Tunnel-Firewallvor Produktivnahme
2 · MissbrauchsschutzKontingente, Kontenregister, Notfallablauf, Volumen-Alarm (Abschnitte 7, 14)vor Produktivnahme
3 · AbnahmeNegativtests und Checkliste (Abschnitt 15), Backup und Restore-Test (Abschnitt 16)vor Produktivnahme, danach bei jeder Änderung
4 · AusbauDMARC reject, MTA-STS enforce/DANE, postscreen, konto-genaue Header-Prüfung per Milter, getrennte Ein-/Ausgangssystemenach Auswertung realer Betriebsdaten
Hinweis

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.