SSD Nodes Learn
指南 Matt Connor作者: Matt Connor · 更新于 2026-07-19

在 VPS 上用 Mailcow 自建邮件服务器

在 VPS 上用 mailcow 自建完整邮件服务器:开放 25 端口,配置 MX/SPF/DKIM/DMARC/PTR 记录,在 mail-tester 拿到满分 10/10,并排查每一种投递失败。

您要搭建的东西

在一台您自己拥有的服务器上运行一套完整的邮件服务器:用 SMTP 收发邮件,用 IMAP 让您的手机和笔记本保持同步,一个网页邮件(webmail)客户端,以及一个对进出两个方向的每封邮件都打分的垃圾邮件过滤器。mailcow-dockerized 把 Postfix、Dovecot、Rspamd、SOGo webmail、MariaDB、Redis 和一个 ACME 客户端打包进一个 Docker Compose 技术栈,所以软件本身并不是难点。您可以在半小时内让它跑起来。

难的是它周围的一切。电子邮件是这样一种服务:整个互联网的其余部分会主动不信任一台全新的服务器;而“它能用”和“Gmail 悄悄吞掉每一封邮件”之间的差距,归结为四条 DNS 记录,以及一项您可能无法完全掌控的 IP 信誉设置。在您租用任何东西之前,请先阅读下面的前置条件。如果读完之后,您认定这种维护 IP 信誉的苦活不值得,那也是一个合理的答案 —— 我们对 2026 年真正值得自建的服务的盘点正是出于这些原因,把电子邮件归入“只有当您是认真的才做”这一类。

前置条件本身就是整个项目

只要漏掉其中任何一项,您发出的邮件就永远到不了。下面大致按每一项坑住人的频率排序:

出站 25 端口必须开放。 您的服务器通过 TCP 25 端口把邮件投递给 Gmail 和微软。相当大一部分 VPS 和云服务商为了对抗垃圾邮件,默认封锁出站 25 端口,而且这种封锁是无声的 —— 启动时不会报任何错,一切看起来都很健康,邮件只是永远卡在队列里。在安装任何东西之前先测试它。如果被封锁,唯一的办法就是提交工单请求服务商为您开放;有些服务商会为老账户开放,有些则永远不会。

一个信誉可用的干净 IP。 回收再利用的 VPS IP 往往因为上一个租户发垃圾邮件而已经上了黑名单。在下定决心之前,用 Spamhaus 查询或 mxtoolbox 这类服务检查一下您的 IP。一个被列入黑名单的 IP 意味着您无论怎么写配置都绕不过去的拒收。

对 DNS 的掌控,外加一条正确的 PTR 记录。 您需要往域名的区域文件里添加记录,还需要让服务器 IP 的反向 DNS(PTR)指回您的邮件主机名。PTR 几乎从不在您的 DNS 面板里设置 —— 它归属于 IP 的拥有者,所以要在您的 VPS 服务商控制面板里设置,或通过工单设置。

6 GiB 内存和 2 个 vCPU 是舒适的下限。 mailcow 自己给出的最低要求是:私人安装 6 GiB 内存加 1 GiB swap,一旦有若干用户依赖它,则建议 8 GiB。在大约 2.5 GiB 以下,generate_config.sh 会提议禁用 ClamAV 病毒扫描器,以免内核开始杀掉容器。起步给它 20 GB 的 SSD。

一个 DNS 名称,而不是一个裸 IP。 选一个像 mail.example.com 这样的主机名。这一个名称会成为您的 MAILCOW_HOSTNAME、您的 TLS 证书主题、您的 PTR 目标以及您的 SMTP 欢迎横幅。到处都要保持一致。

第 1 步 —— 证明出站 25 端口是开放的

先做这件事。如果这一步失败,其他一切都是白费功夫。在这台全新的 VPS 上,试着与一台真实的邮件服务器打开一次 SMTP 会话:

sudo apt update && sudo apt install -y netcat-openbsd
nc -vz -w 5 gmail-smtp-in.l.google.com 25

成功的结果是即时的:

Connection to gmail-smtp-in.l.google.com (142.250.x.x) 25 port [tcp/smtp] succeeded!

被封锁的端口会挂起整整五秒,然后失败:

nc: connect to gmail-smtp-in.l.google.com port 25 (tcp) timed out: Operation now in progress

那个超时就是封锁。它是服务商一侧的网络过滤,不是您的防火墙,所以任何本地改动都修不好它。提交一个工单:“请为我位于 <IP> 的 VPS 启用出站 TCP 25 端口;我运行的是一台合法的邮件服务器。”在这条命令返回“succeeded”之前,不要安装 mailcow。请注意,入站 25 端口(其他服务器连到您)是另一条独立的路径,通常是开放的 —— 服务商限制的是出站这一侧。

第 2 步 —— 现在就设置 DNS 记录

DNS 改动需要时间才能传播开,所以在安装之前尽可能把能发布的都发布出去。假设您的域名是 example.com,邮件主机是 mail.example.com,IP 是 10.0.0.10。在您的区域文件里,创建:

mail.example.com.        A      10.0.0.10
mail.example.com.        AAAA   2001:db8::10          ; only if you have IPv6
example.com.             MX  10 mail.example.com.
example.com.             TXT    "v=spf1 mx -all"
_dmarc.example.com.      TXT    "v=DMARC1; p=none; rua=mailto:postmaster@example.com"

这条 SPF 记录的意思是“只有我的 MX 才能为这个域名发信,其余一律拒绝”。DMARC 从 p=none 起步,这样您可以观察报告而不至于把自己的邮件退回;一旦对齐得到验证,再收紧到 p=quarantine,然后 p=reject。还有两条记录是故意留空的:DKIM,mailcow 会在第 6 步为您生成;以及 PTR,现在就在您服务商的面板里设置。

10.0.0.10 的 PTR(反向 DNS)设置为 mail.example.com —— 也就是 MAILCOW_HOSTNAME 的确切值。这是最多人遗忘的一条记录,而大型服务商会因它而拒收。如果您的面板没有 rDNS 字段,就提交一个工单。

第 3 步 —— 安装 Docker

mailcow 需要 Docker Engine 以及 Compose v2 插件。请使用 Docker 官方的便捷脚本,而不是 Ubuntu 的 docker.io 软件包,后者根本不附带 Compose 插件:

curl -fsSL https://get.docker.com | sudo sh
sudo docker compose version

您应该会看到一行 Docker Compose version v2.x。如果 docker compose version 打印出 docker: 'compose' is not a docker command,说明 Docker Engine 已安装,但 Compose 插件没有。从 Docker 的仓库安装该插件 —— 重新运行上面的脚本,或者按照我们的 Docker Compose 基础指南来做,它会从 Docker 自己的 apt 仓库把两者都装好。

第 4 步 —— 克隆 mailcow 并生成配置

cd /opt
sudo git clone https://github.com/mailcow/mailcow-dockerized
cd mailcow-dockerized
umask
sudo ./generate_config.sh

先检查 umask 是否打印出 0022 —— mailcow 拒绝在异常的文件掩码下构建,而一个全新的 Ubuntu 24.04 root shell 已经给了您 0022。脚本随后会询问唯一重要的一件事:完全限定的主机名。输入 mail.example.com —— 这个值必须与您的 A 记录和 PTR 完全一致。它会写出 mailcow.conf,也就是整个技术栈唯一读取的环境文件。如果您需要更改网页端口(HTTP_PORTHTTPS_PORT),或者在一台小机器上禁用 ClamAV,就打开它:

MAILCOW_HOSTNAME=mail.example.com
HTTP_PORT=80
HTTPS_PORT=443
SKIP_CLAMD=n          # set to y to drop the virus scanner on a <2.5 GiB box

在低内存机器上,SKIP_FTS=y 是另一个可用的开关:全文检索是 mailcow 文档点名的第二大内存消耗,而跳过它只会让您在 webmail 里失去正文文本检索的功能。

除非主机上已有别的东西占用了它们,否则请保持 HTTP_PORT=80HTTPS_PORT=443 不变 —— mailcow 内置的 ACME 客户端需要 80 端口能从互联网访问到才能获取证书。这就是为什么您不要在同一台机器上运行另一套 nginx 加 Certbot 的方案;mailcow 会在内部签发并续期自己的 TLS,而第二个服务霸占 80/443 会破坏这一点。

第 5 步 —— 启动技术栈并登录

sudo docker compose pull
sudo docker compose up -d
sudo docker compose ps

这次拉取大约会获取二十多个镜像;请给它几分钟。当 docker compose ps 显示每个容器都是 running(或 healthy)时,在浏览器里打开 https://mail.example.com。默认的管理员登录是用户名 admin、密码 moohoo。请立即在管理界面的 Access → Administrators 下更改该密码。如果浏览器警告 NET::ERR_CERT_AUTHORITY_INVALID,说明 ACME 证书还没签发出来 —— 在断定它坏了之前,先看下面的 ACME 失败一节;头一两分钟出现一个自签名的占位证书是正常的。

第 6 步 —— 添加域名、邮箱,并发布 DKIM

在管理界面里,打开 Mail Setup 页面(Configuration → Mail Setup),在 Domains 选项卡下点击 Add domain 并输入 example.com。然后在 Mailboxes 下,用 Add mailbox 创建 you@example.com 并设置一个密码。这样一个可用的邮箱就有了,而且已经可以通过 IMAP 访问。

现在是 DKIM 密钥。进入 Configuration → ARC/DKIM keys;在您添加域名时 mailcow 可能已经生成了一个密钥,如果没有,就在这里生成一个 —— 选择域名,选择器保持 dkim,选择 2048 位,然后点击 Add。复制它显示的那一长串 TXT 值,并把它发布为:

dkim._domainkey.example.com.  TXT  "v=DKIM1;k=rsa;t=s;s=email;p=MIIBIjANBgkqh...long-key...QAB"

mailcow 的 Domains 页面上有一个 DNS 按钮,它会列出它期望的每一条记录,并针对实际已发布的内容显示绿色对勾或红色叉号。把它当作您的核对清单 —— 在测试投递能力之前,让每一行都变绿。发布之后 DKIM 那一行仍然是红的,通常意味着密钥被错误地拆分到了多个 TXT 分块里;一个 2048 位的密钥比单条 TXT 字符串 255 个字符的上限还要长,所以请把它作为一个逻辑值粘贴进去,让您的 DNS 托管商替您拆分成分块。

第 7 步 —— 测试投递能力,冲击 10/10

打开 mail-tester.com,复制它显示的那个随机地址,然后从您的新邮箱给它发一封邮件 —— 在 https://mail.example.com/SOGo 登录 SOGo webmail,从那里发送。然后点击“Then check your score”。

目标是 10/10。常见的扣分项及其原因:

  • SPF 未对齐 —— 您的 MX/SPF 记录缺失,或者发送 IP 未被覆盖。重新检查 SPF 的 TXT 记录。
  • DKIM 签名无法验证 —— dkim._domainkey 的 TXT 记录缺失、仍在传播中,或被弄乱了。这是最常见的疏漏。
  • 没有 PTR / PTR 不匹配 —— 反向 DNS 没有解析到 mail.example.com。在服务商处修复。
  • 被列入黑名单 —— 您 IP 之前的信誉问题。申请移除,或者要求换一个更干净的 IP。

在这个分数读到 10/10 之前,不要向 Gmail 或 Outlook 发送真实邮件。低分再加上一个新 IP,正是让您的域名在第一天就被标记的原因。

第 8 步 —— 连接一个真实的邮件客户端

用下面这些设置,把 Thunderbird、Apple Mail 或您的手机指向这台服务器。对所有客户端来说,服务器主机都是 mail.example.com

  • IMAP: 993 端口,SSL/TLS(或 143 端口配 STARTTLS)
  • SMTP 提交: 465 端口,SSL/TLS(或 587 端口配 STARTTLS)
  • 用户名: 完整地址,you@example.com
  • 密码: 您设置的邮箱密码

绝不要通过 25 端口从客户端发信 —— 那个端口只用于服务器之间,mailcow 在那里不提供带认证的提交,指向它的客户端会被拒绝。如果某个客户端报告 Relay access denied,说明它正试图在 25 端口上、或者在没有认证的情况下发送;把它切换到 465 或 587 端口,并使用您的邮箱凭据。

第 9 步 —— 备份真正重要的东西

mailcow 附带一个备份脚本,它会为每一个有状态的卷做快照。把它运行到一块外部磁盘或一个挂载的远程位置上:

sudo MAILCOW_BACKUP_LOCATION=/opt/mailcow-backups \
  ./helper-scripts/backup_and_restore.sh backup all

all 会捕获六样东西,丢失其中任何一样都会丢失数据:vmail(真正的邮箱)、crypt(解密 vmail 的密钥 —— 没有它 vmail 就没用)、mysql(保存域名、用户、别名和设置的 MariaDB)、redis(队列和缓存状态)、rspamd(学习到的垃圾邮件/正常邮件数据),以及 postfix(邮件队列)。它在一个辅助容器里运行,写出压缩归档,所以即使技术栈还在运行,备份也能保持一致。用每晚一次的 cron 任务把它自动化,并加上 --delete-days 14 来清理旧的备份集。恢复用的是同一个脚本加 restore,它会列出各个快照,让您挑选要恢复回来的内容。一个您从未测试恢复过的备份,只是一种指望,而不是备份 —— 请在一台临时 VPS 上做一次演练。

第 10 步 —— 按计划更新

mailcow 通过它自己的脚本更新,该脚本会拉取新代码、迁移 mailcow.conf、预取镜像,并按顺序重启容器:

cd /opt/mailcow-dockerized
sudo ./update.sh --check   # reports whether an update exists, changes nothing
sudo ./update.sh           # applies it

先备份(第 9 步),因为数据库结构迁移很难回退。更新来得很频繁,而且包含面向互联网的守护进程的安全修复,所以不要让一台邮件服务器好几个月无人问津。如果某次更新之后有容器处于不健康状态,sudo docker compose logs --tail=50 <service>-mailcow 会指出没能恢复过来的那个守护进程。

关于加固的说明

mailcow 运行着它自己的 netfilter 服务(netfilter-mailcow),会封禁猛烈敲打邮件和 webmail 端口的 IP,所以邮件这一侧开箱即得到防护。但这并不覆盖主机本身的 SSH,它依然暴露在外,依然会被暴力破解 —— 请把这套搭建与监视 SSH 认证日志的 Fail2ban以及仅密钥登录搭配使用。让 mailcow 管理界面处于一个强密码之后,并且最好不要放在公网上,或者放在 VPN 之后。

失败模式,附带确切的字符串

邮件排队却永远投递不出去。 运行 sudo docker compose exec postfix-mailcow postqueue -p,或者查看管理界面的邮件队列;条目会以 deferred 状态卡在那里,并带有:

status=deferred (connect to gmail-smtp-in.l.google.com[142.250.x.x]:25: Connection timed out)

这就是您的服务商封锁了出站 25 端口(第 1 步)。没有配置能修好它 —— 提交一个工单。这不是 DNS,也不是 TLS 的问题;判断依据就是针对 25 端口上某个远程 MX 出现的 timed out 字样。

Gmail 把一切都标记为垃圾邮件,或者把邮件退回。 在 Gmail 里打开这封邮件,点击“Show original”(显示原始邮件),阅读认证结果。dkim=faildkim=none 意味着您的 dkim._domainkey TXT 记录缺失、被弄乱,或者尚未传播开 —— 请原样重新发布 ARC/DKIM 页面所显示的内容,然后等待 TTL 过去。spf=fail 意味着 SPF/MX 记录没有覆盖您的 IP。对齐就是一切;只要有一项检查失败,就足以让邮件落进垃圾箱。

在连接时被大型服务商拒绝。 退信或 Postfix 日志里会带有 Gmail 的 PTR 拒绝信息:

550-5.7.25 [10.0.0.10] The IP address sending this message does not have a PTR
550-5.7.25 record setup, or the corresponding forward DNS entry does not match
550 5.7.25 the sending IP. As a policy, Gmail does not accept messages from IPs
550 5.7.25 with missing PTR records.

550 5.7.25 这个代码意味着反向 DNS 缺失或不匹配。在服务商处把您 IP 的 PTR 设置为 mail.example.com(第 2 步)。正向(A)和反向(PTR)必须一致,而且两者都必须指向 mailcow 向其他服务器打招呼时所用的同一台主机。

浏览器显示一个始终不消失的证书警告。 acme-mailcow 容器没能拿到一张真正的证书。检查它的日志:

sudo docker compose logs acme-mailcow | tail -n 40

出现像 Cannot validate any hostnames, skipping Let's Encrypt for 1 hour. 这样的一行,或者一次质询失败,意味着 80 端口无法从互联网访问,或者 A 记录没有指向这台服务器。确认 mail.example.com 解析到这台机器,在任何主机防火墙上开放 80 和 443 端口,并确保没有别的东西占用这些端口。修好根因之后,用 sudo docker compose restart acme-mailcow 重启该客户端,而不是干等那长达一小时的退避。

FAQ

自建电子邮件到底值不值得?

如果您想要数据所有权、无限量的别名和完全的掌控,那么值得 —— mailcow 用一台 VPS 的价格就给了您一套专业级的技术栈。但投递能力是一项长期的杂活:IP 信誉、DNS 对齐和黑名单监控永远不会真正结束。对于一个关键业务地址,哪怕只在别人的垃圾箱里待上一天都会让您付出代价,那么用托管服务商才是务实的选择。当您把掌控看得比便利更重,并且真的会去打理它时,就自建。

我怎么知道出站 25 端口是不是被封锁了?

在服务器上运行 nc -vz -w 5 gmail-smtp-in.l.google.com 25。出现“succeeded!”就说明它是开放的;停顿之后出现 timed out 就说明您的服务商封锁了它。这是自建服务器能收到邮件却永远发不出去的最常见的单一原因,而唯一的解决办法就是让您的服务商开放该端口 —— 没有任何本地设置能改变它。

为什么我的邮件仍然落进 Gmail 的垃圾箱?

几乎总是认证链出了问题。在 Gmail 里用“Show original”,查找 spf=passdkim=passdmarc=passdkim=fail 指向一条缺失或被弄乱的 dkim._domainkey TXT 记录;PTR 不匹配,或者一个没有发送历史的新 IP,同样会造成伤害。先让 mail-tester.com 达到 10/10,然后慢慢给 IP 预热 —— 每天发几封,逐步增加 —— 而不是在第一天就猛发大量邮件。

我到底需要备份哪些东西?

运行 backup_and_restore.sh backup all,并把整套备份保存在服务器之外。它会捕获 vmail(邮箱)、crypt(解密它们的密钥)、MariaDB 数据库(域名、用户、别名、设置)、Redis、Rspamd 学习到的数据,以及 Postfix 队列。crypt 卷是人们容易忽视的那一个 —— 没有它,vmail 备份就是一堆读不出来的密文。至少在一台临时机器上测试恢复一次。

我能在一台 2 GB 的 VPS 上运行 mailcow 吗?

不太舒适。generate_config.sh 会在大约 2.5 GiB 以下提议禁用 ClamAV,而即便如此,Rspamd、ClamAV、Dovecot 和 MariaDB 仍会争抢内存,所以在任何真实负载下您都会触发 swap 和 OOM 杀进程。把 6 GiB 加 1 GiB swap 当作一个稳定的单用户安装的下限,一旦有超过两三个人依赖它,就立刻升到 8 GiB。

#mailcow#email#自托管#Docker#dns