Featured image of post docker-mailserver 邮件服务器搭建记录

docker-mailserver 邮件服务器搭建记录

让 Caddy 按要求申请 TLS 证书真是个大坑。

某天早上醒来,发现收件箱里多了封邮件,通知我的邮箱在韩国登录,当时认为只是偶尔的 IP 误判,也就没有多想。

可接下来几天,时不时又有新的邮件进来,而且每次 IP 都不一样,这下可就有些问题了,再不采取行动,怕不是邮箱真的要被拿去群发邮件。于是赶紧删掉了域名的 MX、SPF、DMARC 记录,寻找新的解决方案。

我原本只是想改改密码,结果呢,腾讯企业邮箱改密码的入口真的巨能藏,到处都找不到。算了,迁移,何必受这气。

拉取配置文件

docker-mailserver 需要配置的配置文件有两个,compose.yamlmailserver.env,分别拉取配置文件:

1
2
3
4
mkdir mailserver && cd mailserver
sudo apt install wget
wget -O compose.yaml "https://raw.githubusercontent.com/docker-mailserver/docker-mailserver/master/compose.yaml"
wget -O mailserver.env "https://github.com/docker-mailserver/docker-mailserver/blob/master/mailserver.env"

根据需求,可以修改不同的配置,下面是我修改的部分:

1
2
3
4
5
POSTMASTER_ADDRESS=webmaster@l3zc.com
TZ=Asia/Shanghai
SSL_TYPE=manual
SSL_CERT_PATH=/certs/cert.crt
SSL_KEY_PATH=/certs/cert.key

用 Caddy 维护 TLS 证书1

因为我已经在使用 Caddy,所以我这次就打算直接使用 Caddy 来自动维护证书。根据项目的官方文档,我只需要在 Caddyfile 里新增一个子域名,Caddy 就可以自动帮我维护证书。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
mail.example.com {
  tls internal {
    # ?? 这不是直接生成的内部证书么
    key_type rsa2048
  }

  # Optional, can be useful for troubleshooting
  # connection to Caddy with correct certificate:
  respond "Hello DMS"
}

当我更新完 DNS 记录,实际操作起来发现,我的情况比较特殊:我这台服务器上并没有用到 Caddy 的自动申请证书功能,而是使用了15年有效期的泛域名 Cloudflare Origin Certificate。即使我不为这个子域名指定证书文件,Caddy 也会在加载这张泛域名证书后认为无需再申请新的证书。这就很尴尬了,用 Caddy 的时候 Cloudflare Origin Certificate 和邮件服务器只能二选一。只好安装了dns.providers.cloudflare,反正都是自动的,眼不见心不烦。

安装dns.providers.cloudflare后的全局配置:

1
2
3
{
        acme_dns cloudflare {API_KEY}
}

最后把 Caddy 的证书存储位置映射到 docker-mailserver 容器内。

1
2
volumes:
   - /home/user/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/yourdomain.tld/:/certs/

如果 Caddy 以 root 用户运行,则证书的位置应当为/root/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/yourdomain.tld/,做出相应的修改即可。

然后就是之前提到过的配置,我将证书存储位置映射到了容器内的/certs/目录,所以配置为:

1
2
SSL_CERT_PATH=/certs/cert.crt
SSL_KEY_PATH=/certs/cert.key

防火墙的处理

我的防火墙比较特殊,用到了ufwTo Fix The Docker and UFW Security Flaw Without Disabling Iptables 一文中提到的配置。所以当我想要把 Docker 容器的端口暴露在外时需要做出额外的配置。

1
2
3
4
5
6
# 开放邮件服务器所需的端口
ufw route allow proto tcp from any to any port 25
ufw route allow proto tcp from any to any port 143
ufw route allow proto tcp from any to any port 465
ufw route allow proto tcp from any to any port 587
ufw route allow proto tcp from any to any port 993

首次启动容器

首次启动容器需要立即(120 秒内)创建新的 Postmaster 账号。

1
2
docker compose up -d
docker exec -it <CONTAINER NAME> setup email add <postmaster@yourdomain.tld>

DNS 记录

  • 一条 A 记录,名称随意(假设为mail),指向服务器 IP(Cloudflare 需使用「仅 DNS」)
  • 一条 MX 记录,Apex 域(@),指向mail.yourdomain.tld
  • 一条 TXT 记录(SPF),Apex 域(@),可以在这里生成(可选)。
  • 一条 TXT 记录(DMARC),名称_dmarc,可以在这里生成(可选)。
  • 一条 TXT 记录(DKIM),名称mail._domainkey,具体配置在后文(可选)。

DKIM, SPF 和 DMARC2

什么是 DKIM,SPF 和 DMARC?简单来说,DKIM 是一个数字签名机制;SPF 类似于「员工名单」,记录所有合法的发件服务器;DMARC 则用于指示收件方的服务器如何处理未通过 DKIM 和 SPF 验证的邮件。3

首先设置 DKIM,在服务器上生成公私钥对。

1
docker exec -it <CONTAINER NAME> setup config dkim

生成后的公私钥对可以在映射的容器目录./docker-data/dms/config/opendkim/keys/下找到。

mail.txt 便是公钥的 DNS Zone file Cloudflare 的导入入口

下载 mail.txt(复制其内所有内容粘贴到本地文件也可以),将其导入到 Cloudflare Dashboard 即可完成 DKIM 设置。需要注意配置完成 DKIM 后需要重启容器才能生效。4

1
docker compose up -d --force-recreate

前面提到 SPF 相当于一个「员工名单」,既然我合法的发件服务器只有一台,那么 DNS 填写如下设置即可。

1
"v=spf1 mx ~all"

再就是 DMARC,可以使用前面提到的模板生成,这里不再赘述。

如果你的域名服务商拥有自己的设置向导(例如 Cloudflare),亦可以使用它们简化设置流程。

客户端设置

客户端设置相对就很简单了,IMAP 和 SMTP 服务器都是mail.yourdomain.ltd,开启 SSL 后端口分别是 465 和 993,登录即可。

设置示例

TODO-List

  • 配套 WebUI