拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Mailcow邮件服务器部署指南:从DNS配置到容器化避坑实战

简介Mailcow 是基于 Docker 容器化技术的开源邮件服务器解决方案集成 SMTP、IMAP、POP3 与 Webmail 服务内置反垃圾、反病毒、DKIM/DMARC/SPF 多重安全校验适合需要自建邮件系统的系统管理员、运维工程师及中小企业 IT 人员快速搭建安全可控的邮件环境。压缩包含 2000 个文件整体约 10.98MB以 1515 个 PHP 源码文件为主体辅以 YML 容器编排配置、Shell 部署初始化脚本、JSON/XML 配置、Markdown 说明文档及 CSS/JS 前端资源覆盖容器编排、业务逻辑到界面展示的完整链路。资源中提供 Docker Compose 管理方式的配置示例、安全策略文件、第三方依赖与密钥证书类文件可对照理解 Mailcow 各组件的协作方式也可作为二次开发或私有化部署的参考基线。目前已有 200 人学习下载适合邮件系统原理梳理和容器化运维实践。1. 自己搭邮件服务器难点从来不是装软件很多团队在评估 Mailcow 之前已经在虚拟机里手工拼过一轮 Postfix、Dovecot 和 Webmail。装完才发现真正的门槛是三个一是公网 25 端口被限、PTR 记录缺失导致发信被拒收二是 DKIM、DMARC、SPF 三条 DNS 记录没配齐进了对方垃圾箱还没察觉三是日志散落在十几个进程里出了问题只能靠猜。Mailcow 是一套把 Postfix、Dovecot、Rspamd、SOGo、ClamAV 等组件用 Docker Compose 串起来、自带统一 Web 管理后台的开源邮件服务器解决方案它把这些零散的坑提前替你踩平了一大半。适合手里有一个域名和至少 4G 内存的小团队、多域名管理的运维以及想从老旧的商业邮件系统迁移出来、又不想把所有事情都外包出去的从业者。这篇文章我用一线部署的视角把从 DNS 准备、容器启动到反向代理避坑的完整过程拆给你看。2. Mailcow 的架构逻辑为什么用容器把邮件系统拆成十几个服务初次打开docker compose ps看到十几个容器很多人第一反应是“太重了”。但邮件系统本身就是一个长链路外部通过 SMTP 25 端口进来Postfix 做收信路由用户用 IMAP 从 Dovecot 拉信收发过程中每一封邮件都要经过 Rspamd 打分附件要过 ClamAV 查毒Webmail 界面 SOGo 又要读写同一个账号体系。把这些放在一台虚拟机里手工配置任何一个组件升级都会牵连另外几个这才是真正重的部分。2.1 Postfix、Dovecot、Rspamd、SOGo 各管哪一段单独看 Mailcow 的镜像列表会觉得很乱但按“信在哪、谁在管”这条线梳理立刻就清楚了Postfix负责 SMTP 收信和发信邮件进入系统的第一道门。外部服务器把信投递到你的 MX 记录指定的主机实际落地的进程就是 Postfix。Dovecot负责 IMAP/POP3 服务邮件进入用户邮箱后由它提供读取接口同时处理用户密码验证和邮箱目录锁定。Rspamd负责每一封邮件的垃圾评分和 DKIM 签名。Mailcow 里 DKIM 私钥的签名动作也由 Rspamd 承担而不是 Postfix。SOGoWebmail 客户端提供网页收信、日历、联系人同步支持 ActiveSync。ClamAV附件病毒扫描默认开启在内存不足时是第一个应该被关闭的组件。MySQL保存账号、域名、别名等元数据邮件正文则存在 Dovecot 的卷目录里。Redis缓存 Rspamd 的统计数据、SOGo 会话等热数据。这套分工里我最看重的设计是Rspamd 在 Postfix 的 smtpd 阶段就直接参与决策垃圾评分发生在邮件落盘之前。高分的邮件可以直接被拒收、丢进隔离区或加标题而不是先进入 Dovecot 再由客户端侧规则二次处理。实践里Rspamd 的学习数据随时间累积对特定业务域的误判率会越来越低这是手工配置 SpamAssassin 很难达到的体验。从运维角度看容器化最大的好处是“单点可替换”。比如你觉得 ClamAV 在低配机器上太吃内存可以只停掉clamd-mailcow容器邮件系统其余部分完全不受影响。这比在传统 Linux 上systemctl stop clamd之后再处理一堆依赖关系要干净得多。当然容器化也有代价数据卷和容器生命周期一旦搞混升级时会出现数据不丢但配置丢失的怪问题这一点放在后面的避坑章节专门讲。2.2 按需裁剪内存只有 4G 时关掉哪些容器Mailcow 官方建议的最小内存是 4GB实际操作中 4G 跑全套会比较紧张。我一般会在docker-compose.override.yml里做减法这个文件不会影响后续升级时主 compose 文件的改动services: clamd-mailcow: image: mailcow/clamd:1.10 profiles: [disabled] olefy-mailcow: profiles: [disabled] soapy-mailcow: profiles: [disabled]用profiles禁用服务后docker compose up -d默认不会启动它们。这里逻辑上要注意Mailcow 的主 compose 文件是由generate_config.sh生成的升级时不会被整体覆盖但docker compose在读取时会同时加载 override 文件里的合并配置。所以你把服务加进profiles之后还需要执行docker compose up -d --remove-orphans让已经运行的旧容器停下来。为什么先关 ClamAV 而不是关 SOGo因为 SOGo 承担了邮箱用户唯一能看到的“界面”关了它团队里非技术成员就回到了纯客户端收信的方式。而 ClamAV 只影响附件病毒扫描4G 内存的机器上关掉它省出的 1.5G 能让 MySQL 和 Rspamd 从容很多。如果安全要求不允许关那么最低限度也要给系统加 swap否则碰到大附件并发扫描时直接触发 OOMDocker 守护进程会连坐杀掉其它容器。2.3 数据落在哪里容器卷与备份边界Mailcow 的数据主要在三个地方MySQL 容器里的mailcow数据库、Dovecot 容器挂载的邮件存储卷、Redis 里的缓存数据。前两个决定你的邮件系统能不能恢复Redis 丢了只是重新学习反垃圾模型问题不大。邮件存储卷默认以vmail-mailcow这种命名卷的形式存在用docker volume ls能看到。很多人升级时执行了惯用的docker compose down docker compose up -d这不会删除命名卷数据是安全的但如果有人手贱执行了docker compose down -v那么整个邮件存储和数据库会一起消失没有任何后悔药。我的习惯是部署完成后立刻在mailcow.conf的备份设置里打开自动备份同时每周对一个特定目录做快照。备份不是等到要迁移时才想起的是在第一天就设好的。3. 从零部署 MailcowDNS 先配好再跑容器部署 Mailcow 本身只需要三个大步骤准备 DNS、生成配置、启动容器。但我见过太多人把步骤搞反——先装好套件再回头发现域名解析没生效、PTR 没办法立刻配上结果容器反复重启还以为是软件 bug。所以我把 DNS 的准备放在最前面这一部分花费的时间比容器启动时间还长。3.1 部署前的域名与 DNS 准备MX、A、PTR 一个都不能少假设你的域名是example.com邮件服务器主机名规划为mail.example.com。以下是必须在域名 DNS 管理后台配好的记录我用表格列出照着填就行记录类型主机名内容作用Amail服务器公网 IP让mail.example.com可以解析到服务器MXmail.example.com优先级 10告诉别的邮件服务器投递入口在这PTR服务器 IPmail.example.com反向解析很多大邮箱不发 PTR 直接拒信TXTvspf1 mx a ip4:服务器IP ~all声明哪些服务器有资格代发该域名TXTmailDKIM 公钥后面导出的长串邮件签名校验防伪造TXT_dmarcvDMARC1; pnone; ruamailto:adminexample.com接收方反馈未通过校验的邮件来源这里最容易漏的是 PTR。它能控制权不在域名服务商而在给你分配公网 IP 的机房或云厂商。绝大多数云控制台里都有“反向解析”或“PTR 记录”入口你得提交工单或直接在控制台里配置把 IP 反解到mail.example.com。PTR、A、MX 三者不一致是对方服务器判定垃圾邮件的最直接依据这个不解决后面 SPF、DKIM 配得再完美你的信也大概率进回收站。3.2 最小安装命令下载套件并生成 mailcow.confDNS 配置开始生效后可以用dig MX example.com确认就可以在服务器上拉取部署套件。官方仓库是 GitHub 上的mailcow/mailcow-dockerized国内服务器拉取如果速度慢可以把github.com换成镜像地址但我不建议改 —— 邮件系统需要长期跟随更新还是直接用官方源一次性拉干净apt update apt install -y git curl git clone https://github.com/mailcow/mailcow-dockerized /opt/mailcow-dockerized cd /opt/mailcow-dockerized ./generate_config.shgenerate_config.sh是交互式脚本会问你几个问题MAILCOW_HOSTNAME要填mail.example.com这个值必须和 MX 记录的 A 记录解析到同一台主机HTTP_PORT和HTTPS_PORT默认分别是 8080 和 8443如果你不想让邮件管理后台占用标准 80/443保持默认即可后面用 Nginx 反代过去。脚本运行完会生成mailcow.conf所有后续自定义参数都在这个文件里改。3.3 启动与健康检查看日志确认各容器就绪配置生成完毕后首次启动要把镜像全部拉下来。这一步耗时取决于网络环境建议在 screen 或 tmux 里跑docker compose pull docker compose up -d docker compose psdocker compose ps输出的 STATE 列在启动初期会显示starting这是正常的。等到所有容器变成healthy大概需要 1 到 3 分钟。如果卡在某个容器上不要反复up -d先看对应容器日志。比如 MySQL 起不来常见原因是/opt/mailcow-dockerized所在磁盘空间不足或者之前装过别的 MySQL 留下了端口冲突docker compose logs mysql-mailcow --tail 200日志里出现ready for connections才说明数据库初始化完成。接着检查nginx-mailcow容器是否监听在 8080 端口然后打开http://服务器IP:8080看到登录页就说明 Web 管理界面已经起来了。默认管理员账号是admin初始密码在生成配置时终端会打印登录后第一件事就是改掉它。3.4 把 SPF、DKIM、DMARC 写进 DNS发信前置条件容器全部启动后进入管理后台的Configuration - DKIM页面选择example.com会看到一把 DKIM 公钥。这个值是一个很长的 TXT 记录主机名通常是dkim._domainkey.example.com。复制完整值的时候要小心有些 DNS 服务商对单条 TXT 的长度有限制超过 255 字符会分成多段而 DKIM 记录恰好就长。关于分段导致的校验失败问题我在避坑章节会展开说。SPF 记录按前面表格里的写法直接加在域名根上。DMARC 可以先从pnone开始跑两周后看rua邮箱收到哪些来源没通过校验再逐步收紧为pquarantine或preject。这一步别跳DMARC 报告是了解自己域名是否被仿冒的唯一廉价入口。三条记录配置完成后用dig TXT dkim._domainkey.example.com和dig TXT example.com能正确解析就可以用任意外部邮箱给这个域名发一封测试邮件再回复一封来验证双向收发。4. Web 管理后台的日常域名、邮箱账号、别名与反垃圾策略Mailcow 的 Web 管理后台和普通邮件系统的最大区别是管理员不需要手工编辑 Postfix 的 virtual 文件也不需要 SSH 到服务器敲 useradd。域名、邮箱、别名、配额全部在 UI 里点出来后端落到 MySQL。但我建议运维至少要掌握两条终端命令因为一旦 UI 因为反代配置问题打不开、或者要做批量导入终端是唯一的逃生通道。4.1 域名与邮箱账号的创建流程登录管理后台后进入Mailboxes - Domains先添加域名example.com。这里有两个选项容易被忽略Active决定域名是否可用Relayhost选项用于把该域名的所有来信转发到另一个 SMTP 服务器。如果只是内部测试可以不填 Relayhost如果公司有历史邮件网关想先跑一段灰度可以把 Relayhost 填成旧的网关地址。域名创建完成后在同一个页面切到Mailboxes点Add mailbox创建邮箱。用户名格式是完整邮箱地址密码最短 8 位且需要包含大小写字母。配额默认给 1GB按团队实际使用量调整。创建完成后立刻用这个邮箱通过客户端登录一次确认 Dovecot 的 IMAP 服务和密码验证链路正常。批量创建账号时我一般会直接操作数据库而不是在 UI 里一个个点。Mailcow 用 API 也可以但内网环境直接进容器更直观docker compose exec mysql-mailcow mysql -umailcow -p mailcow进入 SQL 提示符后你可以查询mailbox表结构确认字段后批量插入。但这里要奉劝一句不要绕过后台直接写mailbox表来创建邮箱因为 Mailcow 的密码存储格式不是 md5 也不是 bcrypt 的纯哈希它绑定了每个域名的特殊密钥。批量插入后如果登录不上又查不出原因非常耽误时间。我把查询表结构当成排查手段创建账号还是用 UI。4.2 别名与邮件列表让团队协作更顺手的两个配置别名是邮件系统里使用频率最高的功能。比如公司有supportexample.com需要同时发给三个客服同事不需要创建三个邮箱只要在Aliases页面新增一个别名把目标设成三个逗号分隔的邮箱地址即可。别名本身不占配额也不会产生独立收件箱所有来信都会投递到目标邮箱回复时发件人显示为其中某个具体邮箱。邮件列表的差异在于每个成员是独立收件人可以单独退订或收到不同的过滤规则。Mailcow 的邮件列表也走别名机制但使用Lists页面管理成员地址作为独立条目维护。小团队用别名就够对外发布邮件列表才需要单独开列表账号。配置别名时容易遇到一个坑如果目标邮箱里包含外部域名的地址例如someoneothercompany.comMailcow 默认允许这样设置但发过来的外部邮件会被当成转发邮件处理经过 Postfix 的 relay 逻辑这可能和你的Relayhost配置互相影响。碰到投递延迟第一反应去docker compose logs postfix-mailcow看是否存在relay access denied而不是猜 DNS 问题。4.3 反垃圾与 Rspamd从黑名单到按需放宽阈值Rspamd 在 Mailcow 里默认已经打开且接入 Postfix 的过滤流程。管理后台里Configuration - Rspamd能实时看到每封邮件的评分明细包括命中哪些规则、各得多少分。这个页面是我日常运维看的最多的一个地方判断一封邮件是垃圾还是误判靠历史数据比靠直觉准。如果某个外部客户频繁给公司发信但因为对方 IP 没有反解或使用了动态 IP被 Rspamd 一路压着打不要急着全局降低阈值。Rspamd 的 UI 里有一个“自动白名单”机制你可以把这个来源域名加入本地白名单让它的来信跳过垃圾评分。注意白名单粒度加域名比加 IP 更合适因为对方 IP 变了自动白名单就失效而域名在邮件头的From字段里是稳定标识。邮件进入隔离区后普通用户和管理员在 Webmail 的Junk文件夹里能看到被丢弃的邮件。这里有个运营习惯每周让人看一眼隔离区里有没有真实业务邮件。Rspamd 的模型本质是一个统计分类器它需要人工纠偏来学习。只依赖默认规则时间长了误判率一定会上升。5. 邮件系统避坑清单从端口封禁到容器半夜重启跑通一套 Mailcow 只意味着搭建完成离“稳定生产”还差一段距离。我在给客户做邮件系统的时候踩过很多次坑下面这五条是高频中的高频每一条都按现象、原因、解决三段来写直接对标你上线后可能遇到的第一批问题。5.1 发信被对方拒收25 端口被封和 PTR 缺失现象是邮件在队列里反复重试日志里出现connect to gmail-smtp-in.l.google.com:25: Connection timed out或550-Please turn on SMTP Authentication。前者的直接原因是服务器出站的 25 端口被运营商封锁这是用虚拟机邮件服务器最常见的现实问题后者则是对方服务器认为你的服务器不可信通常伴随着 PTR 记录不存在。解决分两步。第一步确认机房是否放行 25 端口临时用telnet 某大型邮件服务器 25测试如果连 25 端口都不通联系机房开通出站 SMTP 权限。如果机房明确不能开那就要换方案把 Mailcow 的投递方式改成通过第三方 SMTP 服务中继在mailcow.conf里启用relayhost配置。第二步补齐 PTR 记录这个前面说过必须在云控制台操作。我只见过 PTR 缺失导致被拒收的情况没见过补齐之后还被拒收的情况。5.2 DKIM 校验失败TXT 记录过长与符号转义现象是发出的邮件能够送达但外部邮箱显示“通过 DKIM 校验失败”或者邮件直接进垃圾箱。查 Rspamd 的日志能看到dkim: fail (bad signature)。原因多半不是 Mailcow 这边签名错了而是 DNS 里的 DKIM TXT 记录被截断或转义错误。DKIM 公钥是一整串约 200 多字符的文本某些 DNS 服务商在后台自动把它拆成两段每段分别加上双引号而解析方会把两段合并时在多出的引号处解析失败。解决方法是回到 DNS 后台确认 TXT 值是以vDKIM1; krsa; p开头的一段完整内容中间不能有空格和换行如果服务商强制分段请把两段合并为一条记录再提交。除了这些还有另一种容易忽略的场景你从管理后台复制公钥时末位多复制了一个空格肉眼根本看不出来。用dig TXT dkim._domainkey.example.com查看解析结果把输出和后台显示的值逐字符比对才是最快的排查方式。5.3 内存不足导致容器反复重启ClamAV 与 OOM现象是邮件服务运行两三天后某个容器变成Restartingdocker compose logs里能看到Killed或Out of memory。最常背锅的是 ClamAV它启动时加载病毒库要吃掉 1G 以上内存如果当时 MySQL 正好在跑大查询内核就会随机杀掉进程。这个坑在 4G 内存的虚拟机上尤其常见。解决方式有两个层次。短期方案是把 ClamAV 容器禁用按 2.2 节的方式加profiles或者给它设置内存上限让它 OOM 时只杀自己而不是连累 Redis。长期方案是给服务器增加内存或配置 swap我一般会在/etc/fstab里加一个 4G 的 swapfile虽然性能不如物理内存但足以避免邮件系统半夜因瞬时内存波动而整个瘫痪。5.4 反向代理后面登录异常X-Forwarded-* 没配现象是 Mailcow 跑在 Nginx 反代后面外部用 HTTPS 访问正常但登录后页面不断跳回登录页或者在 SOGo 里点击邮件链接时 URL 用的是 HTTP 和奇怪端口。原因是反代没有正确传递X-Forwarded-ProtoMailcow 的判断机制拿到的还是后端 HTTP 协议的http://于是认为当前会话不安全拒绝写入 cookie。解决方式是在你的 Nginx 反代配置里显式增加两条 headerX-Forwarded-Proto $scheme和X-Forwarded-Host $host。Mailcow 的mailcow.conf里也有一项可选的HTTP(S)强制 HTTPS 跳转开关如果反代已经承担了 TLS 终结建议把容器自身的 HTTPS 跳转关掉避免出现“反代到容器 8080容器又 302 回 8443”的死循环。这个坑排查起来最费时间因为它涉及容器内外两层协议判断我建议反代上线前先直接访问一次容器原生端口确认源站正常再对比反代访问就能快速定位问题出在反代还是应用。5.5 备份与还原的坑MySQL 一致性现象是服务器出现故障后从备份恢复时邮件账号能登录但邮件列表缺失或某些邮箱密码集体失效。原因大多是备份时只备份了 docker 卷文件没有保证 MySQL 的一致性。Docker 卷的复制操作不是数据库的在线备份MySQL 在写入过程中文件被复制走得到的就是损坏或半截的数据。正确做法是对数据库单独做逻辑备份。可以写一个 cron 任务每天凌晨用mysqldump导出整个mailcow库再把 dump 文件和邮件存储目录一起同步到异地。恢复流程也建议每季度演练一次在一个干净的虚拟机上把 Mailcow 跑起来导入 dump再挂载邮件卷确认能登录并看到历史邮件。邮件系统丢了账号可以重建但丢了几年的业务往来邮件对任何公司来说都是不可接受的损失。6. 进阶调优把 Rspamd 的评分阈值调到符合你的业务场景Mailcow 默认的 Rspamd 策略对个人用户比较友好但对业务邮件频繁、且经常收到陌生客户来信的公司来说默认阈值会误杀不少首封咨询邮件。Rspamd 默认的grow_factor和评分规则可以在 UI 的 Rspamd 设置页里看到但我更推荐直接进入容器查看完整配置因为 UI 暴露的只是冰山一角docker compose exec rspamd rspamadm configdump | grep -A 20 settings这段命令会把 Rspamd 当前生效的所有配置以 dump 形式打印出来settings区块下的symbol列表就是每个规则对应的加分项。你可以看到某个规则加了 15 分而你的阈值设在 15 分意味着命中一条就会判定垃圾。调整方式是在docker-compose.override.yml里给rspamd-mailcow服务挂载一份自定义配置文件而不是直接在容器里改因为容器重建会把改动全部抹掉。针对“首封陌生客户邮件更容易被误杀”的问题我通常的处理是把GREYLIST这个符号的加分降低同时把USER_IN_SHORTLIST这类基于收件人通讯录的规则加分提高。这样对完全陌生的邮件保持警惕但对已经有过往来的地址会立刻放宽。调整后至少观察一周每天看一眼 Rspamd UI 里的评分分布如果垃圾邮件进隔离区的比例没有明显上升就可以把改动固化下来。最后还有一个容易被忽略的细节投递日志。docker compose logs postfix-mailcow里每一行成功投递都以statussent (250 2.0.0 OK)结尾。我养成的习惯是写一个简单的 cron每天扫一遍日志里的statusbounced统计退信数量超过阈值就报警。这个习惯帮我提前发现过两次因 DNS 记录被服务商悄悄改动而导致的整域退信。邮件服务器上了生产日常维护拼的不是花哨操作是把这些细节一点点盯住。希望今天的这些部署思路和踩坑记录能帮你少走一段弯路。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门