MTA with Postfix - ionathanch/ionathanch.github.io GitHub Wiki

The examples below use domain.tld as the server domain, mail as an additional subdomain, and xxx.xxx.xxx.xxx and xxxx:xxxx:xxxx:xxxx::1 as the IPv4 and IPv6 addresses. Other values meant to be substituted with real values are marked by {braces}.

DNS records

Type Host Value
A mail xxx.xxx.xxx.xxx
AAAA mail xxxx:xxxx:xxxx:xxxx::1
MX @ mail.domain.tld
TXT @ v=spf1 mx ~all
TXT _dmarc v=DMARC1; p=none; pct=100
TXT {selector}._domainkey v=DKIM1; h=sha256; k=rsa; p={public key}
  • SPF: allow emails from addresses pointed at by domains in MX records
  • DMARC: do nothing for failed DMARC checks
  • DKIM: generated using OpenDKIM

DKIM setup

See How-To Geek’s guide for details.

  1. apt install opendkim opendkim-tools
  2. Add to /etc/opendkim.conf:
    Socket                  inet:8892@localhost # uncomment
    AutoRestart             Yes
    AutoRestartRate         10/1h
    Canonicalization        relaxed/simple
    SignatureAlgorithm      rsa-sha256 # ed25519-sha256 not well supported
    
    ExternalIgnoreList      refile:/etc/opendkim/trusted.hosts
    InternalHosts           refile:/etc/opendkim/trusted.hosts
    KeyTable                refile:/etc/opendkim/key.table
    SigningTable            refile:/etc/opendkim/signing.table
        
  3. Create /etc/opendkim/trusted.hosts:
    127.0.0.1
    localhost
    *.domain.tld
    xxx.xxx.xxx.xxx
    [xxxx:xxxx:xxxx:xxxx::]/64
        
  4. Create /etc/opendkim/signing.table:
    *@domain.tld {selector}._domainkey.domain.tld
        
  5. Create /etc/opendkim/key.table:
    {selector}._domainkey.domain.tld domain.tld:{selector}:/etc/opendkim/keys/{selector}.private
        
  6. Create /etc/opendkim/keys/ and in that directory run:
    $ opendkim-genkey -d domain.tld -s {selector}
    $ chown opendkim:opendkim /etc/opendkim/keys/{selector}.private
    $ opendkim-testkey -d domain.tld -s {selector} -vvv
        
  7. Copy /etc/opendkim/keys/mail.txt to TXT record
  8. Add to /etc/postconf/main.cf:
    milter_protocol = 2
    milter_default_action = accept
    smtpd_milters = inet:localhost:8892
    non_smtpd_milters = inet:localhost:8892
        

rDNS with Hetzner

The server IPv6 address can be found using ip -6 addr show scope global.

Postfix configuration

Configuration file /etc/postfix/main.cf can be checked with postfix check, edited with postconf -e {key}={value}, and reloaded with postfix reload. See Postfix Configuration Parameters for more.

...
mydomain                = domain.tld # mail system domain name
myhostname              = domain.tld # mail system hostname; default for $myorigin and other parameters
# mynetworks              = xxx.xxx.xxx.xxx [xxxx:xxxx:xxxx:xxxx::]/64 127.0.0.0/8 [::1]/128 [fe80::]/64
# inet_interfaces         = loopback-only # don't accept incoming mail
# inet_protocols          = all # accept from ipv4 and ipv6
mailbox_size_limit      = 0 # no limit
recipient_delimiter     = + # mail for [email protected] goes to [email protected]
smtp_address_preference = ipv6 # prefer sending over IPv6 (make sure MX records and SPF include it!)
alias_map               = hash:/etc/aliases
virtual_alias_map       = hash:/etc/postfix/virtual

# reject if no A record, rDNS, MX record, or malformed domain
smtpd_sender_restrictions =
  permit_mynetworks
  permit_sasl_authenticated
  reject_unknown_client_hostname
  reject_unknown_reverse_client_hostname
  reject_unknown_sender_domain
  reject_non_fqdn_sender
# reject if hostname from HELO/EHLO is missing or wrong
smtpd_helo_required = yes
smtpd_helo_restrictions =
  permit_mynetworks
  permit_sasl_authenticated
  reject_invalid_helo_hostname
  reject_non_fqdn_helo_hostname
  reject_unknown_helo_hostname
...

Test mail can be sent using mailutils with mailx, e.g.

mailx -a "From: Sender Name <[email protected]>" -s "Subject Line" "Recipient Name <[email protected]>"

(Virtual) aliases can be updated using postmap /etc/postfix/virtual and postalias /etc/aliases. See virtual(5) and aliases(5) syntax.

Ports and firewalls

Configuration file /etc/postfix/master.cf needs to have SMTPS and submission uncommented out to use ports 485 and 587.

...
submission inet n       -       y       -       -       smtpd
  # plus a bunch of -o options
smtps     inet  n       -       y       -       -       smtpd
  # plus a bunch of -o options
...

Hetzner

  • Outbound ports 25 and 465 are blocked by default; request unblocking them through Limits.
  • Inbound ports need to be allowed through the firewall at https://console.hetzner.cloud/projects/{project}/firewalls/{server}/rules.

UFW

$ ufw allow Postfix              # port 25
$ ufw allow "Postfix SMTPS"      # port 465
$ ufw allow "Postfix Submission" # port 587
$ ufw status verbose             # check that ports are allowed
$ postfix status                 # get the Postfix process {PID}
$ netstat -tnlp | grep {PID}     # check that Postfix is actually listening on the ports

TLS certificate with Certbot

  1. certbot certonly --standalone -d mail.domain.tld (webservers must not be up!)
  2. Set in /etc/postfix/main.cf:
    tls_random_source = dev:/dev/urandom
    
    smtpd_use_tls = yes
    smtpd_tls_cert_file = /etc/letsencrypt/live/mail.domain.tld/fullchain.pem
    smtpd_tls_key_file = /etc/letsencrypt/live/mail.domain.tld/privkey.pem
    smtpd_tls_security_level = may
    
    smtp_use_tls = yes
    smtpd_tls_cert_file = /etc/letsencrypt/live/mail.domain.tld/fullchain.pem
    smtpd_tls_key_file = /etc/letsencrypt/live/mail.domain.tld/privkey.pem
    smtp_tls_security_level = may
        
⚠️ **GitHub.com Fallback** ⚠️