Postfix、VPN 与 smarthost:队列不是防火墙
队列负责持有邮件,防火墙负责决定路径。让中继保持稳定,在网络层强制只经 VPN 投递,并把延迟当作调度问题。
让邮件留在队列里等 VPN 起来,是个调度问题;常见的做法是停掉 Postfix、换掉 main.cf 再重新启动。这确实能用,但形状不对,因为它把两个要求混在一起:什么时候该尝试投递,以及 哪条网络路径可以承载它。第二个是防火墙的决定,任何 Postfix 指令都做不了。把两者分开之后,每一个都有小而可验证的答案。
保持中继目标稳定,在网络边界阻断直接投递,让队列负责等待。
被合并成一个要求的两个要求
中继告诉 Postfix 把邮件交给谁;relayhost = [smtp.corp.example]:587 指定主机和端口,方括号则抑制该下一跳基于 MX 的选择。路径告诉内核数据包可以去哪里。没有路径限制的正确 relayhost,在路由表恰好允许时仍会让 Postfix 直接连上互联网;而 relayhost 弄错的正确防火墙会把邮件送到错误的地方。两者互不蕴含。
传输映射、按发件人的覆盖和备用中继都值得同样的读法。稳定的 relayhost 并不能证明每条已配置的投递路径都在用它,而绕过中继的备用路径,只是伪装得更好的同一种故障。
defer_transports 实际做了什么
defer_transports 会扣住指定传输的邮件,在队列运行触及它们之前不会做任何投递尝试。这是调度控制,队列 flush 会覆盖它;这是设计,不是缺陷。请使用 master.cf 里真实的传输名,因为自定义传输或另起名字的中继传输,并不会仅因为它的程序也说 SMTP 就被覆盖。普通的重试是另一套机制:在 fail-closed 路径下,投递尝试只会失败并留在队列里,完全不涉及延迟指令。
永久性失败不会客气地等候。错误的收件人或被拒的凭据会弹回或卡成硬错误,而不是一直等到隧道出现,所以一个塞满延迟邮件的队列并不自动就是安全队列。
在网络层强制路径
限制应当放在本地出口边界,或者放在隔离的网络命名空间里。它必须覆盖中继的真实地址、传输和端口,启用时也包括 IPv6,因为一条阻断 TCP 25 的规则对 587 或 465 上的中继毫无作用。把回环和本地提交路径分开,让队列本身继续工作。当隧道路由消失,或以另一个接口名重新出现时,策略应当保持关闭。
接口 up 事件并不能证明中继可以通过它到达。所以一个先检查链路再 flush 的钩子只是方便,不是保证:链路可能在检查和投递尝试之间变化。
用一封无害邮件证明
在 VPN 断开时发一封无害邮件,然后查看 postqueue -p 里的延迟原因和队列 ID。恢复预期路径并请求一次受控的队列运行,观察邮件是从中继出去而不是去了别处。然后再次在途中切断路径,确认邮件回到队列。在决定中断可以持续多久之前,先看 maximal_queue_lifetime,因为值为零意味着只尝试一次,而不是无限保留。
监控队列年龄和磁盘占用。如果没人注意到队列从周五起一直在增长,等待 VPN 的策略就会变成邮件中断。
把中继、传输、防火墙规则和生命周期记在一起。这四项才描述整套安排;任何单独一项都只是猜测。