Mailcow 企业邮件服务器部署-保姆级教程

📧 保姆级教程:Mailcow 企业邮件服务器部署(Debian 12)

mailcow

依据: Mailcow 官方安装文档 + 官方 DNS 文档
官方安装指南: https://docs.mailcow.email/getstarted/install/
官方 DNS 指南: https://docs.mailcow.email/getstarted/prerequisite-dns/
官方 Relayhost 文档: https://docs.mailcow.email/post_install/relayhosts/
操作系统: Debian 12(适用 Ubuntu 24.04+)
预估耗时: 60~120 分钟(含排错)


📋 完整操作流程一览

1. 开放服务器防火墙端口       ↓
2. 配置域名 DNS(部署前必须做)
       ↓
3. SSH 连接服务器
       ↓
4. 安装系统依赖包
       ↓
5. 安装 Docker + Docker Compose
       ↓
6. 下载 Mailcow 代码
       ↓
7. 执行 generate_config.sh(交互式问答)
       ↓
8. 修改 mailcow.conf(时区 + SAN + 跳过健康检查)
       ↓
9. docker compose pull
       ↓
10. docker compose up -d(启动)
       ↓
11. 用 admin / moohoo 登录面板
       ↓
12. 配置 SPF / DKIM / DMARC / PTR
       ↓
13. 修复容器 DNS(Unbound 转发器)
       ↓
14. 修复 HTTPS 证书(certbot 方案)
       ↓
15. 排查出站 25 端口是否被封
       ↓
16. 配置 SMTP 中继(解决出站 25 端口封锁)
       ↓
17. 验证收发邮件
       ↓
18. 日常运维

1. 准备工作

1.1 你需要什么

项目说明
服务器Debian 12 或 Ubuntu 24.04,最低 2C2G(推荐 2C4G)
域名例如 example.com
SSH 客户端Windows 用 Putty,Mac/Linux 用终端
端口在云安全组放行 22、25、80、443、587、465、993、995、4190

1.2 端口说明

端口用途说明
22TCPSSH
25TCPSMTP 发信
80TCPHTTP 证书验证
443TCPHTTPS 管理面板
587TCPSMTP 发信(客户端)
465TCPSMTP SSL 发信
993TCPIMAP SSL 收信
995TCPPOP3 SSL 收信
4190TCPSieve 过滤管理

⚠️ TCP vs UDP 特别说明:SMTP(发邮件)、IMAP(收邮件)、HTTP/HTTPS(网页)全部使用的是 TCP 协议,不是 UDP。云安全组规则中放行端口时,确保协议选择的是 TCP。如果误开 UDP 25 端口,对邮件收发没有任何作用。UDP 主要用于 DNS 查询(53端口)、NTP 时间同步(123端口)等场景,邮件收发不涉及 UDP。

1.3 本教程使用的示例

  • 域名: example.com(请替换为你的真实域名)

  • 服务器 IP: <你的服务器IP>(请替换为你的真实 IP)


2. 域名 DNS 配置(部署前必须完成)

2.1 官方最小 DNS 配置

Mailcow 官方文档明确要求,部署前必须配置以下 DNS 记录。

这些记录决定了:

  • A 记录:别人能找到你的邮件服务器在哪

  • CNAME:邮件客户端能自动发现服务器配置

  • MX 记录:别人知道发往 @example.com 的邮件该送到哪台服务器

2.2 登录域名管理后台

域名注册商入口
阿里云(万网)控制台 → 域名 → 点击域名 → 解析设置
腾讯云(DNSPod)控制台 → 域名管理 → 解析
CloudflareDashboard → 选择域名 → DNS → Records
其他找「DNS 管理」或「域名解析」

2.3 添加 A 记录

记录类型:A主机记录:mail
记录值:<你的服务器IP>
TTL:默认

生成的完整域名:mail.example.com → 你的服务器IP

2.4 添加 CNAME 记录(两条)

记录① autodiscover:

记录类型:CNAME主机记录:autodiscover
记录值:mail.example.com.
TTL:默认

记录② autoconfig:

记录类型:CNAME主机记录:autoconfig
记录值:mail.example.com.
TTL:默认

⚠️ 记录值末尾必须加 **.**(点号),即 mail.example.com.

2.5 添加 MX 记录

记录类型:MX
主机记录:@
记录值:mail.example.com.
优先级:10
TTL:默认

⚠️ 同样末尾加 .mail.example.com.

2.6 验证记录生效

nslookup mail.example.com
nslookup -type=mx example.com
nslookup autodiscover.example.com

3. SSH 连接服务器

3.1 Windows(Putty)

  1. 下载打开 Putty

  2. Host Name = <服务器IP>Port = 22

  3. 点击 OpenAccept

  4. 输入 root + 密码

3.2 Mac / Linux

ssh -p 22 root@<服务器IP>

4. 安装系统依赖包

apt update && apt upgrade -y
apt install -y git openssl curl gawk coreutils grep jq certbot python3-certbot-nginx

这 8 个包是 Mailcow 官方列出的必需依赖。gawk(awk)、coreutils(包含 sha1sum、cut 等)、jq(JSON 解析,2025-09 版本起新增)。**certbotcertbot-nginx** 用于后续自动申请 HTTPS 证书(第14章)。


5. 安装 Docker + Docker Compose

5.1 安装 Docker 引擎(官方一键脚本)

curl -sSL https://get.docker.com/ | CHANNEL=stable sh

⚠️ 官方特别强调:必须使用最新版 Docker Engine,不要用系统自带源中的旧版。 如果 curl 超时,先配代理或重试几次。

5.2 启动 Docker 并设置为开机自启

systemctl enable --now docker

5.3 验证 Docker

docker --version

应显示 Docker version 27.x.x, build xxxxx 或更高版本。

5.4 安装 Docker Compose 插件

apt update && apt install -y docker-compose-plugin

如果使用 get.docker.com 一键脚本,它已经自动安装了 compose 插件,但这步做了也不会错。

5.5 验证 Docker Compose

docker compose version

应显示 Docker Compose version v2.x.x

⚠️ 注意:Docker Compose 插件版的命令是 **docker compose**(中间有空格,没有连字符)。


6. 下载 Mailcow

suumask 0022cd /opt
git clone https://github.com/mailcow/mailcow-dockerizedcd mailcow-dockerized

su 确保以 root 身份操作。umask 0022 确保文件权限为 755(目录)/ 644(文件),容器才能正常读取。


7. 生成配置文件

7.1 启动配置生成脚本

./generate_config.sh

执行后屏幕上会显示提示文字,让你输入邮件服务器的主机名。

你会看到:

Press enter to confirm the detected value '[value]' where applicable or enter a custom value.
Mail server hostname (FQDN) - this is not your mail domain, but your mail servers hostname:

📌 这里要填什么?

这里的 FQDN(完全限定域名) 指的是你的邮件服务器完整域名,格式为 子域名.主域名.顶级域

  • 你买的域名是 example.com

  • FQDN 应填写 mail.example.com

  • 这个域名后续用于:访问管理面板、SMTP/IMAP 服务、SSL 证书申请

不要example.com(缺少子域名),不要填完整 URL(如 https://...)。

7.2 填写邮件域名(第1个问题)

→ 输入 mail.example.com 然后回车

必须包含子域名(如 mail.),不能只输入 example.com。FQDN 示例:mx.gmail.com 中的 mx 就是子域名。


Q2 — 时区

Timezone [Etc/UTC]:

→ 国内服务器输入 Asia/Shanghai 然后回车

直接回车则保留 Etc/UTC,后续可以通过修改 mailcow.conf 来更改。


Q3 — 选择分支

Which branch of mailcow do you want to use?
Available Branches:
- master branch (stable updates) | default, recommended [1]
- nightly branch (unstable updates, testing) | not-production ready [2]
- legacy branch (supported until February 2026) | deprecated, security updates only [3]
Choose the Branch with it's number [1/2/3]:

→ 输入 1 然后回车(master 稳定版,生产环境首选)


Q4 — ClamAV(仅当内存 ≤ 2.5GB 时出现)

如果你的服务器内存大于 2.5GB,不会出现此问题,自动跳过。


7.3 问答结束后脚本自动完成的内容

IPv6 not detected on host – disabling IPv6 support.Skipping Docker IPv6 configuration because host does not support IPv6.IPv6 configuration complete: ENABLE_IPV6=falseGenerating snake-oil certificate...
...+.........++++++++++++
-----Copying snake-oil certificate...
root@你的主机名:/opt/mailcow-dockerized#

回到命令行提示符,表示配置文件已生成完毕。

7.4 检查生成的配置文件

cat mailcow.conf

确认包含以下内容:

MAILCOW_HOSTNAME=mail.example.comHTTP_PORT=80HTTPS_PORT=443TZ=Asia/Shanghai

8. 修改配置文件

这一步不是官方强制的,但根据实际部署经验,有五个地方建议调整,否则会遇到各种问题。

8.1 修改时区(如果上一步选择了 Etc/UTC)

为什么改: Etc/UTC 比北京时间晚8小时,不改也能用,但邮件时间、日志时间都会差8小时。改成 Asia/Shanghai 后所有时间都显示为北京时间,排查问题方便。

sed -i 's|TZ=.*|TZ=Asia/Shanghai|' mailcow.conf

如果上一步生成配置时已经选 Asia/Shanghai,这步可以跳过。

8.2 添加附加域名(方便邮件客户端自动发现)

为什么加: DNS 里配了 autodiscoverautoconfig 两条 CNAME。如果不在 SSL 证书里包含这两个域名,Outlook 或 Thunderbird 自动配置时会弹出安全警告说证书不匹配。加上这行后 Let's Encrypt 申请的证书会同时覆盖这三个域名。

echo 'ADDITIONAL_SAN=autodiscover.example.com,autoconfig.example.com' >> mailcow.conf

8.3 跳过 Unbound 健康检查(防止部署失败)

为什么加: Mailcow 默认自带一个 DNS 解析器叫 Unbound,MySQL 容器启动时依赖它。但在国内云服务器上,Unbound 经常 DNS 查询超时而无法通过健康检查,导致 MySQL 初始化失败。

MySQL 没初始化,admin 账号就写不进数据库。结果就是你打开面板输入 admin / moohoo 时登录失败。

加了这行: Mailcow 跳过 Unbound 的健康检查,直接用系统 DNS。MySQL 正常初始化,admin 账号正常创建,不会再登录失败。

echo 'SKIP_UNBOUND_HEALTHCHECK=y' >> mailcow.conf

8.4 跳过 IP 地址检查(防止 acme 容器启动失败)

为什么加: acme-mailcow 容器启动时会自动检测你的服务器公网 IP,并尝试将域名解析结果与公网 IP 匹配。在某些云服务商环境下(特别是 NAT 或非标准网络),检测会超时或失败,导致 Let's Encrypt 证书申请跳过。

加了这行: 跳过公网 IP 检测,按时尝试申请证书。如果证书申请失败,可以后续用手动方式修复(见第14章)。

echo 'SKIP_IP_CHECK=y' >> mailcow.conf

8.5 确认修改结果

grep -E "MAILCOW_HOSTNAME|HTTP_PORT|HTTPS_PORT|TZ|ADDITIONAL_SAN|SKIP_UNBOUND|SKIP_IP" mailcow.conf

应显示:

MAILCOW_HOSTNAME=mail.example.comHTTP_PORT=80HTTPS_PORT=443TZ=Asia/ShanghaiADDITIONAL_SAN=autodiscover.example.com,autoconfig.example.comSKIP_UNBOUND_HEALTHCHECK=ySKIP_IP_CHECK=y

9. 启动 Mailcow

9.1 拉取 Docker 镜像

docker compose pull

这步做什么: 从 Docker Hub 下载 Mailcow 运行所需要的全部容器镜像,包括 nginx(网页服务器)、mysql(数据库)、postfix(发件)、dovecot(收件)、rspamd(反垃圾)、sogo(Webmail)等十几个组件。每台服务器只需下载一次。

⏱ 根据网速需要 5~20 分钟。

国内服务器镜像加速: 如果 pull 卡住不动,是因为 Docker Hub 在国内被限制。配置镜像加速器后重新 pull:

mkdir -p /etc/dockercat > /etc/docker/daemon.json << 'EOF'{ "registry-mirrors": [  "https://docker.xuanyuan.xyz",  "https://docker.1ms.run",  "https://docker.m.daocloud.io"]}
EOF
systemctl restart docker
docker compose pull

9.2 启动所有容器

docker compose up -d

这步做什么: 根据 docker-compose.yml 文件的定义,按依赖顺序依次创建并启动所有容器。-d 参数表示后台运行,关闭 SSH 后服务也不会停止。

9.3 检查容器状态

等待 30~60 秒让容器完成初始化,然后:

docker compose ps

怎么看结果: STATUS 列显示 UpUp (healthy) 表示正常启动。如果某个容器显示 unhealthyExited,说明有问题需要排查。


10. 登录管理面板

10.1 浏览器访问

https://mail.example.com

首次访问会提示「您的连接不是私密连接」,因为用的是自签名证书。点击高级 → 继续前往即可。正式证书申请(见第14章)之后就不会再提示了。

10.2 官方默认登录凭据

字段
用户名admin
密码moohoo

官方文档原文:*"You can now access https://${MAILCOW_HOSTNAME}/admin using the default credentials admin and the password moohoo."*

10.3 登录成功后立即修改密码

面板右侧菜单 → 系统 → 配置 → 权限管理 → 编辑,然后修改密码保存。

10.4 😱 登录失败怎么办?

如果 admin / moohoo 提示错误:

# 重置 admin 密码为 moohoocd /opt/mailcow-dockerized
docker compose exec -T mysql-mailcow mysql -uroot -p$(grep -oP 'DBROOT=\K.*' mailcow.conf) -e \  "USE mailcow; REPLACE INTO admin (username, password, superadmin, active) VALUES ('admin', '\2y\10\$NLRS/FE07kqBWB5cGNjPOeKcxS9C8VTJPi8VQC8q5NQ.RWYqnPwK6', 1, 1);"

执行后密码重置为 moohoo,刷新页面重新登录。


11. 配置邮箱域名

Mailcow 只是个邮件服务器框架,它不知道你要管理哪个域名、要给谁开邮箱。你得告诉它:你要添加 example.com 这个域名,然后在这个域名下创建具体的邮箱账号。

11.1 添加域名

为什么做这步: 告诉 Mailcow 你要管理 example.com 这个域名的邮件。只有添加了域名,才能在这个域名下创建邮箱。

面板右侧菜单 → E-Mail→ 配置 → 域名添加域名

字段
域名example.com
描述可为空或填备注
最大允许的邮箱数10000

⚠️ 其他默认,并且点击「添加域名并重启」按钮(不是「添加域名」)。

11.2 创建邮箱

为什么做这步: 添加域名后,需要给具体的人开邮箱账号,这样他才能在 Webmail 或手机邮件客户端上收发邮件。一个域名可以创建几百个邮箱,每个邮箱对应一个员工。

面板右侧菜单 → 配置 → 邮件设置 → 邮箱添加邮箱

字段示例说明
用户名(邮箱地址左侧的部分)zhangsan邮箱前缀,@ 前面的部分
域名example.com选择刚添加的域名
全称张三显示名称,收件人看到的发件人姓名
密码设置强密码邮箱密码,以后登录要用
配额2048(2GB)邮箱容量,单位 MB,2048 = 2GB

创建后邮箱地址为 zhangsan@example.com

11.3 使用 Webmail

为什么是这个地址: SOGo 是 Mailcow 自带的 Webmail 网页邮箱程序,可以直接在浏览器里收发邮件,不用装任何软件。

https://mail.example.com/SOGo

zhangsan@example.com + 密码登录。和正常邮箱一样,可以收件、发件、管理文件夹。


12. 配置 SPF / DKIM / DMARC / PTR(DNS 补全)

第2章已经配置了 A、CNAME(autodiscover/autoconfig)、MX 四条记录。现在配置 SPF、DKIM、DMARC、PTR 这四项认证记录。

为什么这些记录决定了你的邮件能否送达:

当 Gmail、QQ邮箱、Outlook 等收到你发的邮件后,会自动进行以下三项检查:

检查项目的不配置的后果
SPF确认发件服务器是否被授权邮件直接进垃圾箱或被拒收
DKIM验证邮件数字签名是否被篡改邮件被标记为「可疑邮件」
DMARC告诉对方 SPF/DKIM 验证失败怎么处理收件方按默认策略处理,通常丢弃
PTRIP 反向验证是否匹配域名Gmail 等会降低信誉分

缺任何一个,邮件都可能无法送达。 这是企业邮件服务器的必配项。

12.1 SPF 记录

做什么用的: SPF 告诉全世界「只有 mail.example.com 这台服务器有权发送 example.com 的邮件」。防止别人伪造你的域名发诈骗邮件。

记录类型:TXT
主机记录:@
记录值:v=spf1 mx a -allTTL:10分钟(或默认)

-all 表示拒绝非授权服务器发信(严格模式)。如果担心误拦截,可以先改用 ~all(仅标记不拒绝),运行稳定后再改回 -all

12.2 DKIM 记录

做什么用的: DKIM 给每封发出的邮件盖上一个数字签名。收件方用你 DNS 里的公钥验证签名,确认邮件确实是你发的、没有被篡改。

从 Mailcow 面板获取 DKIM 值:

  1. 登录 https://mail.example.com

  2. 配置 → 反垃圾邮件 → DKIM → 选择域名 example.com

  3. 点击 添加生成密钥对

  4. 复制「DNS 记录」框里的完整 TXT 值(以 v=DKIM1; k=rsa; p= 开头)

或者命令行获取:

cat /opt/mailcow-dockerized/data/conf/rspamd/dkim/example.com.txt

添加到域名管理:

记录类型:TXT
主机记录:dkim._domainkey
记录值:v=DKIM1; k=rsa; p=...(粘贴面板复制的内容)
TTL:10分钟(或默认)

12.3 DMARC 记录

做什么用的: DMARC 告诉收件方:如果 SPF 或 DKIM 验证失败了,该怎么处理这封邮件——是放行、进垃圾箱、还是直接拒绝。同时还能收到验证失败的统计报告,方便排查谁在伪造你的域名。

记录类型:TXT
主机记录:_dmarc
记录值:v=DMARC1; p=reject; rua=mailto:mailauth-reports@example.com
TTL:10分钟(或默认)

官方推荐 p=reject(验证失败直接拒绝)。初期可以先设为 p=none(仅收集报告不处理),等确认 SPF/DKIM 配置正确后再逐步改为 p=quarantine(进垃圾箱)或 p=reject(拒绝)。

12.4 设置 PTR 反向解析

做什么用的: PTR 是反向 DNS 查询——根据你的服务器 IP 反查域名。Gmail、QQ、Outlook 等大邮箱会对比:邮件声称自己是 mail.example.com 发的,那这个 IP 的 PTR 必须是 mail.example.com。对不上就降低信誉分。

PTR 记录 不是在域名管理设置,而是需要在云服务器提供商处设置。

云厂商操作方式
阿里云工单 / ECS → 实例 → 更多 → 设置反向解析
腾讯云云服务器 → 实例 → 更多 → 设置反向DNS
华为云弹性IP → 更多 → 设置反向解析
AWSEC2 → 弹性IP → 操作 → 反向DNS
其他提工单给云厂商,「申请设置反向解析 PTR 为 mail.example.com」

PTR 内容:**mail.example.com**

12.5 验证 DNS 配置

# SPFdig TXT example.com +short# DKIMdig TXT dkim._domainkey.example.com +short# DMARCdig TXT _dmarc.example.com +short# PTRdig -x <你的服务器IP> +short

或使用在线工具:MXToolbox SuperTool


13. 修复容器内部 DNS 解析

13.1 问题现象

部署完成后你可能发现:

  • HTTPS 面板上证书始终是「不安全」

  • acme-mailcow 容器日志报错 No A or AAAA record found for hostname

  • 无法接收外部邮件

  • postfix 日志报错 bad command startup

根本原因: 容器内部的 DNS 解析链路断了。容器 DNS → Unbound 容器 → 外部 DNS 查询失败。因为 Mailcow 默认的 Unbound 没有配置上游转发器。

13.2 配置 Unbound 转发器

cd /opt/mailcow-dockerized

编辑 data/conf/unbound/unbound.conf,在文件末尾添加以下内容:

cat >> data/conf/unbound/unbound.conf << 'EOF'forward-zone:
  name: "."
  forward-addr: 8.8.8.8
  forward-addr: 1.1.1.1
EOF

💡 解释: 这告诉 Unbound —— 当它要查询外部域名时,向 8.8.8.8(Google DNS)和 1.1.1.1(Cloudflare DNS)转发请求。不配置这行,Unbound 去问根服务器,在部分云环境会超时。

13.3 重启 Unbound 并验证

docker compose restart unbound-mailcowsleep 3

验证容器 DNS 是否修复:

# 从宿主机测试 Unbound 解析dig A mail.example.com @172.22.1.254 +short# 从 acme 容器内部测试docker compose exec -T acme-mailcow dig A mail.example.com +short

如果两条命令都返回你的服务器 IP,说明 DNS 修复成功。


14. 修复 HTTPS 证书(certbot 方案)

14.1 问题说明

Mailcow 的 acme-mailcow 容器会在容器内部自动向 Let's Encrypt 申请证书,但它依赖容器 DNS。如果第13章 DNS 没有修复,acme 容器无法解析域名,证书申请会失败。

除了修复 DNS,你可能还需要手动申请证书并用 certbot 替代。方法如下:

14.2 在宿主机上用 certbot 申请证书

# 确保 nginx 容器正在监听 80 端口curl -I http://127.0.0.1# 用 certbot 申请证书(webroot 模式)certbot certonly --webroot \
  -w /opt/mailcow-dockerized/data/web/ \
  -d mail.example.com \
  -d autodiscover.example.com \
  -d autoconfig.example.com \
  --non-interactive --agree-tos -m admin@example.com

💡 解释:

  • certonly:仅获取证书,不修改 nginx 配置

  • --webroot:通过在网站目录下放置验证文件来证明域名所有权

  • -w:指定 webroot 路径为 Mailcow 的网页根目录

  • -d:指定要包含在证书里的域名,这里同时覆盖 mail/autodiscover/autoconfig

14.3 将证书安装到 Mailcow

# 复制到 Mailcow SSL 目录cp /etc/letsencrypt/live/mail.example.com/fullchain.pem /opt/mailcow-dockerized/data/assets/ssl/cert.pemcp /etc/letsencrypt/live/mail.example.com/privkey.pem  /opt/mailcow-dockerized/data/assets/ssl/key.pemchmod 644 /opt/mailcow-dockerized/data/assets/ssl/cert.pemchmod 600 /opt/mailcow-dockerized/data/assets/ssl/key.pem

14.4 将证书复制到 acme 容器

# 找到 acme 容器 IDCID=$(docker ps -q -f name=acme)# 先用 readlink -f 解析真实路径(Let's Encrypt 证书是符号链接)REAL_CERT=$(readlink -f /etc/letsencrypt/live/mail.example.com/fullchain.pem)
REAL_KEY=$(readlink -f /etc/letsencrypt/live/mail.example.com/privkey.pem)# 用 -L 参数复制到容器docker cp -L "REAL_CERT"CID:/var/lib/acme/cert.pem
docker cp -L "REAL_KEY"CID:/var/lib/acme/key.pem
docker exec $CID chmod 600 /var/lib/acme/key.pem

14.5 将证书复制到 postfix 容器

postfix 也需要使用正确的证书来加密 SMTP 连接。用 docker cp 方式:

PFX=$(docker ps -q -f name=postfix)
docker cp -L "REAL_CERT"PFX:/etc/ssl/mail/cert.pem
docker cp -L "REAL_KEY"PFX:/etc/ssl/mail/key.pem

14.6 重启相关容器

cd /opt/mailcow-dockerized
docker compose restart nginx-mailcow
docker compose restart postfix-mailcow
docker compose restart dovecot-mailcow

14.7 验证证书

openssl x509 -in /opt/mailcow-dockerized/data/assets/ssl/cert.pem -noout -issuer -subject -dates

输出示例:

issuer=C = US, O = Let's Encrypt, CN = YE2
subject=CN = mail.example.com
notBefore=Jun 27 05:41:17 2026 GMT
notAfter=Sep 25 05:41:16 2026 GMT

14.8 设置自动续期

certbot 会自动创建定时任务续期证书。每次续期后需要重新安装到容器:

# 创建续期钩子脚本cat > /etc/letsencrypt/renewal-hooks/post/mailcow-copy.sh << 'EOF'#!/bin/bashCERT_DIR=/etc/letsencrypt/live/mail.example.com
ACL_DIR=/opt/mailcow-dockerized/data/assets/sslcp -L "CERT_DIR/fullchain.pem" "ACL_DIR/cert.pem"cp -L "CERT_DIR/privkey.pem" "ACL_DIR/key.pem"chmod 644 "$ACL_DIR/cert.pem"chmod 600 "$ACL_DIR/key.pem"CID=$(docker ps -q -f name=acme)
REAL_CERT=(readlink -f "CERT_DIR/fullchain.pem")
REAL_KEY=(readlink -f "CERT_DIR/privkey.pem")
docker cp -L "REAL_CERT"CID:/var/lib/acme/cert.pem
docker cp -L "REAL_KEY"CID:/var/lib/acme/key.pem

PFX=$(docker ps -q -f name=postfix)
docker cp -L "REAL_CERT"PFX:/etc/ssl/mail/cert.pem
docker cp -L "REAL_KEY"PFX:/etc/ssl/mail/key.pemcd /opt/mailcow-dockerized
docker compose restart nginx-mailcow postfix-mailcow dovecot-mailcow
EOFchmod +x /etc/letsencrypt/renewal-hooks/post/mailcow-copy.sh

15. 排查出站 25 端口是否被封

15.1 为什么发不出去邮件?

如果配置了 SPF/DKIM/DMARC/PTR,邮箱能收到信但发不出信,最常见的两个原因:

  1. 云服务商封锁了出站 TCP 25 端口(最常见)

  2. 服务器本身有防火墙拦截了出站 25 端口(系统防火墙如 iptables/ufw)

SMTP 邮件协议只使用 TCP 协议。云安全组中开 UDP 25 端口对发邮件没有任何帮助,因为邮件传输根本不使用 UDP。

15.2 判断出站 25 端口是否被封

方法一:用 nc 测试连接到外部邮件服务器

# 测试到 QQ 邮箱的 25 端口是否通echo -e "EHLO test\r\n" | nc -w 5 mx3.qq.com 25
  • 如果返回 220 ... XMail Esmtp QQ Mail Server... = 25 端口正常

  • 如果卡住不动并超时 = 出站 25 端口被封

# 也可以测试 Gmail 和 SendGridecho -e "EHLO test\r\n" | nc -w 5 gmail-smtp-in.l.google.com 25echo -e "EHLO test\r\n" | nc -w 5 smtp.sendgrid.net 25

方法二:用 Bash 内置的 /dev/tcp 进行精确测试

# 连接到 QQ MX 的 25 端口timeout 10 bash -c 'exec 3<>/dev/tcp/mx3.qq.com/25 2>&1 && echo "CONNECTED" && read -t 5 banner <&3 && echo "Banner: $banner" || echo "FAILED"'

方法三:查看 postfix 日志

docker compose logs --tail=20 postfix-mailcow 2>&1 | grep -E "to=|deferred|sent|relay="

如果看到:

connect to mx3.qq.com[203.205.219.57]:25: Connection timed out

说明出站 25 端口被封。

方法四:检查邮件队列

docker compose exec -T postfix-mailcow mailq
  • 队列为空 = 邮件已成功发出

  • 队列有邮件 = 发信被阻塞

方法五:检查系统防火墙

iptables -L -n 2>/dev/null | grep 25
ufw status 2>/dev/null

15.3 判断出站 25 端口是「被封」还是「路由不通」

执行以下测试有助于判断阻塞发生在哪里:

# 1. 测试 587 端口(通常不受封禁)timeout 5 bash -c 'exec 3<>/dev/tcp/smtp.qq.com/587 2>&1 && echo "587 OK" || echo "587 FAILED"'# 2. 测试 25 端口timeout 10 bash -c 'exec 3<>/dev/tcp/mx3.qq.com/25 2>&1 && echo "25 OK" || echo "25 FAILED"'# 3. 用 IP 直接测(绕过 DNS)timeout 10 bash -c 'exec 3<>/dev/tcp/203.205.219.57/25 2>&1 && echo "DIRECT IP 25 OK" || echo "DIRECT IP 25 FAILED"'

结果判断:

587 端口25 端口IP 直连 25结论
✅ OK✅ OK25 端口畅通,查其他原因
✅ OK❌ 超时❌ 超时云厂商封锁了出站 25 端口,需配 SMTP 中继
✅ OK❌ 超时✅ OKDNS 解析问题,查容器 DNS(第13章)
❌ 失败❌ 失败❌ 失败系统防火墙拦截,检查 iptables/ufw

587 端口通但 25 端口不通,基本可以确定是云服务商策略性封锁了出站 25,而不是服务器自身防火墙的问题。

15.4 解决方案对比

方案优点缺点
SMTP 中继(推荐)立即生效,无需联系云厂商依赖第三方服务,免费额度有限
申请解封 25 端口独立收发邮件,不依赖第三方云厂商审核严格,通常需要企业资质
更换云服务器选择不封 25 的厂商迁移成本高

推荐方案:先配 SMTP 中继。用发件量小的免费额度先跑起来,后续再考虑解封 25 端口。

16. 配置 SMTP 中继(解决出站发信)

16.1 选择 SMTP 中继服务

服务商免费额度注册要求推荐场景
SendGrid100封/天邮箱注册个人/测试
Mailgun100封/天需要手机号个人/测试
Brevo (Sendinblue)300封/天邮箱注册个人/测试
腾讯云邮件推送按量计费实名认证国内用户

16.2 以 SendGrid 为例配置中继

步骤 1:注册 SendGrid

  1. 打开 https://sendgrid.com/ 注册账号

  2. 邮箱验证后登录

  3. 左侧导航 → Settings → API KeysCreate API Key

步骤 2:获取 SMTP 凭据

  1. API Key 类型选择 Full Access

  2. 复制生成的 API Key(只显示一次,保存好)

  3. SendGrid 的 SMTP 配置:

    • 服务器:smtp.sendgrid.net

    • 端口:587(TLS)

    • 用户名:apikey(固定)

    • 密码:你刚才复制的 API Key

16.3 在 Mailcow 面板配置中继

方法 A:UI 面板配置(推荐)

面板右侧菜单 → 配置 → 邮件设置 → 中继添加

字段
主机名和端口smtp.sendgrid.net:587
用户名apikey(SendGrid 固定)
密码粘贴 SendGrid API Key
域名留空(全局生效)

如果「中继」菜单项未找到,请确认 Mailcow 版本为 2025-09 或更高。

方法 B:直接写入 MySQL(如果面板没有中继菜单)

cd /opt/mailcow-dockerized# 插入中继配置docker compose exec -T mysql-mailcow mysql -uroot -p$(grep -oP 'DBROOT=\K.*' mailcow.conf) mailcow -e "
  INSERT INTO relayhosts (hostname, username, password, active)
  VALUES ('smtp.sendgrid.net:587', 'apikey', '你的SendGrid_API_Key', 1);
"

然后重启 postfix:

docker compose restart postfix-mailcow

16.4 验证发信

  1. 登录 Webmail https://mail.example.com/SOGo

  2. zhangsan@example.com 登录

  3. 写一封邮件发给自己的 QQ 邮箱测试

  4. 检查邮件队列是否为空:

    docker compose exec -T postfix-mailcow mailq
  5. 查看发送日志:

    docker compose logs --tail=10 postfix-mailcow 2>&1 | grep -E "relay=|status=sent|to="

    如果看到 relay=smtp.sendgrid.netstatus=sent,说明配置成功。

16.5 解封 25 端口(可选)

如果后续希望不经过中继直接发信,需要向云厂商申请解封 25 端口:

申请模板:
【云厂商 25端口解封申请】- 服务器IP:[填写]- 域名:example.com- 用途:自建企业邮件服务器收发公司邮件- 已配置:SPF/DKIM/DMARC/PTR 均已配置完毕- 承诺:遵守《互联网电子邮件服务管理办法》,不发送垃圾邮件

⚠️ 注意:即使解封了 25 端口,SPF/DKIM/DMARC/PTR 也必须配置,否则邮件仍然无法送达。


17. 验证收发邮件

17.1 收件测试

  1. 从 QQ邮箱 或 Gmail 发送一封邮件到 zhangsan@example.com

  2. 登录 Webmail https://mail.example.com/SOGo

  3. 检查收件箱是否收到

如果收不到,检查 postfix 日志:

docker compose logs --tail=30 postfix-mailcow 2>&1 | grep -E "from=|to=|Client host"

17.2 发件测试

  1. 在 Webmail 写一封邮件发送到你的 QQ 邮箱

  2. 检查 QQ邮箱的收件箱、垃圾箱

如果收不到,检查队列和日志:

# 查看队列docker compose exec -T postfix-mailcow mailq# 查看日志docker compose logs --tail=20 postfix-mailcow 2>&1 | grep -E "to=|deferred|sent|status="

17.3 常见发件问题排查

问题日志特征解决方法
出站 25 被封Connection timed out配 SMTP 中继(第15章)
SPF 未配置Gmail退回说未授权添加 SPF 记录(第12章)
DKIM 未配置邮件变「未签名」添加 DKIM 记录(第12章)
IP 在黑名单blocked using zen.spamhaus.org检查 IP 信誉,联系云厂商
PTR 未设置Gmail 标记为垃圾设置 PTR(第12章)

18. 日常运维

18.1 服务管理

# 查看所有容器状态docker compose ps# 停止所有服务docker compose stop# 启动所有服务docker compose start# 重启某个服务docker compose restart nginx-mailcow# 查看日志docker compose logs -f
docker compose logs -f postfix-mailcow
docker compose logs -f acme-mailcow

18.2 查看邮件队列

docker compose exec -T postfix-mailcow mailq

如果队列不为空,可以强制重试:

docker compose exec -T postfix-mailcow postqueue -f

18.3 备份

为什么做这步: 邮件数据(收件箱、发件箱、附件)保存在 Docker 数据卷里。如果服务器出问题,没有备份就等于所有邮件丢了。建议每周执行一次。

cd /opt/mailcow-dockerized
docker compose down                             # 停止所有容器,保证数据一致性tar -czf /root/mailcow-backup-$(date +%Y%m%d).tar.gz /opt/mailcow-dockerized  # 压缩整个目录docker compose up -d                            # 重新启动服务

备份文件保存在 /root/ 下,文件名包含日期,如 mailcow-backup-20250627.tar.gz

注意: 生产环境建议用 backup.sh 脚本,它会正确排空并备份数据库。

18.4 升级

为什么做这步: Mailcow 持续更新安全补丁和新功能。每 1~3 个月升级一次可以修复已知漏洞。

cd /opt/mailcow-dockerized
git pull
docker compose pull    # 下载最新版镜像docker compose up -d   # 用新镜像重建容器./update.sh            # 运行 Mailcow 自带更新脚本(更新数据库结构等)

18.5 重置管理员密码

什么时候用: 忘了 admin 密码,或者 moohoo 登录失败时用。

cd /opt/mailcow-dockerized
docker compose exec -T mysql-mailcow mysql -uroot -p$(grep -oP 'DBROOT=\K.*' mailcow.conf) -e \  "USE mailcow; REPLACE INTO admin (username, password, superadmin, active) VALUES ('admin', '\2y\10\$NLRS/FE07kqBWB5cGNjPOeKcxS9C8VTJPi8VQC8q5NQ.RWYqnPwK6', 1, 1);"

执行后密码恢复为 moohoo

18.6 完全重来

什么时候用: 部署过程中搞砸了,想从零开始重新部署,或者练习多次安装时用。

cd /opt/mailcow-dockerized
docker compose down -v   # 停止并删除所有容器和数据卷(注意:会丢失所有邮件数据!)rm -f mailcow.conf       # 删除配置文件# 从第7步(./generate_config.sh)重新开始

⚠️ -v 参数会删除所有邮件数据,生产环境千万不能用。仅限测试练习使用。


⚠️ 常见问题排错指南

Q1:登录时提示密码错误

原因: MySQL 容器初始化异常(通常是 Unbound 健康检查失败导致的)。

解决: 先尝试重置密码(第18.5节)。如果无效,检查 mailcow.conf 是否包含 SKIP_UNBOUND_HEALTHCHECK=y,没有则加上后重新部署。

Q2:HTTPS 证书不安全

原因: acme-mailcow 容器内部 DNS 解析失败。

排查:

docker compose logs acme-mailcow

如果看到 No A or AAAA record found,说明 DNS 问题。按第13章修复 DNS,然后按第14章手动安装 certbot 证书。

Q3:收不到外部邮件

排查步骤:

  1. 确认 DNS MX 记录生效:nslookup -type=mx example.com

  2. 确认端口 25 入站通:telnet mail.example.com 25(从另一台电脑测试)

  3. 检查 postfix 日志:docker compose logs postfix-mailcow | grep -E "Client host|from="

Q4:发件对方收不到

排查步骤:

  1. 检查邮件队列:docker compose exec -T postfix-mailcow mailq

  2. 如果队列为空但对方没收到,可能是进了垃圾箱,检查 SPF/DKIM/DMARC(第12章)

  3. 如果队列有邮件且状态为 deferred

    • Connection timed out → 出站 25 被封 → 配 SMTP 中继(第15章)

    • blocked using zen.spamhaus.org → IP 在黑名单 → 联系云厂商

    • Helo command rejected → 检查 myhostname 配置

Q5:acme 容器一直重启

原因: 配置文件有语法错误或 DNS 不通。

排查:

docker compose logs acme-mailcow

如果看到 unbound.conf 相关错误,检查第13章的 Unbound 配置是否正确。

Q6:容器内部 DNS 不通

排查:

# 测试 Unbounddig A mail.example.com @172.22.1.254 +short# 测试 acme 容器docker compose exec -T acme-mailcow dig A mail.example.com +short

如果宿主机正常但容器不通,执行:

docker compose restart unbound-mailcowsleep 3
docker compose exec -T acme-mailcow dig A mail.example.com +short

✅ 部署完成检查清单

基础配置

  • <input type="checkbox" disabled=""/>A 记录 mail.example.com → 服务器 IP

  • <input type="checkbox" disabled=""/>CNAME autodiscovermail.example.com.

  • <input type="checkbox" disabled=""/>CNAME autoconfigmail.example.com.

  • <input type="checkbox" disabled=""/>MX @mail.example.com.

部署流程

  • <input type="checkbox" disabled=""/>SSH 连上服务器,apt install 完成

  • <input type="checkbox" disabled=""/>Docker 已安装并运行

  • <input type="checkbox" disabled=""/>Mailcow 代码已克隆

  • <input type="checkbox" disabled=""/>generate_config.sh 执行完毕

  • <input type="checkbox" disabled=""/>docker compose pull 完成

  • <input type="checkbox" disabled=""/>docker compose up -d 所有容器 Up

  • <input type="checkbox" disabled=""/>能打开 https://mail.example.com

  • <input type="checkbox" disabled=""/>用 admin / moohoo 登录成功(已改密码)

  • <input type="checkbox" disabled=""/>添加了域名 example.com

  • <input type="checkbox" disabled=""/>创建了邮箱用户 zhangsan@example.com

DNS 认证(必配,否则发信失败)

  • <input type="checkbox" disabled=""/>SPF 记录 v=spf1 mx a -all

  • <input type="checkbox" disabled=""/>DKIM 记录 dkim._domainkey

  • <input type="checkbox" disabled=""/>DMARC 记录 _dmarc

  • <input type="checkbox" disabled=""/>PTR 反向解析 mail.example.com

修复配置

  • <input type="checkbox" disabled=""/>Container DNS 已修复(Unbound 转发器)

  • <input type="checkbox" disabled=""/>HTTPS 证书已安装(Let's Encrypt / 自动)

  • <input type="checkbox" disabled=""/>SMTP 中继已配置(如出站 25 被封)

  • <input type="checkbox" disabled=""/>出站 25 端口已解封(可选)

功能验证

  • <input type="checkbox" disabled=""/>能收到外部邮件(如从 QQ 发送)

  • <input type="checkbox" disabled=""/>能发送邮件到外部邮箱(如 QQ 收到)

  • <input type="checkbox" disabled=""/>邮件不进垃圾箱(SPF/DKIM/DMARC/PTR 均生效)


参考链接:

祝你部署顺利!🐝🐮