diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-24 10:49:14 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-24 10:49:14 +0200 |
| commit | c30b3233c725fcd60feb4bec79230b434ef0f7f8 (patch) | |
| tree | bca77c414f357caec55c59518508fb02b6c21d1e | |
| parent | 059eb0318daf27d98bd1c9532705ff406c0a10f8 (diff) | |
| download | meshbay-c30b3233c725fcd60feb4bec79230b434ef0f7f8.tar.gz | |
docs(mail): route @meshbay.org mail to OVH, document the real setup
Mail to OVH redirections such as devel@meshbay.org never left the server:
Postfix listed meshbay.org in mydestination and delivered it locally, and
systemd-resolved answered MX queries for the hostname with an empty
authoritative reply. New §9 states both settings and how to check them.
§1 now shows the DNS records as deployed (IPv6, DKIM, DMARC quarantine,
PTR), §2 adds the IPv6 address to SPF and PTR, and §3 describes the
installation itself — packages, main.cf, DKIM key, OpenDKIM tables,
resolved drop-in — instead of pointing at a script outside the repository.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| -rw-r--r-- | docs/MAIL-SERVER.md | 209 |
1 files changed, 175 insertions, 34 deletions
diff --git a/docs/MAIL-SERVER.md b/docs/MAIL-SERVER.md index c5a0d17..cbad53d 100644 --- a/docs/MAIL-SERVER.md +++ b/docs/MAIL-SERVER.md @@ -1,6 +1,7 @@ # Mail Server — Postfix send-only with SPF/DKIM/DMARC -> Target: meshbay.org (Ubuntu 26.04 LTS, OVH VPS 164.132.246.44) +> Target: meshbay.org (Ubuntu 26.04 LTS, OVH VPS 164.132.246.44 / +> 2001:41d0:20a:900::2389) > > Purpose: send confirmation codes to validate user email addresses at > registration. The hub sends mail; it does not receive it. OVH MX servers @@ -8,16 +9,18 @@ --- -## 1. Current DNS state (before setup) +## 1. DNS state -| Record | Value | Status | -|---|---|---| -| A | `164.132.246.44` | OK | -| MX | `1 mx1.mail.ovh.net` / `5 mx2` / `100 mx3` | OK — receiving stays OVH | -| SPF | `v=spf1 include:mx.ovh.com -all` | Must add VPS IP | -| DKIM | (none) | Must create | -| DMARC | (none) | Must create | -| PTR (rDNS) | `vps-7148538e.vps.ovh.net` | Must change to `meshbay.org` | +| Record | Value | +|---|---| +| A / AAAA | `164.132.246.44` / `2001:41d0:20a:900::2389` | +| MX | `1 mx1.mail.ovh.net` / `5 mx2` / `100 mx3` — inbound mail and redirections stay at OVH | +| SPF | `v=spf1 ip4:164.132.246.44 ip6:2001:41d0:20a:900::2389 include:mx.ovh.com -all` | +| DKIM | `meshbay._domainkey` — RSA 2048, `h=sha256` | +| DMARC | `v=DMARC1; p=quarantine; sp=quarantine; adkim=s; aspf=s; pct=100; rua=mailto:postmaster@meshbay.org` | +| PTR (rDNS) | `meshbay.org.` for both the IPv4 and the IPv6 address | + +DMARC is at the intermediate `quarantine` step; `reject` is the target (§7). ## 2. What must happen @@ -28,25 +31,28 @@ Set via the **OVH control panel** (not DNS zone): 1. OVH Manager → Bare Metal Cloud → VPS → IP 2. Click the gear icon next to `164.132.246.44` → Modify reverse DNS 3. Set to: `meshbay.org.` (trailing dot) +4. Same for `2001:41d0:20a:900::2389` — Postfix sends over IPv6 whenever the + receiver has an AAAA MX (Gmail does) Most receiving MTAs reject or score down mail from an IP whose PTR does not match the HELO/EHLO hostname. ### 2.2 DNS zone records (OVH DNS zone editor) -**Update SPF** — add the VPS IP alongside the existing OVH include: +**Update SPF** — add both VPS addresses alongside the existing OVH include: ``` -meshbay.org. TXT "v=spf1 ip4:164.132.246.44 include:mx.ovh.com -all" +meshbay.org. TXT "v=spf1 ip4:164.132.246.44 ip6:2001:41d0:20a:900::2389 include:mx.ovh.com -all" ``` -**Add DKIM** — after running the install script which generates the key: +**Add DKIM** — once the key is generated (§3.3): ``` meshbay._domainkey.meshbay.org. TXT "v=DKIM1; h=sha256; k=rsa; p=<PUBLIC_KEY_BASE64>" ``` -The script prints the exact record content. The selector is `meshbay`. +The `p=` value is in `/etc/opendkim/keys/meshbay.org/meshbay.txt`, written by +`opendkim-genkey`. The selector is `meshbay`. **Add DMARC** — start with `none` policy (monitoring), tighten to `quarantine` then `reject` after confirming deliverability: @@ -60,12 +66,13 @@ Target (after validation): _dmarc.meshbay.org. TXT "v=DMARC1; p=reject; sp=reject; adkim=s; aspf=s; rua=mailto:postmaster@meshbay.org" ``` -### 2.3 Server-side (automated by the install script) +### 2.3 Server-side | Component | Role | |---|---| | **Postfix** | MTA — sends mail directly to recipient MX servers | | **OpenDKIM** | Signs outgoing mail with the domain's DKIM private key | +| **systemd-resolved drop-in** | Lets the domain's MX resolve on a host named after the domain (§9.2) | Postfix is configured as **send-only** (no listening on port 25 from outside). @@ -73,27 +80,112 @@ Postfix is configured as **send-only** (no listening on port 25 from outside). ## 3. Installation -Run the provided script on the server: +### 3.1 Packages ```bash -# From the local machine: -scp QE/deploy/setup-mailserver.sh cbesson@meshbay.org:/tmp/ -ssh cbesson@meshbay.org 'sudo bash /tmp/setup-mailserver.sh' +# Ubuntu — answer "Internet Site", mail name meshbay.org +sudo apt install postfix opendkim opendkim-tools mailutils +# Fedora +sudo dnf install postfix opendkim opendkim-tools mailx +``` + +### 3.2 Postfix — `/etc/postfix/main.cf` + +``` +smtpd_banner = $myhostname ESMTP +biff = no +append_dot_mydomain = no + +smtp_tls_security_level = may +smtp_tls_CApath = /etc/ssl/certs +smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache +smtp_tls_loglevel = 1 + +myhostname = meshbay.org +myorigin = meshbay.org +mydestination = localhost.localdomain, localhost +mynetworks = 127.0.0.0/8 [::1]/128 + +inet_interfaces = loopback-only +inet_protocols = all + +relayhost = +smtpd_relay_restrictions = permit_mynetworks, reject_unauth_destination + +mailbox_size_limit = 0 +recipient_delimiter = + + +milter_protocol = 6 +milter_default_action = accept +smtpd_milters = local:opendkim/opendkim.sock +non_smtpd_milters = $smtpd_milters + +compatibility_level = 3.9 ``` -Or directly on the server: +`inet_interfaces = loopback-only` makes the server send-only; `meshbay.org` is +absent from `mydestination` so that mail to its OVH mailboxes and redirections +goes through the MX lookup (§9.1). + +### 3.3 DKIM key + +```bash +sudo mkdir -p /etc/opendkim/keys/meshbay.org +sudo opendkim-genkey -b 2048 -d meshbay.org -s meshbay -D /etc/opendkim/keys/meshbay.org/ +sudo chown -R opendkim:opendkim /etc/opendkim +sudo chmod 600 /etc/opendkim/keys/meshbay.org/meshbay.private +``` + +### 3.4 OpenDKIM + +`/etc/opendkim.conf`: + +``` +Syslog yes +SyslogSuccess yes +LogWhy yes +Canonicalization relaxed/simple +Mode s +SubDomains no +KeyTable /etc/opendkim/KeyTable +SigningTable refile:/etc/opendkim/SigningTable +InternalHosts /etc/opendkim/TrustedHosts +OversignHeaders From +Socket local:/var/spool/postfix/opendkim/opendkim.sock +PidFile /run/opendkim/opendkim.pid +UMask 007 +UserID opendkim +``` + +| File | Content | +|---|---| +| `/etc/opendkim/KeyTable` | `meshbay._domainkey.meshbay.org meshbay.org:meshbay:/etc/opendkim/keys/meshbay.org/meshbay.private` | +| `/etc/opendkim/SigningTable` | `*@meshbay.org meshbay._domainkey.meshbay.org` | +| `/etc/opendkim/TrustedHosts` | `127.0.0.1`, `::1`, `localhost`, `meshbay.org` — one per line | + +The socket lives inside the Postfix spool so that Postfix reaches it: ```bash -sudo bash setup-mailserver.sh +sudo mkdir -p /var/spool/postfix/opendkim +sudo chown opendkim:postfix /var/spool/postfix/opendkim +sudo chmod 750 /var/spool/postfix/opendkim +sudo usermod -aG opendkim postfix ``` -The script: -1. Installs Postfix + OpenDKIM -2. Configures Postfix as send-only (inet_interfaces = loopback-only) -3. Generates a 2048-bit RSA DKIM key with selector `meshbay` -4. Wires OpenDKIM into Postfix via milter -5. Prints the DKIM TXT record to add to DNS -6. Sends a test email (if an address is given as argument) +### 3.5 systemd-resolved + +Install the drop-in described in §9.2, then +`sudo systemctl daemon-reload && sudo systemctl restart systemd-resolved`. + +### 3.6 Start + +```bash +sudo systemctl enable --now opendkim postfix +sudo systemctl restart opendkim postfix +dig +short MX meshbay.org # must list the OVH MX (§9.2) +``` + +Then add the DNS records of §2.2 and set the PTR (§2.1). ## 4. Verification @@ -210,9 +302,58 @@ Once deliverability is confirmed (SPF pass, DKIM pass on test emails): ## 8. Fedora 44 notes -The install script auto-detects Fedora and uses `dnf` instead of `apt`. -OpenDKIM is in the `opendkim` package, same as Ubuntu. The main difference is -the Postfix service name (`postfix` on both) and that SELinux may need a policy -for the OpenDKIM socket. The script handles this with `setsebool -P -dkim_milter_enable on` if SELinux is enforcing, and falls back to a custom -module if the boolean does not exist. +Packages come from `dnf` (§3.1); OpenDKIM is in the `opendkim` package and the +Postfix service is `postfix`, as on Ubuntu. When SELinux is enforcing, Postfix +needs permission to connect to the OpenDKIM socket: +`sudo setsebool -P dkim_milter_enable on`. If that boolean does not exist, a +local module granting `postfix_smtpd_t` `connectto` on `opendkim_t` +`unix_stream_socket` does the same. + +## 9. Hostname equal to the mail domain + +The server's hostname is `meshbay.org`, which is also the domain whose mailboxes +and redirections live at OVH. Two settings keep mail addressed to `@meshbay.org` +(for example `devel@meshbay.org`, an OVH redirection) going to OVH's MX instead +of being handled on the server. + +### 9.1 `mydestination` does not list the domain + +``` +mydestination = localhost.localdomain, localhost +``` + +Postfix delivers locally every domain listed in `mydestination`, without an MX +lookup. With `meshbay.org` in the list — which is also what the default +`$myhostname` expands to — a message to `devel@meshbay.org` is refused with +`550 5.1.1 User unknown in local recipient table`, or bounced by `postfix/local` +into the local mailbox of the sender, and never reaches OVH. + +Consequence: mail for `root` and `cbesson` (cron output, bounces) is qualified +with `myorigin = meshbay.org` and goes to OVH too. It is delivered only if the +address exists there as a mailbox or a redirection. + +### 9.2 systemd-resolved does not answer for the hostname + +``` +# /etc/systemd/system/systemd-resolved.service.d/no-synthesize-hostname.conf +[Service] +Environment=SYSTEMD_RESOLVED_SYNTHESIZE_HOSTNAME=0 +``` + +systemd-resolved answers queries for the local hostname itself: A/AAAA with the +machine's addresses, and an authoritative empty answer (`aa`, `NOERROR`, no +records) for every other type, MX included. Postfix then falls back to the A +record and connects to its own port 25, which is loopback-only: +`status=deferred (connect to meshbay.org[164.132.246.44]:25: Connection refused)`. +The drop-in turns the synthesis off, so the query goes to the upstream resolver +and returns OVH's MX. Hostname resolution for `sudo` and other local tools is +unaffected: it goes through the NSS `myhostname` module, not through resolved. + +After `systemctl daemon-reload && systemctl restart systemd-resolved`: + +```bash +dig +short MX meshbay.org # must list mx1/mx2/mx3.mail.ovh.net +echo test | mailx -s test devel@meshbay.org +sudo journalctl -u postfix@- --since "1 min ago" | grep status= +# Expected: relay=mx1.mail.ovh.net[...]:25, status=sent +``` |