
夜里 2:40,香港湾仔机房内,我盯着 tail -f /var/log/maillog,看着一封发往 Gmail 的业务邮件在队列里反复退信:
550-5.7.26 This message does not pass authentication checks (SPF and DKIM).
客户第二天上午 9 点要发 30 万条运营邮件,这台香港服务器是他们新的外发节点。留给我“洗白”IP 和把认证三件套(SPF/DKIM/DMARC)拉满的时间,不多了。
这篇文章,我把那一夜到天亮的全部实操过程、配置文件、踩坑与解决记录下来。无论你是第一次在 CentOS 7 配邮件,还是需要在香港机房做对海外(Gmail/Outlook/雅虎)“不误判”的高阶优化,都能按图索骥一遍跑通。
1. 现场环境与“硬件/网络参数”
| 参数项 | 数值/说明 |
|---|---|
| 机房 | 香港(HKG),国际出口直连 GIA |
| 服务器 | Supermicro 单路,Intel Xeon E-2288G,32GB RAM,NVMe 2×1TB(RAID1) |
| OS | CentOS Linux release 7.9 (Core) |
| MTA | Postfix 2.10.x(CentOS 7 系统仓库版本) |
| DKIM | OpenDKIM 2.10.x(EPEL) |
| DMARC | OpenDMARC 1.3.x(EPEL) |
| 客户端提交 | 465 (smtps) / 587 (submission) |
| 外发域 | example.com(示例域名,替换为你的) |
| 外发主机名 | mail.example.com(A 记录、rDNS、TLS 证书一致) |
| 公网 IP | 203.0.113.10(示例) |
| 端口策略 | 出口 25 开放(机房白名单放通),防火墙放行 25/465/587 |
| 证书 | Let’s Encrypt(/etc/letsencrypt/live/mail.example.com/) |
要点:
- rDNS(PTR) 必须和外发主机名一致(形如 203.0.113.10 -> mail.example.com)。这一点是海外大厂判断“像不像真 MTA”的首要信号。
- HELO/EHLO 名称必须是 FQDN(mail.example.com),且能正反向解析。
- TLS 证书 使用正规 CA(Let’s Encrypt 即可),别用自签。
- 新 IP 发量 请先暖 IP(Warming),下文有策略。
2. 基础系统准备
2.1 安装必需组件(含 EPEL)
yum install -y epel-release
yum install -y postfix opendkim opendkim-tools opendmarc \
certbot git vim policycoreutils-python \
cyrus-sasl cyrus-sasl-plain cyrus-sasl-md5 \
mailx swaks
2.2 防火墙 & SELinux
# 放行 smtp/smtps/submission
firewall-cmd --permanent --add-service=smtp
firewall-cmd --permanent --add-service=smtps
firewall-cmd --permanent --add-port=587/tcp
firewall-cmd --reload
# SELinux(建议保留 Enforcing,避免“全关”)
# 采用 inet 套接字连接 milter,可减少本地 socket 的 SELinux 摸索
2.3 系统用户与目录(OpenDKIM)
# 确保 opendkim 用户存在
id opendkim || useradd -r -s /sbin/nologin opendkim
mkdir -p /etc/opendkim/keys/example.com
chown -R opendkim:opendkim /etc/opendkim
chmod go-rwx /etc/opendkim/keys -R
3. Postfix 基础配置(/etc/postfix/main.cf)
目标:正确的主机名、域设置、TLS、提交端口与中继策略,确保海外接收方看到“像样”的 MTA。
myhostname = mail.example.com
mydomain = example.com
myorigin = $mydomain
inet_interfaces = all
inet_protocols = ipv4
mydestination = $myhostname, localhost.$mydomain, localhost
mynetworks = 127.0.0.0/8
relayhost =
# 正确的 HELO/EHLO
smtp_helo_name = $myhostname
# TLS 证书(Let’s Encrypt)
smtpd_tls_cert_file = /etc/letsencrypt/live/mail.example.com/fullchain.pem
smtpd_tls_key_file = /etc/letsencrypt/live/mail.example.com/privkey.pem
smtpd_use_tls = yes
smtpd_tls_security_level = may
smtp_tls_security_level = may
smtpd_tls_loglevel = 1
smtp_tls_loglevel = 1
smtpd_tls_received_header = yes
# SASL(用于客户端提交)
smtpd_sasl_auth_enable = yes
smtpd_sasl_local_domain = $mydomain
smtpd_sasl_security_options = noanonymous
broken_sasl_auth_clients = yes
smtpd_recipient_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
reject_unauth_destination
# 队列与并发(香港到海外,适当温和)
default_process_limit = 200
default_destination_concurrency_limit = 20
initial_destination_concurrency = 5
minimal_backoff_time = 300s
maximal_backoff_time = 3600s
maximal_queue_lifetime = 2d
# DKIM/DMARC milter 挂载(注意端口与顺序)
milter_default_action = accept
milter_protocol = 6
smtpd_milters = inet:127.0.0.1:8891, inet:127.0.0.1:8893
non_smtpd_milters = $smtpd_milters
# 可选:postscreen(提高对方对你的评分,减少僵尸 IP 干扰)
postscreen_dnsbl_action = enforce
postscreen_greet_action = enforce
3.1 master.cf 开启 587/465
submission inet n - n - - smtpd
-o syslog_name=postfix/submission
-o smtpd_tls_security_level=encrypt
-o smtpd_sasl_auth_enable=yes
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
-o milter_macro_daemon_name=ORIGINATING
smtps inet n - n - - smtpd
-o syslog_name=postfix/smtps
-o smtpd_tls_wrappermode=yes
-o smtpd_sasl_auth_enable=yes
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
-o milter_macro_daemon_name=ORIGINATING
4. DKIM 配置(OpenDKIM)
4.1 /etc/opendkim.conf
Syslog yes
UMask 002
Mode sv
Canonicalization relaxed/simple
KeyTable /etc/opendkim/KeyTable
SigningTable /etc/opendkim/SigningTable
ExternalIgnoreList /etc/opendkim/TrustedHosts
InternalHosts /etc/opendkim/TrustedHosts
Socket inet:8891@127.0.0.1
OversignHeaders From
Statistics /var/run/opendkim/stats.dat
UserID opendkim:opendkim
4.2 生成密钥与表文件
cd /etc/opendkim/keys/example.com
opendkim-genkey -b 2048 -h rsa-sha256 -r -s default -d example.com
chown opendkim:opendkim default.private
chmod 600 default.private
# KeyTable
cat >/etc/opendkim/KeyTable <<'EOF'
default._domainkey.example.com example.com:default:/etc/opendkim/keys/example.com/default.private
EOF
# SigningTable
cat >/etc/opendkim/SigningTable <<'EOF'
*@example.com default._domainkey.example.com
EOF
# TrustedHosts
cat >/etc/opendkim/TrustedHosts <<'EOF'
127.0.0.1
localhost
mail.example.com
EOF
4.3 DNS 发布 DKIM 公钥
把 default.txt 中的内容发布到 DNS,通常是 TXT 记录:
| 主机名(Host) | 记录类型 | 值(Value) |
|---|---|---|
default._domainkey.example.com |
TXT | v=DKIM1; k=rsa; p=MIIBIjANBgkq... |
小技巧:有的 DNS 托管商不接受过长 TXT,一键粘贴会失败。用自动换行或分段 TXT(支持自动合并)即可;切记 去掉双引号中的换行或按平台要求分段。
4.4 启动
systemctl enable opendkim
systemctl restart opendkim
systemctl enable postfix
systemctl restart postfix
# 验证 DKIM 公钥可见
opendkim-testkey -d example.com -s default -vvv
5. DMARC(OpenDMARC)落地
5.1 /etc/opendmarc.conf
AuthservID mail.example.com
TrustedAuthservIDs mail.example.com
Socket inet:8893@127.0.0.1
Syslog true
FailureReports true
SyslogFacility mail
Umask 007
UserID opendmarc:opendmarc
IgnoreAuthenticatedClients true
systemctl enable opendmarc
systemctl restart opendmarc
5.2 DNS 发布 DMARC
先用 观察模式(p=none),收集报表,确认通过率再逐步收紧。
| 记录名 | 类型 | 值 |
|---|---|---|
_dmarc.example.com |
TXT | v=DMARC1; p=none; rua=mailto:dmarc-agg@example.com; ruf=mailto:dmarc-forensic@example.com; fo=1; adkim=s; aspf=s |
两周后若通过率稳定,可改为 p=quarantine,再到 p=reject。adkim/aspf 设为 s(strict)利于一致性,但要确保 From 域 与 Return-Path(SPF) / DKIM d= 对齐。
6. SPF 配置
| 记录名 | 类型 | 值 |
|---|---|---|
example.com |
TXT | v=spf1 ip4:203.0.113.10 include:_spf.your-saas.com -all |
如果还有第三方代发(如营销平台),加 include:。
末尾 -all(硬拒)比 ~all 更严格;刚开始可以 ~all,稳定后上 -all。
7. rDNS / HELO / TLS 全链路自检
7.1 反向解析(机房/ISP 侧设置)
将 PTR 指向 mail.example.com。
验证:
dig -x 203.0.113.10 +short
# 应返回 mail.example.com.
host 203.0.113.10
7.2 HELO 与 A/AAAA 一致性
dig A mail.example.com +short
dig AAAA mail.example.com +short # 若你没有 IPv6,禁用 inet_protocols=ipv6
7.3 TLS
openssl s_client -starttls smtp -connect mail.example.com:25 -servername mail.example.com -brief
- 证书链必须完整(fullchain.pem)。
- CN/SAN 包含 mail.example.com。
8. 端到端发送测试
8.1 用 swaks 测试外发
swaks --server mail.example.com --port 587 --auth LOGIN \
--auth-user no-reply@example.com --auth-password 'YourPass' \
--from no-reply@example.com --to youremail@gmail.com \
--tls --data "Subject: DKIM Test\n\nHello DKIM."
8.2 检查日志
tail -f /var/log/maillog
你应看到 DKIM 签名、DMARC 评估通过的记录;Gmail 端查看“显示原始邮件(Show original)”中有 DKIM=PASS, SPF=PASS, DMARC=PASS。
9. 高阶优化:让海外更“信任”
9.1 Postfix 队列/重投策略(跨境链路友好)
# main.cf 补充或调整
smtp_connection_cache_on_demand = yes
smtp_connection_reuse_time_limit = 300s
smtp_helo_timeout = 60s
smtp_tls_note_starttls_offer = yes
smtp_dns_support_level = dnssec
- 复用连接减少握手开销。
- DNSSEC 支持对少数目标有加分(非必要)。
9.2 MTA-STS 与 TLSRPT(可选但推荐)
MTA-STS TXT:_mta-sts.example.com → v=STSv1; id=2024090101
托管 https://mta-sts.example.com/.well-known/mta-sts.txt,内容:
version: STSv1
mode: enforce
mx: mail.example.com
max_age: 604800
TLSRPT TXT:_smtp._tls.example.com → v=TLSRPTv1; rua=mailto:tlsrpt@example.com
9.3 IP 暖量(Warming Plan)
天数 每小时上限 每日上限 备注
1–3 200 1,000 以 Gmail/Outlook/雅虎为散布对象
4–7 500 3,000 控制硬退信 < 0.3%
8–14 1,000 8,000 逐步放开,保持互动指标
15+ 视信誉 视信誉 根据反馈曲线加速或刹车
关键:收件人互动(打开/点击/回复)决定长期评分。退信率和投诉率(spam complaint)是红线。
9.4 From/Return-Path 对齐策略
业务系统发信统一使用 From: something@example.com,Return-Path 使用相同域(通过 Postfix sender_dependent_relayhost_maps 或 SaaS 对齐)。
确保 DKIM d=example.com,SPF 的 MAIL FROM 也在 example.com。
10. 多域签名(一个 OpenDKIM 签多域)
假设还要为 example.org 发信:
mkdir -p /etc/opendkim/keys/example.org
opendkim-genkey -b 2048 -h rsa-sha256 -r -s default -d example.org
chown opendkim:opendkim /etc/opendkim/keys/example.org/default.private
chmod 600 /etc/opendkim/keys/example.org/default.private
# 追加 KeyTable
echo "default._domainkey.example.org example.org:default:/etc/opendkim/keys/example.org/default.private" >> /etc/opendkim/KeyTable
# 追加 SigningTable
echo "*@example.org default._domainkey.example.org" >> /etc/opendkim/SigningTable
# 追加 TrustedHosts(如有需要)
echo "mail.example.org" >> /etc/opendkim/TrustedHosts
systemctl restart opendkim postfix
11. 监测与数据化验证
11.1 关键日志模式(示例)
成功外发(含 DKIM 签名):
postfix/qmgr: <queueid>: from=<no-reply@example.com>, ...
opendkim[PID]: <queueid>: DKIM-Signature field added (s=default, d=example.com)
postfix/smtp: <queueid>: to=<user@gmail.com>, relay=gmail-smtp-in.l.google.com[...]:25, status=sent
11.2 观察一周的认证与投递数据(样例)
| 指标 | 第 1 天 | 第 3 天 | 第 7 天 |
|---|---|---|---|
| DKIM 通过率 | 98.7% | 99.4% | 99.6% |
| SPF 通过率 | 99.1% | 99.3% | 99.5% |
| DMARC 通过率 | 97.9% | 99.0% | 99.3% |
| Gmail 收件箱占比 | 86% | 92% | 96% |
| Outlook 收件箱占比 | 78% | 88% | 93% |
| 退信率 | 0.9% | 0.4% | 0.2% |
注:这些是我当时的“真机房”观测曲线的一个近似复刻,用以说明暖 IP + 认证一致性带来的改善趋势。
12. 常见坑位与我的临场解决
DKIM 私钥权限错误
现象:日志里 opendkim: can't open key file。
处理:chown opendkim:opendkim default.private && chmod 600;UserID opendkim:opendkim 要匹配。
DNS 生效延迟/被拆分
现象:opendkim-testkey 提示找不到或公钥不匹配。
处理:检查权威 DNS,确保 TXT 无多余换行与空格;等待 TTL(我有一次在某云 DNS 上等了 15 分钟)。
rDNS 未同步
现象:Gmail 退信 5.7.1,或 SpamAssassin 分高。
处理:开工单让香港机房把 PTR 改成 mail.example.com;验证 dig -x。
HELO 与证书/CN 不一致
现象:对方记录“mismatched TLS name”。
处理:把 smtp_helo_name 固化为 mail.example.com,证书同名。
端口 25 被出口策略拦截
现象:本地能投递到 587,但 25 外发失败。
处理:香港机房通常可放通,提供用途说明 + IP 白名单,申请解封。
SELinux 阻断本地 socket
现象:postfix 无法连接 /var/run/opendkim/opendkim.sock。
处理:改用 Socket inet:8891@127.0.0.1,避开域套接字布尔值调整。
客户端提交滥用
现象:密码泄露导致垃圾发信。
处理:强制强密码+2FA(如果外部网关支持),收敛 smtpd_client_restrictions,并开启每用户限速(可上 postsrsd + policyd/rsyslog 统计,自行限流)。
13. 安全与信誉维护的日常
- 账号策略:每个业务系统独立发信账号;开启 fail2ban 监控 SASL 失败。
- 灰度策略:新内容/新受众先灰度 5–10%,观察投诉与打开率。
- 退订与回收:退信地址及时清理,无互动地址 90 天内回收。
- DMARC 报表:按周汇总,通过率 < 98% 需复盘。
- 发件域分层:事务类(tx.example.com)与营销类(mkt.example.com)分域,互不拖累。
14. 完整“抄作业”清单(Checklist)
- myhostname = mail.example.com(与 A 记录、rDNS、证书一致)
- 25/465/587 放通;submission/smtps 已开启
- Let’s Encrypt 有效,链完整
- OpenDKIM:密钥、KeyTable/SigningTable/TrustedHosts 正确,端口 8891
- OpenDMARC:AuthservID/端口 8893 正确
- Postfix 挂载 smtpd_milters 顺序:DKIM → DMARC
- SPF/DMARC TXT 生效,opendkim-testkey 通过
- Gmail “Show original”:SPF/DKIM/DMARC 均 PASS
- 暖 IP 在轨,退信率 < 0.3%,投诉率 ≪ 0.1%
15. 天亮时的那封“再试一次”
凌晨 5:55,我把最后一条 DMARC=pass 的日志截给客户。6 点整,用 swaks 走了一封到 Outlook,UI 里稳稳地落在了收件箱。7 点 20,运营同学开始小流量 AB,8 点 15 反馈“打开率明显改善”。
走出机房,早晨的海风顺着告士打道拐进来。我知道,这台香港的外发节点,不再像“可疑的陌生人”,而是一个说得清来路、举止合规的“好公民”。
如果你也在香港/海外环境搭 Postfix + DKIM:按本文把认证一致性、rDNS/HELO/TLS、暖 IP做到位,再辅以日常“数据化体检”,你也能把误判率压到很低。剩下的,就交给内容质量与收件人互动了。
附:关键配置文件一览(可直接对照)
/etc/postfix/main.cf(节选,含 milter/TLS/并发)
myhostname = mail.example.com
mydomain = example.com
myorigin = $mydomain
inet_interfaces = all
inet_protocols = ipv4
mydestination = $myhostname, localhost.$mydomain, localhost
mynetworks = 127.0.0.0/8
smtp_helo_name = $myhostname
smtpd_tls_cert_file = /etc/letsencrypt/live/mail.example.com/fullchain.pem
smtpd_tls_key_file = /etc/letsencrypt/live/mail.example.com/privkey.pem
smtpd_use_tls = yes
smtpd_tls_security_level = may
smtp_tls_security_level = may
smtpd_sasl_auth_enable = yes
smtpd_sasl_security_options = noanonymous
smtpd_recipient_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
reject_unauth_destination
milter_default_action = accept
milter_protocol = 6
smtpd_milters = inet:127.0.0.1:8891, inet:127.0.0.1:8893
non_smtpd_milters = $smtpd_milters
default_process_limit = 200
default_destination_concurrency_limit = 20
initial_destination_concurrency = 5
minimal_backoff_time = 300s
maximal_backoff_time = 3600s
maximal_queue_lifetime = 2d
/etc/postfix/master.cf(开启 587/465)
submission inet n - n - - smtpd
-o syslog_name=postfix/submission
-o smtpd_tls_security_level=encrypt
-o smtpd_sasl_auth_enable=yes
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
-o milter_macro_daemon_name=ORIGINATING
smtps inet n - n - - smtpd
-o syslog_name=postfix/smtps
-o smtpd_tls_wrappermode=yes
-o smtpd_sasl_auth_enable=yes
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
-o milter_macro_daemon_name=ORIGINATING
/etc/opendkim.conf
Syslog yes
UMask 002
Mode sv
Canonicalization relaxed/simple
KeyTable /etc/opendkim/KeyTable
SigningTable /etc/opendkim/SigningTable
ExternalIgnoreList /etc/opendkim/TrustedHosts
InternalHosts /etc/opendkim/TrustedHosts
Socket inet:8891@127.0.0.1
OversignHeaders From
UserID opendkim:opendkim
/etc/opendmarc.conf
AuthservID mail.example.com
TrustedAuthservIDs mail.example.com
Socket inet:8893@127.0.0.1
Syslog true
IgnoreAuthenticatedClients true
UserID opendmarc:opendmarc
DNS 必备(示例)
- A:mail.example.com -> 203.0.113.10
- PTR(rDNS):203.0.113.10 -> mail.example.com(机房侧设置)
- TXT (SPF):example.com -> v=spf1 ip4:203.0.113.10 -all
- TXT (DKIM):default._domainkey.example.com -> v=DKIM1; k=rsa; p=...
- TXT (DMARC):_dmarc.example.com -> v=DMARC1; p=none; rua=mailto:...
如果你有问题就把日志和 DNS 贴给我,我可以帮你把关键的“信号”逐一对上。