Es bestehen keine weiteren Abhängigkeiten (kein CDN, kein externer Aufruf). ============================================================================ --> Postfix-Relay auf Debian einrichten und betreiben — Cantotec GmbH
Cantotec GmbH   Leistungen   Mailserver   Einrichtung & Betrieb
← Zurück zur Website
// Praxis-Dokumentation

Postfix-Relay auf Debian einrichten und betreiben

Ein vollständiger, nachvollziehbarer Leitfaden: vom Aufbau der main.cf und master.cf über SASL-Authentifizierung und die notwendigen öffentlichen DNS-Einträge bis zu TLS mit acme.sh, Absicherung per fail2ban, sauberem Logmanagement mit logrotate und einem WireGuard-Tunnel zum internen Mailserver.

Basis: Debian 12 (Bookworm) Relay-Host: mail.example.com Domain: example.com Postfix · Cyrus SASL · OpenDKIM acme.sh · fail2ban · logrotate · WireGuard

01Einleitung & Zielbild

Ziel dieses Beitrags ist ein produktionstauglicher öffentlicher Mail-Relay auf Debian mit dem Hostnamen mail.example.com. Der Relay übernimmt zwei klar getrennte Aufgaben:

  • Ausgehend (Smarthost): Anwendungen und interne Systeme authentifizieren sich per SASL auf Port 587 (Submission, STARTTLS) und der Relay stellt die Nachrichten an die Ziel-MX im Internet zu.
  • Eingehend (Gateway): Der Relay ist der MX für example.com, nimmt Mails auf Port 25 an und reicht sie über einen WireGuard-Tunnel an den internen Mailserver (10.10.0.2) weiter.

Der interne Mailserver bleibt so vollständig hinter dem Tunnel verborgen; nach außen ist ausschließlich der Relay mit sauberer IP-Reputation, TLS und DKIM sichtbar.

Voraussetzungen

  • Ein Debian-12-Server mit statischer öffentlicher IPv4 (idealerweise auch IPv6) und root-Zugang.
  • Zugriff auf die öffentliche DNS-Zone von example.com sowie die Möglichkeit, den PTR/Reverse-DNS-Eintrag der IP beim Hoster zu setzen.
  • Freigeschaltete Ports: 25, 587, 465 (optional), 80 (nur für ACME-Standalone), 51820/udp (WireGuard) und 22.
Konvention

Alle Beispiele verwenden die Domain example.com, den Relay mail.example.com, das Tunnel-Netz 10.10.0.0/24 (Relay 10.10.0.1, interner MTA 10.10.0.2). Ersetzen Sie diese Werte durch Ihre realen Angaben.

02Grundinstallation & Härtung

Zunächst das System aktualisieren, den vollqualifizierten Hostnamen setzen und die Zeitsynchronisation sicherstellen — ein korrekter FQDN und eine korrekte Uhrzeit sind für TLS, DKIM und Logauswertung essenziell.

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

Firewall

Wir nutzen ufw als schlanke Front für nftables. Nur die tatsächlich benötigten Ports werden geöffnet.

Shell · ufw
apt -y install ufw
ufw allow 22/tcp        # SSH
ufw allow 25/tcp        # SMTP (inbound MX)
ufw allow 587/tcp       # Submission (SASL)
ufw allow 465/tcp       # SMTPS (optional)
ufw allow 51820/udp     # WireGuard
ufw --force enable
# Port 80 bleibt geschlossen. Nur falls ACME per HTTP-01/Standalone (Abschnitt 8)
# genutzt wird, temporär öffnen und danach wieder schließen:
#   ufw allow 80/tcp && acme.sh --issue --standalone ... && ufw delete allow 80/tcp

SSH härten

Der Server ist über Port 22 aus dem Internet erreichbar. fail2ban (Abschnitt 10) ist nur eine zusätzliche Absicherung — die eigentliche Härtung passiert im SSH-Daemon: Schlüssel statt Passwort, kein direkter Root-Login.

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

Grundhärtung des Hosts

Firewallregeln begrenzen die Netzwerk-Exposition, ersetzen aber keine Anwendungs- und Systemhärtung. Für einen dauerhaft exponierten Server empfehlenswert:

Shell · automatische Updates, AppArmor, minimale Angriffsfläche
# Automatische Sicherheitsupdates
apt -y install unattended-upgrades
dpkg-reconfigure -plow unattended-upgrades

# AppArmor aktiv halten (unter Debian standardmäßig vorhanden)
apt -y install apparmor apparmor-utils
aa-status

# nur benötigte Dienste; installierte Pakete regelmäßig prüfen
systemctl list-unit-files --state=enabled
Weitergehend

Für produktive Relays zusätzlich erwägen: sysctl-Härtung (Netzwerk-Stack), Ressourcenlimits, ein Monitoring/Alerting (Abschnitt 12) sowie regelmäßige Paketinventur. Diese Maßnahmen sind Host-Standard und in dieser Doku nur als Checkliste vermerkt.

Postfix installieren

Shell · Postfix
# Nicht-interaktive Installation (Konfiguration folgt manuell)
DEBIAN_FRONTEND=noninteractive apt -y install postfix postfix-pcre
# Bei der Nachfrage "Internet Site" wählen; wir überschreiben ohnehin main.cf
systemctl enable postfix
Sicherung anlegen

Vor Änderungen die Auslieferungsdateien sichern: cp /etc/postfix/main.cf{,.orig} und cp /etc/postfix/master.cf{,.orig}.

03Postfix-Konfiguration: main.cf

Die main.cf definiert das globale Verhalten des Servers: Identität, Netzwerke, Relay-Ziele, TLS, SASL und die Milter-Anbindung für DKIM. Die folgende Datei ist vollständig und kommentiert.

/etc/postfix/main.cf
# ===== Identität =====
myhostname = mail.example.com
mydomain = example.com
myorigin = $mydomain
compatibility_level = 3.6

# ===== Netzwerk / lokale Zustellung deaktivieren =====
inet_interfaces = all
# IPv6 nur aktivieren, wenn es vollständig mitgehärtet ist (Firewall, PTR,
# Monitoring, Reputation). Wird IPv6 nicht benötigt: inet_protocols = ipv4
inet_protocols = all
mydestination =
local_recipient_maps =
local_transport = error:local delivery is disabled
# Nur localhost ist implizit vertraut. Der interne MTA authentifiziert sich
# stattdessen per SASL (siehe Abschnitt 5) — kein IP-Vertrauen über das VPN.
mynetworks = 127.0.0.0/8 [::1]/128

# ===== Eingehend: example.com an den internen MTA weiterreichen =====
relay_domains = example.com
transport_maps = hash:/etc/postfix/transport
relay_recipient_maps = hash:/etc/postfix/relay_recipients

# ===== Restrictions =====
smtpd_helo_required = yes
smtpd_relay_restrictions =
    permit_mynetworks
    permit_sasl_authenticated
    reject_unauth_destination
smtpd_recipient_restrictions =
    permit_mynetworks
    permit_sasl_authenticated
    reject_unauth_destination
    reject_unknown_recipient_domain
    reject_non_fqdn_recipient
smtpd_helo_restrictions =
    permit_mynetworks
    reject_invalid_helo_hostname
    reject_non_fqdn_helo_hostname

# ===== SASL (Details in Abschnitt 5) =====
smtpd_sasl_auth_enable = yes
smtpd_sasl_type = cyrus
smtpd_sasl_path = smtpd
smtpd_sasl_security_options = noanonymous
smtpd_sasl_local_domain = $myhostname
broken_sasl_auth_clients = yes

# ===== TLS (Zertifikate via acme.sh, Abschnitt 8) =====
smtpd_tls_cert_file = /etc/postfix/tls/mail.example.com/fullchain.cer
smtpd_tls_key_file  = /etc/postfix/tls/mail.example.com/private.key
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
tls_preempt_cipherlist = yes
smtpd_tls_loglevel = 1

# ===== DKIM / Milter (OpenDKIM, Abschnitt 7) =====
# tempfail statt accept: Fällt OpenDKIM aus, wird Mail zurückgestellt (4xx)
# statt UNsigniert ausgeliefert. Bewusste Sicherheit-vor-Verfügbarkeit-Wahl.
milter_default_action = tempfail
milter_protocol = 6
smtpd_milters = inet:127.0.0.1:8891
non_smtpd_milters = inet:127.0.0.1:8891

# ===== Hygiene =====
disable_vrfy_command = yes
smtpd_delay_reject = yes
message_size_limit = 52428800
biff = no
append_dot_mydomain = no

# ===== Rate-Limits (anvil, pro Client-IP) =====
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_recipient_limit = 100
anvil_rate_time_unit = 60s
# mynetworks (localhost) von den Limits ausnehmen
smtpd_client_event_limit_exceptions = $mynetworks
Grenze der Anvil-Limits

Diese Limits greifen pro Client-IP. Für authentifizierte Submission über eine gemeinsame IP (z. B. mehrere Anwendungen hinter dem Tunnel) sind sie deshalb nur grob. Echtes Pro-Konto-Rate-Limiting kann Stock-Postfix nicht — dafür postfwd oder policyd (cluebringer) vorschalten. fail2ban ist ausdrücklich kein Ersatz für Rate-Limiting.

„may" heißt opportunistisch

smtp_tls_security_level = may verschlüsselt zu fremden MX nur, wenn möglich — es ist kein garantierter Schutz und anfällig für STARTTLS-Downgrade. Für erzwungene Transportsicherheit siehe MTA-STS/DANE in Abschnitt 6. Der Wert may auf Port 25 ist für Internet-Mail dennoch korrekt, da sich TLS zu beliebigen Fremdservern nicht erzwingen lässt, ohne Zustellung zu riskieren.

Transport- und Empfänger-Maps

Die transport-Map lenkt alle Mails für example.com durch den Tunnel an den internen MTA. Die eckigen Klammern [ ] unterdrücken die MX-Suche und adressieren den Host direkt.

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

Die relay_recipients-Map verhindert Backscatter, indem nur real existierende Postfächer angenommen werden (sonst nimmt der Relay alles an und erzeugt später Unzustellbarkeits-Bounces).

/etc/postfix/relay_recipients
info@example.com        OK
kontakt@example.com     OK
# Alternativ pauschal (weniger streng):
# @example.com          OK
Shell · Maps übersetzen & prüfen
postmap /etc/postfix/transport
postmap /etc/postfix/relay_recipients
postfix check && systemctl reload postfix

04Postfix-Konfiguration: master.cf

Während die main.cf globale Vorgaben macht, definiert die master.cf die einzelnen Dienste (Ports) und erlaubt pro Dienst gezielte Overrides mit -o. Wir aktivieren Submission (587) mit erzwungenem TLS und SASL sowie optional SMTPS (465).

Achtung: Formatierung

Die Override-Zeilen (-o …) müssen mit mindestens einem Leerzeichen eingerückt sein und dürfen keine Leerzeichen um das = enthalten.

/etc/postfix/master.cf — Auszug (ergänzen/anpassen)
# service   type  private unpriv  chroot  wakeup  maxproc command
smtp        inet  n       -       y       -       -       smtpd
  -o smtpd_sasl_auth_enable=no

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_sasl_type=cyrus
  -o smtpd_sasl_path=smtpd
  -o smtpd_reject_unlisted_recipient=no
  -o smtpd_client_restrictions=permit_sasl_authenticated,reject
  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
  -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
  -o milter_macro_daemon_name=ORIGINATING

smtps       inet  n       -       y       -       -       smtpd
  -o syslog_name=postfix/smtps
  -o smtpd_tls_wrappermode=yes
  -o smtpd_tls_auth_only=yes
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_sasl_type=cyrus
  -o smtpd_sasl_path=smtpd
  -o smtpd_client_restrictions=permit_sasl_authenticated,reject
  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
  -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
  -o milter_macro_daemon_name=ORIGINATING

Wichtig: Auf Port 25 (erste Zeile) bleibt smtpd_sasl_auth_enable=no — dort sollen sich fremde MTAs nicht anmelden. Authentifizierte Einlieferung läuft ausschließlich über 587/465. Auf 587 erzwingt smtpd_tls_security_level=encrypt, auf 465 der wrappermode — kombiniert mit smtpd_tls_auth_only=yes ist damit auf beiden Diensten sichergestellt, dass SASL nur über eine bereits bestehende TLS-Verbindung stattfindet.

Shell
postfix check && systemctl restart postfix
ss -tlnp | grep -E ':25|:465|:587'

05SASL-Authentifizierung (Cyrus, sasldb)

Für einen Relay mit wenigen Absende-Konten ist die Cyrus-SASL-auxprop-Datenbank (sasldb2) die schlankste Lösung — kein zusätzlicher Daemon, Verwaltung per saslpasswd2. (Für größere Umgebungen mit vielen Postfächern ist Dovecot-SASL die bessere Wahl; siehe Hinweis am Ende.)

Shell · Pakete
apt -y install libsasl2-modules sasl2-bin

SASL-Mechanismen für Postfix festlegen

Postfix (chroot) sucht die SASL-Konfiguration unter /etc/postfix/sasl/smtpd.conf.

/etc/postfix/sasl/smtpd.conf
pwcheck_method: auxprop
auxprop_plugin: sasldb
mech_list: PLAIN LOGIN
PLAIN/LOGIN nur über TLS

Die Mechanismen PLAIN und LOGIN übertragen das Passwort im Klartext (base64) und dürfen ausschließlich über eine bereits etablierte TLS-Verbindung zum Einsatz kommen. Genau das erzwingen 587 (encrypt) und 465 (wrappermode) plus smtpd_tls_auth_only=yes. Dieselbe SASL-Konfiguration niemals an einem Dienst ohne TLS-Zwang verwenden.

Konto anlegen

Der Realm muss zu smtpd_sasl_local_domain passen (hier mail.example.com). Der Client meldet sich dann mit dem reinen Benutzernamen an.

Shell · Benutzer & Rechte
# Konto "relay-app" im Realm mail.example.com anlegen
saslpasswd2 -c -u mail.example.com relay-app

# Postfix muss die DB lesen dürfen
chown root:postfix /etc/sasldb2
chmod 640 /etc/sasldb2

# Kontrolle
sasldblistusers2
chroot-Falle

Postfix läuft unter Debian per Default im chroot (/var/spool/postfix). Cyrus SASL sucht sasldb2 relativ dazu. Statt die Datei manuell zu kopieren (was auseinanderlaufen kann), binden wir sie per Bind-Mount ein — so ist die im chroot sichtbare DB stets identisch mit /etc/sasldb2, auch nach jeder Kontoänderung.

Shell · sasldb2 dauerhaft in den chroot bind-mounten
install -d /var/spool/postfix/etc
touch /var/spool/postfix/etc/sasldb2

# dauerhafter Bind-Mount via fstab
echo '/etc/sasldb2  /var/spool/postfix/etc/sasldb2  none  bind  0 0' >> /etc/fstab
mount -a

chown root:postfix /etc/sasldb2
chmod 640 /etc/sasldb2
systemctl restart postfix
Alternative: Dovecot-SASL

Bei vielen Nutzern oder wenn ohnehin IMAP betrieben wird, ersetzen Sie die drei smtpd_sasl_*-Zeilen durch smtpd_sasl_type=dovecot und smtpd_sasl_path=private/auth und binden den Dovecot-Auth-Socket ein. Der Rest der Konfiguration bleibt identisch.

Internen MTA per SASL anbinden (kein IP-Vertrauen über das VPN)

Standardmäßig würde man das Tunnel-Netz in mynetworks aufnehmen und dem internen MTA damit den Versand allein aufgrund seiner IP erlauben. Sicherer ist es, auch über den WireGuard-Tunnel SASL zu verlangen: Das Relay-Recht hängt dann an Zugangsdaten statt an der Netzwerkposition. Deshalb steht in der main.cf (Abschnitt 3) 10.10.0.0/24 bewusst nicht mehr in mynetworks.

Wirkung

Ohne IP-Vertrauen kann ein kompromittierter Host im Tunnel-Netz den Relay nicht mehr als offenen Versandweg missbrauchen. Ausgehende Zustellung ist nur noch nach erfolgreicher Anmeldung auf Port 587 möglich; Port 25 nimmt vom Tunnel nur noch Mail für example.com (eingehend) an.

Eigenes Relay-Konto für den internen MTA

Shell · auf dem Relay (mail.example.com)
saslpasswd2 -c -u mail.example.com internal-mta
# Dank Bind-Mount ist das Konto sofort auch im chroot verfügbar — kein Kopieren nötig.
sasldblistusers2

Interner MTA sendet authentifiziert über Port 587

Der interne Postfix wird zum Smarthost-Client: Er relayt ausgehende Mail über den Tunnel an [10.10.0.1]:587 und meldet sich dabei mit dem Konto internal-mta an.

/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
# Tunnel-IP: Zertifikatsname weicht ab -> verschlüsseln ohne Namensprüfung
smtp_tls_security_level = encrypt
/etc/postfix/sasl_passwd — auf dem internen MTA
[10.10.0.1]:587    internal-mta:GEHEIM
Shell · auf dem internen MTA
postmap /etc/postfix/sasl_passwd
chmod 600 /etc/postfix/sasl_passwd /etc/postfix/sasl_passwd.db
systemctl reload postfix
Exchange statt Postfix?

Bei einem Exchange als internem MTA denselben Effekt über einen Sendeconnector erreichen: Smarthost [10.10.0.1]:587, Authentifizierung „Basic" über TLS mit den internal-mta-Zugangsdaten.

Credential-Missbrauch begrenzen

Das größte praktische Risiko eines Relays ist nicht ein Postfix-Bug, sondern gestohlene SASL-Zugangsdaten: Damit lässt sich legitim authentifiziert Massenmail versenden. Deshalb gehören die folgenden Maßnahmen zur Standard-Härtung, nicht ins „Optional".

  • Pro Anwendung ein eigenes Konto — keine geteilten Zugangsdaten, damit Sperren/Rotation gezielt möglich sind.
  • Absender an das Konto binden (siehe unten) — ein Konto darf nur seine freigegebenen Absenderadressen nutzen.
  • Rate-Limits (Abschnitt 3) und Monitoring ungewöhnlicher Versandmengen (Abschnitt 12).
  • Kurze Credential-Lebensdauer und regelmäßige Rotation; Zugangsdaten nur in geschützten Konfigdateien (chmod 600).

Absenderbindung (smtpd_sender_login_maps)

Verknüpft jede erlaubte Absenderadresse mit dem berechtigten SASL-Login und lehnt Abweichungen ab.

/etc/postfix/main.cf — Relay
smtpd_sender_login_maps = hash:/etc/postfix/sender_login
/etc/postfix/sender_login — Absender → erlaubtes Login
noreply@example.com     internal-mta
info@example.com        internal-mta
app1@example.com        relay-app

Im Submission-Dienst (master.cf, Port 587/465) die Prüfung ergänzen — vor permit_sasl_authenticated:

/etc/postfix/master.cf — im submission/smtps-Block
  -o smtpd_sender_restrictions=reject_sender_login_mismatch
  -o smtpd_sender_login_maps=hash:/etc/postfix/sender_login
Shell
postmap /etc/postfix/sender_login && systemctl reload postfix

06Öffentliche DNS-Einträge

Die Zustellbarkeit steht und fällt mit den DNS-Einträgen. Reihenfolge und Zweck im Überblick:

TypNameWert (Beispiel)Zweck
Amail.example.com203.0.113.10Adresse des Relays
AAAAmail.example.com2001:db8::10optional, IPv6
MXexample.com10 mail.example.com.Eingang für die Domain
PTR(IP-Rückwärtszone)mail.example.comReverse-DNS — beim Hoster setzen
TXTexample.comv=spf1 a:mail.example.com -allSPF
TXTdefault._domainkeyv=DKIM1; k=rsa; p=…DKIM (aus Abschnitt 7)
TXT_dmarc.example.comv=DMARC1; p=quarantine; …DMARC

SPF, DMARC & optionale Härtung

DNS · TXT-Records
# SPF: nur mail.example.com darf für example.com senden
example.com.            IN TXT  "v=spf1 a:mail.example.com -all"

# DMARC: Verstöße unter Quarantäne, Reports einsammeln
_dmarc.example.com.     IN TXT  "v=DMARC1; p=quarantine; rua=mailto:dmarc@example.com; fo=1; adkim=s; aspf=s"

# optional: CAA schränkt CAs ein (Let's Encrypt)
example.com.            IN CAA  0 issue "letsencrypt.org"
PTR nicht vergessen

Ein fehlender oder unpassender PTR (Reverse-DNS) ist der häufigste Grund für Spam-Einstufung. Der PTR der öffentlichen IP muss exakt mail.example.com ergeben und wird beim IP-Provider gesetzt, nicht in der Domain-Zone.

Shell · Verifikation
dig +short MX example.com
dig +short TXT example.com
dig +short TXT default._domainkey.example.com
dig +short -x 203.0.113.10        # PTR

Erzwungene Transportsicherheit: MTA-STS, DANE, TLS-RPT

Das opportunistische may (Abschnitt 3) verschlüsselt nur, wenn die Gegenstelle mitspielt, und ist gegen STARTTLS-Downgrade anfällig. Wer TLS für ein-/ausgehende Zustellung erzwingen will, ergänzt eine der folgenden Mechaniken — für schützenswerte Kommunikation ausdrücklich empfohlen:

MechanismusVoraussetzungWirkung
MTA-STSHTTPS-Policy unter mta-sts.example.com + TXT _mta-stsSendende MTAs erzwingen TLS zu Ihrem MX (Policy enforce)
DANE (TLSA)DNSSEC für die Zone + TLSA-RecordBindet das MX-Zertifikat an DNS; stärkster Schutz, aber DNSSEC-Pflicht
TLS-RPTTXT _smtp._tlsBerichte über fehlgeschlagene TLS-Zustellungen
DNS · TXT für MTA-STS & TLS-RPT
_mta-sts.example.com.   IN TXT  "v=STSv1; id=20260101T000000;"
_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: enforce
mx: mail.example.com
max_age: 604800
Ausgehend erzwingen

Damit Ihr Relay die Policies fremder Domains ebenfalls durchsetzt (statt nur opportunistisch zu verschlüsseln), lässt sich postfix-mta-sts-resolver als smtp_tls_policy_maps einbinden. DANE-Ausgangsprüfung aktiviert smtp_tls_security_level = dane — setzt aber einen DNSSEC-validierenden Resolver voraus.

07DKIM mit OpenDKIM

DKIM signiert ausgehende Mails kryptografisch. Wir betreiben OpenDKIM als Milter, den Postfix über inet:127.0.0.1:8891 anspricht (bereits in der main.cf hinterlegt).

Shell · Installation & Schlüssel
apt -y install opendkim opendkim-tools
mkdir -p /etc/opendkim/keys/example.com

# 2048-bit-Schlüssel, Selector "default"
opendkim-genkey -b 2048 -d example.com -D /etc/opendkim/keys/example.com -s default -v
chown -R opendkim:opendkim /etc/opendkim
chmod 600 /etc/opendkim/keys/example.com/default.private
/etc/opendkim.conf — relevante Zeilen
Syslog                  yes
UMask                   007
Mode                    sv
Canonicalization        relaxed/simple
Socket                  inet:8891@127.0.0.1
OversignHeaders         From
KeyTable                /etc/opendkim/KeyTable
SigningTable            refile:/etc/opendkim/SigningTable
InternalHosts           /etc/opendkim/TrustedHosts
ExternalIgnoreList      /etc/opendkim/TrustedHosts
/etc/opendkim/KeyTable · SigningTable · TrustedHosts
# KeyTable
default._domainkey.example.com example.com:default:/etc/opendkim/keys/example.com/default.private

# SigningTable
*@example.com default._domainkey.example.com

# TrustedHosts: nur diese Clients erzeugen SIGNIERTE Mail (Sign-Modus).
# Bewusst NICHT das ganze /24 — konsistent zu "kein IP-Vertrauen über das VPN".
127.0.0.1
::1
10.10.0.2
Inbound vs. Outbound sauber trennen

Zwei Mechanismen wirken zusammen, damit nur die richtige Mail signiert wird: InternalHosts setzt OpenDKIM nur für 127.0.0.1/::1 (relay-lokal) und den internen MTA 10.10.0.2 in den Sign-Modus — Mail von fremden Internet-MX (eingehend auf Port 25) wird dagegen nur verifiziert, nie neu signiert. Zusätzlich signiert die SigningTable ausschließlich Mail mit Absender @example.com. So wird fremde eingehende Mail niemals versehentlich mit Ihrer Domain-Signatur versehen.

Shell · Start & DNS-Wert auslesen
systemctl enable --now opendkim
systemctl restart postfix

# Diesen TXT-Inhalt in die DNS-Zone übernehmen (Selector default):
cat /etc/opendkim/keys/example.com/default.txt
Prüfen

Nach DNS-Propagation: opendkim-testkey -d example.com -s default -vvv sollte „key OK" melden.

08TLS-Zertifikate mit acme.sh

acme.sh ist ein schlanker ACME-Client ohne Python-Abhängigkeiten. Für einen Mailserver ohne Webserver ist die DNS-API-Validierung ideal (kein offener Port 80 nötig); alternativ funktioniert der Standalone-Modus.

Shell · Installation
apt -y install curl socat
curl https://get.acme.sh | sh -s email=admin@example.com
source ~/.bashrc
acme.sh --set-default-ca --server letsencrypt
Shell · Zertifikat ausstellen (DNS-API, Beispiel Cloudflare)
export CF_Token="cf-api-token"
acme.sh --issue --dns dns_cf -d mail.example.com

# Alternative ohne DNS-API (Port 80 muss frei/erreichbar sein):
# acme.sh --issue --standalone -d mail.example.com

Anschließend die Zertifikate an den von Postfix erwarteten Ort installieren (nicht direkt aus dem acme.sh-Verzeichnis referenzieren) und einen Reload-Hook setzen. So werden bei jeder Erneuerung automatisch die neuen Dateien eingespielt.

Shell · Deploy in Postfix
mkdir -p /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      "systemctl reload postfix"

chmod 600 /etc/postfix/tls/mail.example.com/private.key
Automatische Erneuerung

acme.sh legt bei der Installation einen cron-Eintrag an, der Zertifikate rechtzeitig erneuert und dank --reloadcmd Postfix neu lädt. Kontrolle: acme.sh --list und crontab -l.

09WireGuard-Tunnel zum internen Mailserver

Der WireGuard-Tunnel verbindet den öffentlichen Relay mit dem internen MTA — verschlüsselt, ohne offenes Relay und ohne den internen Server im Internet zu exponieren. Der Relay hat die öffentliche IP und ist damit der „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
cat /etc/wireguard/publickey    # öffentlichen Schlüssel notieren
/etc/wireguard/wg0.conf — auf dem Relay (mail.example.com)
[Interface]
Address    = 10.10.0.1/24
ListenPort = 51820
PrivateKey = <RELAY_PRIVATE_KEY>

[Peer]
# interner Mailserver
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
# hält den Tunnel durch NAT/Firewall offen
PersistentKeepalive = 25
Shell · Aktivieren & testen
systemctl enable --now wg-quick@wg0
wg show
ping -c 2 10.10.0.2        # vom Relay aus
Zusammenspiel mit Postfix

Eingehend nutzt die transport-Map aus Abschnitt 3 die Tunnel-Adresse [10.10.0.2]:25 — hier ist das Relay der Client. Ausgehend ist das Tunnel-Netz bewusst nicht in mynetworks: Der interne MTA meldet sich per SASL auf Port 587 an (siehe Abschnitt 5), sodass auch über den verschlüsselten Tunnel eine Authentifizierung erforderlich ist.

10fail2ban — Schutz gegen Brute-Force

fail2ban wertet die Mail- und SSH-Logs aus und sperrt IPs, die wiederholt fehlschlagende Anmeldungen oder auffälliges SMTP-Verhalten zeigen.

Shell
apt -y install fail2ban rsyslog
/etc/fail2ban/jail.local
[DEFAULT]
bantime  = 1h
findtime = 10m
maxretry = 5
# eigene Netze und den Tunnel niemals sperren
ignoreip = 127.0.0.1/8 ::1 10.10.0.0/24
backend  = systemd

[sshd]
enabled = true

[postfix]
enabled = true
mode    = aggressive

[postfix-sasl]
enabled = true
Shell · Start & Status
systemctl enable --now fail2ban
fail2ban-client status
fail2ban-client status postfix-sasl
Logquelle

Debian 12 protokolliert standardmäßig ins systemd-journal — daher backend = systemd. Wenn Sie klassische Dateien (/var/log/mail.log) bevorzugen, installieren Sie rsyslog (siehe nächster Abschnitt) und setzen die Jails auf backend = auto.

11logrotate — Logmanagement

Damit die Mail-Logs nicht unbegrenzt wachsen, rotiert logrotate sie täglich, komprimiert ältere Stände und begrenzt die Aufbewahrung — was zugleich der datenschutzrechtlichen Datenminimierung dient.

/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 bereits Regeln für die Mail-Logs mit. Entfernen Sie dort die /var/log/mail.*-Zeilen, damit nicht zwei Konfigurationen dieselben Dateien rotieren.

acme.sh schreibt sein Log nach ~/.acme.sh/acme.sh.log und begrenzt es selbst; für fail2ban liefert Debian bereits /etc/logrotate.d/fail2ban. Ein manueller Trockenlauf zeigt, was passieren würde:

Shell · Trockenlauf
logrotate -d /etc/logrotate.d/postfix-relay

12Inbetriebnahme & Tests

1 · TLS auf Port 587 prüfen

Shell
openssl s_client -starttls smtp -crlf -connect mail.example.com:587 -servername mail.example.com

2 · Authentifizierten Versand testen (swaks)

Shell
apt -y install swaks
swaks --server mail.example.com --port 587 -tls \
      --auth LOGIN --auth-user relay-app --auth-password 'GEHEIM' \
      --from noreply@example.com --to check-auth@verifier.port25.com

3 · SPF/DKIM/DMARC im Ergebnis prüfen

Senden Sie eine Mail an einen Dienst wie mail-tester.com oder an ein Gmail-Postfach und prüfen Sie im Header Authentication-Results: auf spf=pass, dkim=pass und dmarc=pass.

4 · Queue & Betrieb

Shell · Warteschlange
postqueue -p                 # Queue anzeigen
postqueue -f                 # Zustellung erzwingen
postsuper -d ALL deferred    # zurückgestellte Mails löschen (mit Bedacht)
journalctl -u postfix -f     # Live-Log

Monitoring & Alerting

Ein Relay braucht Beobachtung — gerade um kompromittierte Konten (plötzlicher Versand-Spike) und Betriebsprobleme früh zu erkennen. Mindestens diese Signale sollten überwacht und alarmiert werden:

  • Queue-Länge (postqueue -p | tail -1) — Anstieg deutet auf Zustell- oder Missbrauchsprobleme.
  • Authentifizierungsfehler & Versandmengen — ungewöhnliche Spitzen pro Konto/Client (Hinweis auf gestohlene Credentials).
  • Zertifikatsablauf (openssl x509 -enddate) und erfolgreiche acme.sh-Erneuerung.
  • Blacklist-Status der ausgehenden IP sowie DMARC-/TLS-RPT-Reports.
  • Dienststatus von postfix, opendkim, wg-quick@wg0, fail2ban.
Shell · einfache Kennzahlen (z. B. für Prometheus-Textfile / Cron-Alert)
# Queue-Größe
mailq | grep -c '^[A-F0-9]'
# SASL-Fehlversuche der letzten Stunde
journalctl -u postfix --since "1 hour ago" | grep -c 'SASL .* authentication failed'
# Tage bis Zertifikatsablauf
openssl x509 -enddate -noout -in /etc/postfix/tls/mail.example.com/fullchain.cer
Werkzeuge

Für dauerhaftes Monitoring bieten sich prometheus-postfix-exporter + Alertmanager oder eine schlanke Cron-Lösung mit Schwellwert-Mail an. Wichtig ist vor allem ein Alarm bei anomaler Versandmenge — das ist die wirksamste Erkennung von Credential-Missbrauch.

13Troubleshooting

SymptomWahrscheinliche UrsacheAnsatz
relay access deniedAbsender weder in mynetworks noch SASL-authentifiziertAuf 587 senden und Auth prüfen; mynetworks kontrollieren
SASL authentication failedsasldb2 im chroot nicht lesbar / falscher RealmBind-Mount/Kopie nach /var/spool/postfix/etc; Realm = myhostname
TLS-Fehler beim StartZertifikatspfad falsch oder Key nicht lesbarpostfix check; Pfade & chmod 600 auf den Key
Mails landen im SpamPTR-Mismatch, fehlendes DKIM/DMARCdig -x, opendkim-testkey, Header prüfen
Interner MTA unerreichbarTunnel unten / AllowedIPs falschwg show, ping 10.10.0.2, Firewall 51820/udp
Shell · gezielt in den Logs suchen
journalctl -u postfix --since "10 min ago" | grep -Ei 'warning|error|reject|sasl|tls'

14Datenschutz & Betrieb (DSGVO)

  • Datenminimierung: Mail-Logs enthalten Absender-, Empfänger- und IP-Daten. Die Aufbewahrung über logrotate (hier 30 Tage) sollte dem tatsächlichen Betriebsbedarf entsprechen und dokumentiert sein.
  • Auftragsverarbeitung: Läuft der Relay bei einem Hoster, gehört dieser als Auftragsverarbeiter mit AVV und — bei Standort außerhalb der EU — passendem Übermittlungsmechanismus ins Verzeichnis der Verarbeitungstätigkeiten.
  • Transportverschlüsselung: STARTTLS ist konfiguriert; für sensible Kommunikation kann smtp_tls_security_level selektiv auf encrypt gehoben oder MTA-STS ergänzt werden.
  • Zugriff: SASL-Konten und WireGuard-Schlüssel sind Zugangsdaten — restriktive Dateirechte, getrennte Konten je Anwendung und regelmäßige Rotation.
Hinweis

Diese Dokumentation beschreibt einen technischen Aufbau und ersetzt keine rechtliche Beratung. Die konkrete DSGVO-Bewertung ist im Einzelfall zu treffen.