背景

某内部环境中有两台 Debian 系服务器,分别承担虚拟化管理与备份服务。两台机器都不是实际的公网邮件服务器,也不直接对外投递邮件,而是作为本机系统邮件的提交端使用 Postfix。

整体结构如下:

内部主机 A / 内部主机 B
    ↓
本机 Postfix
    ↓
专用邮件服务器 mail.example.net:<RELAY_PORT>
    ↓
外部邮件系统

其中:

mail.example.net

代表真正负责对外收发和中继的邮件服务器。

两台内部主机上的 Postfix 只负责把本机系统邮件、告警邮件、root 邮件等提交给该邮件服务器。两台机器各自对应一个真实存在的邮件账户:

host-a@example.net
host-b@example.net

这两个账户在邮件系统中是独立账户,均可正常收发邮件。

原始状态

两台机器的对外发信功能已经长期正常运行,说明核心 SMTP 中继链路本身没有问题。

关键配置大致如下:

relayhost = [mail.example.net]:<RELAY_PORT>
smtp_tls_wrappermode = yes
smtp_tls_security_level = encrypt
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_sasl_security_options = noanonymous
smtp_sasl_tls_security_options = noanonymous
smtp_generic_maps = hash:/etc/postfix/generic
inet_interfaces = loopback-only

这些项目是邮件能否成功提交到外部邮件服务器的核心配置,因此整理过程中没有改动。

真正需要检查的是一些非核心但影响一致性和邮件头表现的项目:

myhostname
mydomain
generic 地址改写表
inet_protocols
smtputf8_enable

发现的问题

检查后发现两台机器的核心中继配置一致,但 Postfix 自身身份和地址改写规则并不完全一致。

主要差异包括:

1. myhostname 不一致
   主机 A 使用 mail.example.net
   主机 B 使用 localdomain 风格的默认主机名

2. mydomain 不一致
   主机 A 使用 example.net
   主机 B 使用 localdomain

3. generic 地址改写表仍保留旧的本地域名
   例如 host-a.old-local、host-b.old-local 之类旧名称

4. inet_protocols 不一致
   一台为 ipv4
   一台为 all

5. smtputf8_enable 不一致
   一台使用默认值
   一台显式关闭

同时,系统层面的主机名解析也进行了确认:

/etc/hostname
/etc/hosts
hostname
hostname -f
hostname -s
getent hosts

整理后,两台机器的系统层面主机名保持简单结构:

主机 A:
  /etc/hostname = host-a
  hostname -f   = host-a.lan

主机 B:
  /etc/hostname = host-b
  hostname -f   = host-b.lan

这里的 host-a.lanhost-b.lan 只作为内部局域网身份使用,不等同于公网管理入口,也不等同于实际邮件服务器。

generic 是什么

generic 指的是 Postfix 的出站地址改写表,对应配置项是:

smtp_generic_maps = hash:/etc/postfix/generic

它不负责连接 SMTP 服务器,不负责 TLS,不负责 SASL 登录,也不负责端口配置。

它的作用是:在邮件交给外部邮件服务器之前,把本机生成的发件人地址改写成真实存在的邮箱账户。

例如:

root
root@host-a.lan
root@mail.example.net

在主机 A 上统一改写为:

host-a@example.net

在主机 B 上统一改写为:

host-b@example.net

这样做的目的,是避免本机系统邮件以 root@localhostroot@host-a.lanroot@localdomain 这类内部地址对外出现。

整理原则

本次整理遵循几个原则:

1. 不改动 relayhost
2. 不改动 SMTP 端口
3. 不改动 TLS 配置
4. 不改动 SASL 认证配置
5. 不改动 /etc/postfix/sasl_passwd
6. 不改动 master.cf
7. 不把内部主机当成真正的公网邮件服务器
8. 只整理身份标识和地址改写层

换句话说,发信链路保持原样,只对 Postfix 的身份显示和出站发件人改写规则做一致化处理。

修改内容

1. 统一 myhostname 和 mydomain

两台机器统一为:

myhostname = mail.example.net
mydomain = example.net

这样做的目的,是让两台内部主机在 Postfix 层面统一使用邮件系统的主身份。

需要注意的是,这并不表示两台内部主机变成了邮件服务器。真正的邮件服务器仍然是:

mail.example.net:<RELAY_PORT>

内部主机仍然只是通过 relayhost 提交邮件。

2. 整理 generic 地址改写表

主机 A 的 /etc/postfix/generic 整理为类似结构:

root                         host-a@example.net
root@localhost               host-a@example.net
root@mail.example.net        host-a@example.net
@mail.example.net            host-a@example.net

root@host-a                  host-a@example.net
root@host-a.lan              host-a@example.net
@host-a.lan                  host-a@example.net

root@host-a.old-local        host-a@example.net
@host-a.old-local            host-a@example.net

主机 B 的 /etc/postfix/generic 整理为类似结构:

root                         host-b@example.net
root@localhost               host-b@example.net
root@mail.example.net        host-b@example.net
@mail.example.net            host-b@example.net

root@host-b                  host-b@example.net
root@host-b.lan              host-b@example.net
@host-b.lan                  host-b@example.net

root@host-b.old-local        host-b@example.net
@host-b.old-local            host-b@example.net

root@host-b.localdomain      host-b@example.net
@host-b.localdomain          host-b@example.net

整理后执行:

postmap /etc/postfix/generic

用于重新生成:

/etc/postfix/generic.db

3. 统一 inet_protocols

两台机器统一为:

inet_protocols = ipv4

由于两台机器均为:

inet_interfaces = loopback-only

因此这项主要是为了保持行为一致,避免一台监听 IPv4 loopback,另一台同时监听 IPv6 loopback。

4. 启用 smtputf8_enable

两台机器统一为:

smtputf8_enable = yes

当前使用的邮箱地址均为 ASCII 地址,因此启用该项不会改变现有发信行为。统一启用后,两台机器在 Postfix 行为上更加一致。

未修改的内容

本次没有修改以下关键配置:

relayhost = [mail.example.net]:<RELAY_PORT>
smtp_tls_wrappermode = yes
smtp_tls_security_level = encrypt
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_sasl_security_options = noanonymous
smtp_sasl_tls_security_options = noanonymous
inet_interfaces = loopback-only

也没有修改:

/etc/postfix/sasl_passwd
/etc/postfix/master.cf
真正邮件服务器配置
公网 DNS 配置
管理网页反向代理配置
证书配置

这部分是整个发信功能的核心,不应在身份整理过程中顺手改动。

验证方式

整理后检查 Postfix 关键配置:

postconf myhostname mydomain myorigin relayhost inet_interfaces inet_protocols smtputf8_enable smtp_generic_maps

期望看到:

myhostname = mail.example.net
mydomain = example.net
myorigin = $myhostname
relayhost = [mail.example.net]:<RELAY_PORT>
inet_interfaces = loopback-only
inet_protocols = ipv4
smtputf8_enable = yes
smtp_generic_maps = hash:/etc/postfix/generic

检查 generic 改写是否生效:

postmap -q root hash:/etc/postfix/generic
postmap -q root@mail.example.net hash:/etc/postfix/generic
postmap -q root@host-a.lan hash:/etc/postfix/generic

主机 A 应返回:

host-a@example.net

主机 B 应返回:

host-b@example.net

最后执行:

postfix check
postfix reload

确认配置可被 Postfix 正常加载。

关于警告信息

整理过程中可能出现类似提示:

Postfix is using backwards-compatible default settings

这通常与 compatibility_level 有关,不代表本次修改导致发信异常。

也可能出现 chroot 环境中的库文件版本提示,例如:

/var/spool/postfix/... and /lib/... differ

这类提示和本次 myhostnamegenericinet_protocolssmtputf8_enable 整理不是同一个问题。若邮件功能正常,可以单独记录,不应与本次整理混在一起处理。

最终结果

整理完成后,两台机器达到以下状态:

1. 系统层面主机名清晰
   host-a.lan
   host-b.lan

2. Postfix 层面邮件系统身份统一
   myhostname = mail.example.net
   mydomain   = example.net

3. 出站发件人地址按机器分别改写
   主机 A → host-a@example.net
   主机 B → host-b@example.net

4. SMTP 中继链路保持不变
   relayhost、TLS、SASL 均未改动

5. inet_protocols 统一为 ipv4

6. smtputf8_enable 统一启用

7. 两台机器对外发信均保持正常

这次整理的关键点不是“把内部主机变成邮件服务器”,而是在不影响已有中继发信链路的前提下,让两台内部服务器的 Postfix 身份、协议行为和发件人改写规则更加一致。

最重要的经验是:邮件系统整理时必须先分清层级。

relayhost / TLS / SASL
  决定能不能连接真正的邮件服务器

generic
  决定本机生成的发件人地址如何改写

/etc/hostname / /etc/hosts
  决定系统本机名称解析

myhostname / mydomain
  决定 Postfix 自身身份

真正邮件服务器
  负责最终对外收发邮件

只有明确这些层级,才能避免把一个已经稳定运行的邮件中继配置改坏。

Leave a Reply

Your email address will not be published. Required fields are marked *