Exim 4.95 常用 CLI 命令详解

Ubuntu 22.04 仓库版 Exim 4.95 命令行工具箱全实测:exim4 主命令(-bt/-bP/-be/-d/-odq)、队列查看与消息操作(-bp 家族、-Mf/-Mt/-Mg/-Mrm/-Mmad/-Mvh/-Mvb、-qff)、exipick/exiqgrep/exiqsumm/exinext、eximstats/exigrep/exiwhat/exim_dumpdb/exim_checkaccess/exim_lock/exim_tidydb/exicyclog,一次完整的 defer→冻结→处置→强制投递→日志统计闭环。

EximExim命令行邮件服务器2026/10/26

Exim 4.95 常用 CLI 命令详解

目标:把 Ubuntu 22.04 仓库版 Exim 4.95 自带的整套命令行工具用起来——exim4 主命令的地址测试、配置查询、队列查看与消息操作,以及 eximstats、exiqgrep、exipick、exiwhat、exim_dumpdb、exim_checkaccess、exim_lock 等配套工具。每条命令均给出真实输入与实测回显,覆盖一次完整的「入队 → defer → 冻结 → 处置 → 强制投递 → 日志统计」生命周期。实测环境为两台互为收发对端的 Ubuntu 22.04 服务器(实测日期:2026-10-26)。

一、环境信息

项目取值
操作系统Ubuntu 22.04(jammy,x86_64)
软件版本Exim 4.95(4.95-4ubuntu2.12)
主测试机mail1.example.org(10.89.61.11),邮件域 example.org,分片配置
对端机mail2.example.net(10.89.61.12),邮件域 example.net,单一配置文件
服务端口25/tcp(SMTP,STARTTLS 可用)
投递格式mail1 本地信箱 $home/Maildir/,root 邮件落 /var/mail/mail(mail4root)
测试账号root 与 admin(口令 q1w2e3r4)
日志/var/log/exim4/mainlog、/var/log/exim4/rejectlog

两台机器已完成互信路由(mail1 的 hubbed_hosts 把 example.net 指向 mail2,mail2 反向指回),安装与双机收发全流程见《Ubuntu 22.04 安装 Exim 4.95 指南》,配置框架见《Exim 4.95 配置参数详解》。

二、工具概览

Exim 不是「一个命令」,而是一个主命令加一组配套 Perl/C 工具的工具箱。先盘点 exim4-base 与 exim4-daemon-light 两个包在 /usr/sbin、/usr/bin 里到底装了什么:

root@mail1:~# ls -l /usr/sbin | grep -E 'exim|exi|runq|rmail|sendmail'
-rwxr-xr-x 1 root root     11293 Sep 22 18:29 exicyclog
-rwxr-xr-x 1 root root     10621 Sep 22 18:29 exigrep
lrwxrwxrwx 1 root root         5 Sep 22 18:29 exim -> exim4
-rwsr-xr-x 1 root root   1392208 Sep 22 18:29 exim4
-rwxr-xr-x 1 root root      4855 Sep 22 18:29 exim_checkaccess
-rwxr-xr-x 1 root root     74502 Sep 22 18:29 exim_convert4r4
-rwxr-xr-x 1 root root     18744 Sep 22 18:29 exim_dbmbuild
-rwxr-xr-x 1 root root     18744 Sep 22 18:29 exim_dumpdb
-rwxr-xr-x 1 root root     22840 Sep 22 18:29 exim_fixdb
-rwxr-xr-x 1 root root     18744 Sep 22 18:29 exim_lock
-rwxr-xr-x 1 root root     18744 Sep 22 18:29 exim_tidydb
-rwxr-xr-x 1 root root    151578 Sep 22 18:29 eximstats
-rwxr-xr-x 1 root root      8270 Sep 22 18:29 exinext
-rwxr-xr-x 1 root root     60677 Sep 22 18:29 exipick
-rwxr-xr-x 1 root root      5504 Sep 22 18:29 exiqgrep
-rwxr-xr-x 1 root root      5159 Sep 22 18:29 exiqsumm
-rwxr-xr-x 1 root root      4446 Sep 22 18:29 exiwho
root@mail1:~# readlink -f /usr/bin/mailq /usr/bin/newaliases /usr/sbin/sendmail /usr/sbin/runq /usr/sbin/rmail /usr/sbin/exim
/usr/sbin/exim4
/usr/sbin/exim4
/usr/sbin/exim4
/usr/sbin/exim4
/usr/sbin/exim4
/usr/sbin/exim4

两个关键结论:

  • mailq、newaliases 位于 /usr/bin(由 exim4-daemon-light 包提供),sendmail、runq、rmail、exim 都是 /usr/sbin/exim4 的符号链接——Debian 系约定主命令叫 exim4,exim 是兼容链接;
  • dpkg -L exim4-base 可以拿到权威工具清单(含 man 手册页):
root@mail1:~# dpkg -L exim4-base | grep '/usr/sbin'
/usr/sbin
/usr/sbin/exicyclog
/usr/sbin/exigrep
/usr/sbin/exim_checkaccess
/usr/sbin/exim_convert4r4
/usr/sbin/exim_dbmbuild
/usr/sbin/exim_dumpdb
/usr/sbin/exim_fixdb
/usr/sbin/exim_lock
/usr/sbin/exim_tidydb
/usr/sbin/eximstats
/usr/sbin/exinext
/usr/sbin/exipick
/usr/sbin/exiqgrep
/usr/sbin/exiqsumm
/usr/sbin/exiwhat
/usr/sbin/syslog2eximlog

工具家族分工如下(本文全部「✓ 实测」的命令在后续章节给出输入与回显;未标注者随包附带、不在本文展开):

工具归属包作用实测情况
exim4exim4-daemon-light主命令:地址测试、配置查询、队列管理、SMTP 提交✓ 实测
mailq / newaliasesexim4-daemon-lightexim4 的 -bp / -bi 包装✓ 实测
sendmail / runq / rmailexim4-daemon-lightSendmail 兼容入口✓ 实测
exipickexim4-base按条件挑选队列消息✓ 实测
exiqgrepexim4-base按发件人/收件人正则查队列✓ 实测
exiqsummexim4-base队列按域汇总✓ 实测
exinextexim4-base查询某主机的重试计划✓ 实测
eximstatsexim4-base日志统计分析报表✓ 实测
exigrepexim4-base按消息 ID 汇总日志轨迹✓ 实测
exiwhatexim4-base显示正在运行的 Exim 进程在做什么✓ 实测
exim_dumpdbexim4-base查看提示数据库(retry/等待/别名缓存)✓ 实测
exim_checkaccessexim4-base以指定 IP 测试收件地址是否被 ACL 接受✓ 实测
exim_lockexim4-base对信箱文件加锁并执行命令✓ 实测
exim_tidydbexim4-base清理提示数据库中的过期记录✓ 实测
exicyclogexim4-base轮转 mainlog/rejectlog✓ 实测
exim_dbmbuild / exim_fixdbexim4-base构建/修复 dbm 数据库未展开
exim_convert4r4exim4-baseExim 3 配置转 4 语法未展开
syslog2eximlogexim4-basesyslog 格式转 mainlog 格式未展开
exim_msgdate——Ubuntu 22.04 未打包该工具(见八、Q2)验证不存在

主命令版本与编译特性用 exim4 --version 确认(等价 exim4 -bV):

root@mail1:~# exim4 --version
Exim version 4.95 #2 built 22-Sep-2026 18:29:02
Copyright (c) University of Cambridge, 1995 - 2018
(c) The Exim Maintainers and contributors in ACKNOWLEDGMENTS file, 2007 - 2020
Berkeley DB: Berkeley DB 5.3.28: (September  9, 2013)
Support for: crypteq iconv() IPv6 GnuTLS TLS_resume move_frozen_messages DANE DKIM DNSSEC Event I18N OCSP PIPE_CONNECT PRDR Experimental_Queue_Ramp SOCKS SRS TCP_Fast_Open
Lookups (built-in): lsearch wildlsearch nwildlsearch iplsearch cdb dbm dbmjz dbmnz dnsdb dsearch nis nis0 passwd
Authenticators: cram_md5 external plaintext
Routers: accept dnslookup ipliteral manualroute queryprogram redirect
Transports: appendfile/maildir/mailstore autoreply lmtp pipe smtp
Fixed never_users: 0
Configure owner: 0:0
Size of off_t: 8
Configuration file search path is /etc/exim4/exim4.conf:/var/lib/exim4/config.autogenerated
Configuration file is /var/lib/exim4/config.autogenerated

💡 说明:Support for: 一行列出编译进二进制的特性,做 TLS/DKIM 排障前先看这一行;最后一行永远是实际生效的配置文件路径。

三、前置条件

本篇所有命令在 mail1.example.org 上以 root 执行(部分注明在 mail2 上)。开始前确认服务在跑、队列为空,保证后面每一步的队列状态变化都由本篇操作产生:

root@mail1:~# systemctl is-active exim4
active
root@mail1:~# mailq
root@mail1:~# exim4 -bpc
0
root@mail2:~# systemctl is-active exim4
active
root@mail2:~# mailq

mailq 与 exim4 -bp 完全等价(前者是符号链接包装),空队列时无输出。

四、exim4 主命令详解

exim4 的选项分几大族群:-bt/-bv 测地址、-bP 查配置、-be 测字符串展开、-d 调试、-bp* 看队列、-M*/-q* 操作队列。本章先讲不改变队列状态的「只读」族群,队列操作留给第五章。

4.1 地址测试与投递模拟(-bt / -bv)

-bt(batch test)对给定地址走一遍完整的路由器链,回答「这封信会投到哪、经哪个路由器/传输器、交给哪台主机」,是排查路由问题的第一工具:

root@mail1:~# exim4 -bt admin@example.org
R: system_aliases for admin@example.org
R: userforward for admin@example.org
R: system_aliases for admin@example.org
R: procmail for admin@example.org
R: maildrop for admin@example.org
R: lowuid_aliases for admin@example.org (UID 1000)
R: local_user for admin@example.org
R: hubbed_hosts for example.net
admin@example.org
    <-- admin@example.org
  router = local_user, transport = maildir_home
admin@example.net
    <-- admin@example.org
  router = hubbed_hosts, transport = remote_smtp
  host mail2.example.net [10.89.61.12]

解读这轮回显:

  • 前 8 行 R: 是逐个路由器的尝试轨迹(aliases → userforward → … → local_user 命中);
  • admin 有 .forward 文件(内容指向 admin@example.net),所以最终生成两个投递副本:本地 maildir_home 一份 + 远端 hubbed_hosts 经 SMTP 发往 mail2.example.net 一份;
  • R: system_aliases for admin@example.org 在 userforward 之后再次出现,是转发地址又被别名表过了一遍的正常链路。

没有 .forward 干扰的纯远端地址输出更干净:

root@mail1:~# exim4 -bt root@example.net
R: hubbed_hosts for example.net
root@example.net
  router = hubbed_hosts, transport = remote_smtp
  host mail2.example.net [10.89.61.12]

别名展开同样能看全:billing 是 /etc/aliases 里指向 admin 的别名,-bt 会展示两级展开:

root@mail1:~# exim4 -bt billing@example.org
R: system_aliases for billing@example.org
R: system_aliases for admin@example.org
R: userforward for admin@example.org
R: system_aliases for admin@example.org
R: procmail for admin@example.org
R: maildrop for admin@example.org
R: lowuid_aliases for admin@example.org (UID 1000)
R: local_user for admin@example.org
R: hubbed_hosts for example.net
admin@example.org
    <-- admin@example.org
    <-- billing@example.org
  router = local_user, transport = maildir_home
admin@example.net
    <-- admin@example.org
    <-- billing@example.org
  router = hubbed_hosts, transport = remote_smtp
  host mail2.example.net [10.89.61.12]

<-- 链就是「由谁展开而来」:admin@example.org <-- billing@example.org。

路由不存在的用户会直接给出失败结论:

root@mail1:~# exim4 -bt nosuchuser@example.org
R: system_aliases for nosuchuser@example.org
nosuchuser@example.org is undeliverable: Unrouteable address

-bv(verify)只验证地址能否路由、不模拟传输,输出更简短:

root@mail1:~# exim4 -bv admin@example.org
admin@example.org verified

💡 说明:-bt 输出里 router = 后是最终命中的路由器,transport = 后是投递方式;远端地址还会多一行 host ... [...] 给出实际 SMTP 目标与 IP。

4.2 配置项查询(-bP)

-bP 打印运行时配置项的实际生效值,一次可查多个,是核对「配置改没改对」的最快途径:

root@mail1:~# exim4 -bP exim_user exim_group spool_directory
exim_user = Debian-exim
exim_group = Debian-exim
spool_directory = /var/spool/exim4
root@mail1:~# exim4 -bP message_size_limit local_interfaces daemon_smtp_ports
message_size_limit = 50M
local_interfaces = <; 0.0.0.0 ; ::0
daemon_smtp_ports = smtp

解读:message_size_limit = 50M 是 Debian 配置框架预设的默认值(改法与验证见《Exim 4.95 配置参数详解》第五章);local_interfaces 的 <; 是 Exim 列表分隔符语法,表示后面用 ; 分隔,监听全部 IPv4 与 IPv6 地址。

4.3 字符串展开测试(-be)

-be 进入交互式展开模式,输入一行 Exim 字符串表达式就输出一行求值结果,写 ACL/路由条件前先在这里验证:

root@mail1:~# printf '%s\n' '${if eq{a}{a}{yes}{no}}' | exim4 -be
> yes
> 

输入行被求值为 yes;第二个 > 提示符出现后 stdin 关闭、工具退出。注意条件字符串必须整体加单引号,否则 ${...} 会被 shell 先行展开。

4.4 调试输出(-d)

-d 打开调试输出,与 -bt 组合可以看到「读配置 → 降权 → 逐路由器决策」的全过程,输出很长,通常配合 head/grep 截取(输出节选,实测前 45 行):

root@mail1:~# exim4 -d -bt admin@example.org 2>&1 | head -45
Exim version 4.95 uid=0 gid=0 pid=21489 D=f7715cfd
Support for: crypteq iconv() IPv6 GnuTLS TLS_resume move_frozen_messages DANE DKIM DNSSEC Event I18N OCSP PIPE_CONNECT PRDR Experimental_Queue_Ramp SOCKS SRS TCP_Fast_Open
Lookups (built-in): lsearch wildlsearch nwildlsearch iplsearch cdb dbm dbmjz dbmnz dnsdb dsearch nis nis0 passwd
Authenticators: cram_md5 external plaintext
Routers: accept dnslookup ipliteral manualroute queryprogram redirect
Transports: appendfile/maildir/mailstore autoreply lmtp pipe smtp
Fixed never_users: 0
Configure owner: 0:0
Size of off_t: 8
Compiler: GCC [11.4.0]
Library version: Glibc: Compile: 2.35
                        Runtime: 2.35
Library version: BDB: Compile: Berkeley DB 5.3.28: (September  9, 2013)
                      Runtime: Berkeley DB 5.3.28: (September  9, 2013)
Library version: GnuTLS: Compile: 3.7.3
                         Runtime: 3.7.3
Library version: IDN2: Compile: 2.3.2
                       Runtime: 2.3.2
Library version: Stringprep: Compile: 1.38
                             Runtime: 1.38
Library version: PCRE: Compile: 8.39
                       Runtime: 8.39 2016-06-14
Total 14 lookups
WHITELIST_D_MACROS: "OUTGOING"
TRUSTED_CONFIG_LIST: "/etc/exim4/trusted_configs"
changed uid/gid: forcing real = effective
  uid=0 gid=0 pid=21489
  auxiliary group list: <none>
seeking password data for user "uucp": cache not available
getpwnam() succeeded uid=10 gid=10
configuration file is /var/lib/exim4/config.autogenerated
log selectors = 0000cffc 64205022 0000003c
trusted user
admin user
dropping to exim gid; retaining priv uid
seeking password data for user "mail": cache not available
getpwnam() succeeded uid=8 gid=8
user name "root" extracted from gecos field "root"
originator: uid=0 gid=0 login=root name=root
sender address = root@example.org
Address testing: uid=0 gid=106 euid=0 egid=106
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Testing admin@example.org
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Considering admin@example.org

前半段是编译特性与库版本,中段 configuration file is ... 确认读到的配置文件,末段进入地址测试。调 ACL 细节时常用 -d 的分类开关(如 -d+expand 只看字符串展开),完整开关见 man 8 exim。

4.5 本地提交接口(sendmail -t / mail / -odq)

Exim 提供 Sendmail 兼容入口。最常用的是 sendmail -t:从邮件头(To/Cc/Bcc)解析收件人,适合脚本发信:

root@mail1:~# printf 'To: admin@example.org\nSubject: CLI sendmail -t\nFrom: root@example.org\n\nsendmail -t body via CLI doc\n' | sendmail -t
root@mail1:~# tail -4 /var/log/exim4/mainlog
2026-10-26 11:46:53 1xLJAD-0005a9-2A <= root@example.org U=root P=local S=366
2026-10-26 11:46:53 1xLJAD-0005a9-2A => admin <admin@example.org> R=local_user T=maildir_home
2026-10-26 11:46:53 1xLJAD-0005a9-2A => admin@example.net <admin@example.org> R=hubbed_hosts T=remote_smtp H=mail2.example.net [10.89.61.12] X=TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256 CV=no DN="CN=mail2.example.net,O=Example Lab,C=CN" K C="250- 378 byte chunk, total 378\\n250 OK id=1xLJAD-0005ed-3Y"
2026-10-26 11:46:53 1xLJAD-0005a9-2A Completed

一封信投出两份(本地 Maildir + 转发到 mail2),验证落箱:

root@mail1:~# grep -h '^Subject:' /home/admin/Maildir/new/* | tail -3
Subject: cross-01b
Subject: fwd-02
Subject: CLI sendmail -t

交互/脚本发信的另一条路是 bsd-mailx 提供的 mail 命令(Subject 用 -s,正文走 stdin),后续章节都用它造测试消息:

root@mail1:~# printf 'CLI queue test A\n' | mail -s 'CLI-A' admin@example.net

-odq(queue only)让消息只入队、不做立即投递尝试,适合压测灌队列:

root@mail1:~# printf 'CLI queue test E (odq)\n' | exim4 -odq admin@example.net
root@mail1:~# exim4 -bp
 0m   320 1xLJ8Y-0005Xi-81 <root@example.org>
          admin@example.net

root@mail1:~# grep 1xLJ8Y-0005Xi-81 /var/log/exim4/mainlog
2026-10-26 11:45:10 1xLJ8Y-0005Xi-81 <= root@example.org U=root P=local S=320

对比普通提交:-odq 的消息在 mainlog 里只有 <= 一行,没有 == defer 行——根本没尝试投递。

五、队列管理命令详解

队列是 MTA 运维的主战场。本章用一个真实的故障闭环演示:停掉 mail2 的 exim4 → mail1 上消息 defer → 各种队列查看 → 冻结/解冻/放弃/删除 → 恢复 mail2 → 强制投递。除标注外命令均在 mail1 上执行。

5.1 制造 defer 与队列查看(-bp 家族)

先把 mail2 的 exim4 停掉(模拟对端宕机),再提交消息 A:

root@mail2:~# systemctl stop exim4
root@mail2:~# systemctl is-active exim4
inactive
root@mail1:~# printf 'CLI queue test A\n' | mail -s 'CLI-A' admin@example.net
root@mail1:~# sleep 3
root@mail1:~# mailq
 0m   452 1xLJ7K-0005VZ-DY <root@example.org>
          admin@example.net

root@mail1:~# exim4 -bpc
1

mailq 每条消息两行:第一行是「在队时长、体积、消息 ID、发件人」,第二行起是未投出的收件人。-bpc 只给计数。mailq 输出里 1xLJ7K-0005VZ-DY 这种 ID 由「时间戳编码-进程号-序号」构成,所有 -M* 操作都拿它当参数。

-bp 家族的变体(队列中只有一条消息时输出与 -bp 相同,差异体现在多条与过滤维度上):

root@mail1:~# exim4 -bp
 0m   452 1xLJ7K-0005VZ-DY <root@example.org>
          admin@example.net

root@mail1:~# exim4 -bpr
 0m   452 1xLJ7K-0005VZ-DY <root@example.org>
          admin@example.net

root@mail1:~# exim4 -bpu
 0m   452 1xLJ7K-0005VZ-DY <root@example.org>
          admin@example.net

root@mail1:~# exim4 -bpa
 0m   452 1xLJ7K-0005VZ-DY <root@example.org>
          admin@example.net
  • -bpr:随机顺序列出(多条消息时避免每次同一顺序);
  • -bpu:只列未投出的地址,已投出的地址不显示;
  • -bpa:连已标记 delivered 的地址一起列出(5.4 节 -Mmad 之后能直观看到 D 标记)。

5.2 队列消息检索与摘要(exiqgrep / exiqsumm / exinext)

exiqgrep 按条件在队列里找消息,-f 按发件人、-r 按收件人(支持正则),-i 只输出消息 ID 方便脚本取用:

root@mail1:~# exiqgrep -f root@example.org
 0m   452 1xLJ7K-0005VZ-DY <root@example.org>
	  admin@example.net

root@mail1:~# exiqgrep -r admin@example.net
 0m   452 1xLJ7K-0005VZ-DY <root@example.org>
	  admin@example.net

root@mail1:~# exiqgrep -r admin@example.net -i
1xLJ7K-0005VZ-DY

exiqsumm 从 stdin 读 mailq 输出、按目标域汇总条数/体积/最老最新:

root@mail1:~# mailq | exiqsumm

Count  Volume  Oldest  Newest  Domain
-----  ------  ------  ------  ------

    1     452      0m      0m  example.net
---------------------------------------------------------------
    1     452      0m      0m  TOTAL

exinext 回答「这个目标主机下次什么时候重试」——数据来自 retry 提示库:

root@mail1:~# exinext mail2.example.net
R: dnslookup for user@mail2.example.net
No remote hosts found for mail2.example.net
Transport: mail2.example.net [10.89.61.12] error 111: Connection refused
  first failed: 26-Oct-2026 11:43:54
  last tried:   26-Oct-2026 11:43:54
  next try at:  26-Oct-2026 11:58:54

error 111: Connection refused 正是 mail2 服务被停的表现;Debian 默认重试规则首轮 15 分钟,所以 next try 恰为失败时刻 +15 分钟。

5.3 冻结、解冻与队列筛选(-Mf / -Mt / exipick)

-Mf 冻结:消息不再参与队列跑,适合「先留住现场再排查」。冻结后队列行尾会出现 *** frozen *** 标记:

root@mail1:~# exim4 -Mf 1xLJ7K-0005VZ-DY
Message 1xLJ7K-0005VZ-DY is now frozen
root@mail1:~# mailq
 0m   452 1xLJ7K-0005VZ-DY <root@example.org> *** frozen ***
          admin@example.net

冻结窗口正好用来演示队列选择器 exipick(按消息属性筛选队列,是脚本化队列运维的主力)——-z 只看冻结消息、-i 只输出消息 ID、-c 只输出计数:

root@mail1:~# exipick -z
 0m   452 1xLJ7K-0005VZ-DY <root@example.org> *** frozen ***
          admin@example.net

root@mail1:~# exipick -i -z
1xLJ7K-0005VZ-DY
root@mail1:~# exipick -c
1 matches out of 1 messages
root@mail1:~# exipick -c -z
1 matches out of 1 messages

-i 的输出可接 xargs exim4 -M 这类管道做批量操作;-c -z 即冻结消息数。-Mt 解冻(thaw),消息恢复参与队列调度:

root@mail1:~# exim4 -Mt 1xLJ7K-0005VZ-DY
Message 1xLJ7K-0005VZ-DY is no longer frozen
root@mail1:~# mailq
 0m   452 1xLJ7K-0005VZ-DY <root@example.org>
          admin@example.net

5.4 放弃、删除与标记(-Mg / -Mrm / -Mmad / -M)

-Mg 放弃消息并向发件人退 NDR(give up)。先提交消息 B 制造第二条 defer,从队列里取出新消息 ID(把已知的老消息 A 过滤掉)再对它执行:

root@mail1:~# printf 'CLI queue test B\n' | mail -s 'CLI-B' admin@example.net
root@mail1:~# sleep 3
root@mail1:~# exiqgrep -r admin@example.net -i | grep -v 1xLJ7K | head -1
1xLJ7n-0005Wd-FO
root@mail1:~# exim4 -Mg 1xLJ7n-0005Wd-FO
root@mail1:~# mailq
 0m   452 1xLJ7K-0005VZ-DY <root@example.org>
          admin@example.net

 0m  1.6K 1xLJ7q-0005Wp-Gd <>
          root@example.org

root@mail1:~# grep 1xLJ7n-0005Wd-FO /var/log/exim4/mainlog
2026-10-26 11:44:23 1xLJ7n-0005Wd-FO <= root@example.org U=root P=local S=452
2026-10-26 11:44:23 1xLJ7n-0005Wd-FO == admin@example.net R=hubbed_hosts T=remote_smtp defer (-54): retry time not reached for any host for 'example.net'
2026-10-26 11:44:26 1xLJ7n-0005Wd-FO cancelled by root
2026-10-26 11:44:26 1xLJ7q-0005Wp-Gd <= <> R=1xLJ7n-0005Wd-FO U=Debian-exim P=local S=1678
2026-10-26 11:44:26 1xLJ7n-0005Wd-FO Completed

注意三点:B 进队列时因重试时间未到直接 defer (-54);-Mg 后日志出现 cancelled by root;队列里多出的 1xLJ7q-0005Wp-Gd < > 是发给 root 的退信(NDR,发件人为空 <>),随后由 mail4root 路由器落进 /var/mail/mail:

root@mail1:~# grep 1xLJ7q-0005Wp-Gd /var/log/exim4/mainlog
2026-10-26 11:44:26 1xLJ7q-0005Wp-Gd <= <> R=1xLJ7n-0005Wd-FO U=Debian-exim P=local S=1678
2026-10-26 11:44:26 1xLJ7q-0005Wp-Gd => /var/mail/mail <root@example.org> R=mail4root T=address_file
2026-10-26 11:44:26 1xLJ7q-0005Wp-Gd Completed

-Mrm 删除消息且不退信(remove)。用消息 C 演示:

root@mail1:~# printf 'CLI queue test C\n' | mail -s 'CLI-C' admin@example.net
root@mail1:~# sleep 3
root@mail1:~# exim4 -Mrm 1xLJ8S-0005XF-68
Message 1xLJ8S-0005XF-68 has been removed
root@mail1:~# grep 1xLJ8S-0005XF-68 /var/log/exim4/mainlog
2026-10-26 11:45:04 1xLJ8S-0005XF-68 <= root@example.org U=root P=local S=452
2026-10-26 11:45:04 1xLJ8S-0005XF-68 == admin@example.net R=hubbed_hosts T=remote_smtp defer (-54): retry time not reached for any host for 'example.net'
2026-10-26 11:45:07 1xLJ8S-0005XF-68 removed by root
2026-10-26 11:45:07 1xLJ8S-0005XF-68 Completed

-Mmad(mark all delivered)把消息的全部收件地址标记为已投递,消息等下次队列跑时自然出队——与 -Mrm 的区别是它会更新提示数据库、向中间路由留下「已投递」痕迹:

root@mail1:~# printf 'CLI queue test D\n' | mail -s 'CLI-D' admin@example.net
root@mail1:~# sleep 3
root@mail1:~# exim4 -Mmad 1xLJ8V-0005XU-74
Message 1xLJ8V-0005XU-74 has been modified
root@mail1:~# mailq
 1m   452 1xLJ7K-0005VZ-DY <root@example.org>
          admin@example.net

 0m   452 1xLJ8V-0005XU-74 <root@example.org>
        D admin@example.net

root@mail1:~# grep 1xLJ8V-0005XU-74 /var/log/exim4/mainlog
2026-10-26 11:45:07 1xLJ8V-0005XU-74 <= root@example.org U=root P=local S=452
2026-10-26 11:45:07 1xLJ8V-0005XU-74 == admin@example.net R=hubbed_hosts T=remote_smtp defer (-54): retry time not reached for any host for 'example.net'
2026-10-26 11:45:10 1xLJ8V-0005XU-74 address <admin@example.net> marked delivered by root

队列行里的 D 前缀就是「该地址已标记投递」。

-M <id> 单独强制重试一条消息(忽略重试时间,等价 -qff 只针对这一条)。

5.5 查看消息 spool 内容(-Mvh / -Mvb)

排队中的消息以两个文件存在 /var/spool/exim4/input/:-H 头部文件与 -D 正文文件。-Mvh 直接打印 -H 文件全文(信封元数据 + 全部邮件头):

root@mail1:~# exim4 -Mvh 1xLJ7K-0005VZ-DY
1xLJ7K-0005VZ-DY-H
root 0 0
<root@example.org>
1793015034 0
-received_time_usec .420035
-received_time_complete 1793015034.420730
-ident root
-received_protocol local
-body_linecount 1
-max_received_linelength 50
-auth_id root
-auth_sender root@example.org
-allow_unqualified_recipient
-allow_unqualified_sender
-local
-manual_thaw
-tls_resumption A
XX
1
admin@example.net

178P Received: from root by mail1.example.org with local (Exim 4.95)
	(envelope-from <root@example.org>)
	id 1xLJ7K-0005VZ-DY
	for admin@example.net;
	Mon, 26 Oct 2026 11:43:54 +0000
022T To: admin@example.net
015  Subject: CLI-A
018  MIME-Version: 1.0
051  Content-Type: text/plain; charset="ANSI_X3.4-1968"
032  Content-Transfer-Encoding: 8bit
050I Message-Id: <E1xLJ7K-0005VZ-DY@mail1.example.org>
030F From: root <root@example.org>
038  Date: Mon, 26 Oct 2026 11:43:54 +0000

每行开头的 NNNx 是 Exim 头部长度标记(如 022T 表示 To 头共 22 字节、T 是头类型)。-Mvb 对应打印 -D 正文文件:

root@mail1:~# exim4 -Mvb 1xLJ7K-0005VZ-DY
1xLJ7K-0005VZ-DY-D
CLI queue test A

⚠️ 注意:-Mvh/-Mvb 需 root 或 Debian-exim 权限;spool 文件是 Exim 内部格式,只读排查用,不要手工编辑。

5.6 队列跑(-q / -qff / runq)

daemon 自带周期队列跑(本环境 exiwhat 显示为 -q30m,见 6.3 节)。手工触发有三个入口:

  • exim4 -q / runq:后台跑一轮(受重试时间约束);
  • exim4 -qff:前台跑并强制投递(忽略重试时间)——恢复故障后的标准动作。

恢复 mail2 服务,对还在队里的 A(解冻后)与 E 执行强制投递:

root@mail2:~# systemctl start exim4
root@mail2:~# systemctl is-active exim4
active
root@mail1:~# exim4 -qff
root@mail1:~# mailq
root@mail1:~# grep -E '1xLJ7K-0005VZ-DY|1xLJ8Y-0005Xi-81' /var/log/exim4/mainlog | grep '=>'
2026-10-26 11:45:20 1xLJ7K-0005VZ-DY => admin@example.net R=hubbed_hosts T=remote_smtp H=mail2.example.net [10.89.61.12] X=TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256 CV=no DN="CN=mail2.example.net,O=Example Lab,C=CN" K C="250- 467 byte chunk, total 467\\n250 OK id=1xLJ8i-0005ZU-MP"
2026-10-26 11:45:20 1xLJ8Y-0005Xi-81 => admin@example.net R=hubbed_hosts T=remote_smtp H=mail2.example.net [10.89.61.12] X=TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256 CV=no DN="CN=mail2.example.net,O=Example Lab,C=CN" K C="250- 330 byte chunk, total 330\\n250 OK id=1xLJ8i-0005Zb-Nh"

两条消息均以 TLS 1.3 送到 mail2 并在 mail1 队列清零。对端落箱验证:

root@mail2:~# grep -E '1xLJ8i-0005ZU-MP|1xLJ8i-0005Zb-Nh' /var/log/exim4/mainlog
2026-10-26 11:45:20 1xLJ8i-0005ZU-MP <= root@example.org H=(mail1.example.org) [10.89.61.11] P=esmtps X=TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256 CV=no K S=719 id=E1xLJ7K-0005VZ-DY@mail1.example.org
2026-10-26 11:45:20 1xLJ8i-0005ZU-MP => admin <admin@example.net> R=local_user T=maildir_home
2026-10-26 11:45:20 1xLJ8i-0005ZU-MP Completed
2026-10-26 11:45:20 1xLJ8i-0005Zb-Nh <= root@example.org H=(mail1.example.org) [10.89.61.11] P=esmtps X=TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256 CV=no K S=587 id=E1xLJ8Y-0005Xi-81@mail1.example.org
2026-10-26 11:45:20 1xLJ8i-0005Zb-Nh => admin <admin@example.net> R=local_user T=maildir_home
2026-10-26 11:45:20 1xLJ8i-0005Zb-Nh Completed
root@mail2:~# grep -h '^Subject:' /home/admin/Maildir/new/* | tail -2
Subject: fwd-02
Subject: CLI-A

-odq 提交的消息 E 没有 Subject 头,所以只以 CLI-A 的形式在信箱留下主题可查。runq 后台跑一轮队列,成功时无任何输出:

root@mail1:~# runq
root@mail1:~# echo $?
0

5.7 生命周期小结

阶段命令日志特征
入队mail -s / sendmail -t / exim4 -odq<=(-odq 之后无 == 行)
投递失败——(daemon 或 -q 触发)== ... defer (-54): retry time not reached ...
冻结/解冻-Mf / -Mt队列行出现/消失 *** frozen ***
放弃退信-Mgcancelled by root + 新增 NDR 消息
静默删除-Mrmremoved by root
标记已投-Mmadaddress <...> marked delivered by root,队列行地址前加 D
强制投递-M <id> / -qff=> 行 + Completed

六、日志与诊断工具详解

6.1 eximstats:日志统计报表

eximstats 读 mainlog 产出总量报表、按时段分布与按传输器/主机分解(输出节选,实测前 55 行):

root@mail1:~# eximstats /var/log/exim4/mainlog 2>&1 | head -55

Exim statistics from 2026-10-26 10:59:02 to 2026-10-26 11:45:20

Grand total summary
-------------------
                                                                  At least one address
  TOTAL               Volume   Messages Addresses     Hosts      Delayed       Failed
  Received              11KB         20                   4       4 20.0%      0  0.0%
  Delivered             15KB         24        24         2
  Rejects                             5                   1

Deliveries by transport
-----------------------
                      Volume    Messages
  address_file          1678           1
  maildir_home          5405           8
  remote_smtp           7916          15

Messages received per hour (each dot is 1 message)
--------------------------------------------------

00-01      0 
01-02      0 
02-03      0 
03-04      0 
04-05      0 
05-06      0 
06-07      0 
07-08      0 
08-09      0 
09-10      0 
10-11      1 .
11-12     19 ...................
12-13      0 
13-14      0 
14-15      0 
15-16      0 
16-17      0 
17-18      0 
18-19      0 
19-20      0 
20-21      0 
21-22      0 
22-23      0 
23-24      0 

Deliveries per hour (each dot is 1 delivery)
--------------------------------------------

00-01      0 
01-02      0 

报表后半段还有「按主机/按目标域/按发件人」的 Top 排名与延迟直方图。Delayed 20.0% 正是本轮 defer 实验留下的真实痕迹。常用变体:eximstats -html < mainlog > report.html 产出网页报表,-t 10 调整 Top 表行数。

6.2 exigrep:按消息 ID 汇总日志轨迹

mainlog 里一条消息的行散落在时间线上,exigrep(Exim grep)按给定模式找出所有相关行并按消息聚合:

root@mail1:~# exigrep 1xLJAD-0005a9-2A /var/log/exim4/mainlog
2026-10-26 11:46:53 1xLJAD-0005a9-2A <= root@example.org U=root P=local S=366
2026-10-26 11:46:53 1xLJAD-0005a9-2A => admin <admin@example.org> R=local_user T=maildir_home
2026-10-26 11:46:53 1xLJAD-0005a9-2A => admin@example.net <admin@example.org> R=hubbed_hosts T=remote_smtp H=mail2.example.net [10.89.61.12] X=TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256 CV=no DN="CN=mail2.example.net,O=Example Lab,C=CN" K C="250- 378 byte chunk, total 378\\n250 OK id=1xLJAD-0005ed-3Y"
2026-10-26 11:46:53 1xLJAD-0005a9-2A Completed

四行读作:本地提交(<=)→ 本地投递 + 远端投递(两个 =>)→ 完成。排障时先 grep 关键词拿 ID、再 exigrep <ID> 拉全程。

6.3 exiwhat:进程在做什么

exiwhat 给所有 Exim 进程发信号让其报告当前动作:

root@mail1:~# exiwhat
19550 daemon(4.95): -q30m, listening for SMTP on port 25 (IPv6 and IPv4)

平时只有 daemon 一行:监听 25 端口收信、每 30 分钟跑一次队列(-q30m)。有投递/队列跑正在进行时会额外列出对应进程及其正在处理的消息。

6.4 exim_dumpdb:提示数据库

Exim 的重试计划、等待队列等状态存在 /var/spool/exim4/db/ 下的提示库。exim_dumpdb <spool 目录> <库名> 查看,常用库名 retry:

投递失败存在时(对端宕机期间):

root@mail1:~# exim_dumpdb /var/spool/exim4 retry
  T:mail2.example.net:10.89.61.12 111 65 Connection refused
26-Oct-2026 11:46:25  26-Oct-2026 11:46:25  26-Oct-2026 12:01:25 

一行一条重试记录:目标 T:mail2.example.net:10.89.61.12、错误码 111(Connection refused)、首次/最近失败时间与下次重试时间——与 exinext 读到的是同一份数据。恢复投递并强制队列跑成功后,对应记录被清除:

root@mail1:~# exim_dumpdb /var/spool/exim4 retry
root@mail1:~# 

(无输出即库已清空——全部主机投递正常时 retry 库保持为空,属正常现象。)

6.5 exim_checkaccess:ACL 预演

exim_checkaccess <IP> <收件地址> 以「来自该 IP 的 RCPT」跑一遍收件 ACL,不真发邮件。先看已授权网段内外的差别:

root@mail1:~# exim_checkaccess 10.89.61.12 admin@example.org
Accepted
root@mail1:~# exim_checkaccess 10.89.61.99 someone@example.com
Accepted
root@mail1:~# exim_checkaccess 192.168.100.55 someone@example.com
Rejected:
  550 relay not permitted

前两个都返回 Accepted:10.89.61.0/24 是本机 dc_relay_nets 授权的中继网段,网段内任意 IP(含本网段其他主机 .99)向外部域发信都会被接受;第三个 IP 在网段外,命中 ACL 的 require message = relay not permitted 被拒。中继三态(网段/认证/拒绝)的完整实验见《Exim 4.95 配置参数详解》6.3 节。

6.6 exim_lock:信箱文件加锁

exim_lock -v <文件> <命令> 以 Exim 同款方式(lock 文件 + fcntl)锁住信箱后执行命令,验证信箱锁竞争或手动安全操作:

root@mail1:~# exim_lock -v /var/mail/mail 'echo lock-held-command-ok'
exim_lock: lockname =  /var/mail/mail.lock
           hitchname = /var/mail/mail.lock.mail1.example.org.6adf3dad.000053d2
exim_lock: creating lock file
exim_lock: lock file successfully created
exim_lock: fcntl() lock successfully applied
exim_lock: locking /var/mail/mail succeeded: running the command ...
lock-held-command-ok
exim_lock: /var/mail/mail closed
exim_lock: lock file removed

hitchname 是带主机名与随机数的「勾住文件」——appendfile 传输器投递 mbox 用的就是这套机制。锁一个不存在的文件会明确报错(实测,目标换成 /var/mail/admin):

root@mail1:~# exim_lock -v /var/mail/admin 'echo x'
exim_lock: lockname =  /var/mail/admin.lock
           hitchname = /var/mail/admin.lock.mail1.example.org.6adf3d9d.000053cb
exim_lock: creating lock file
exim_lock: lock file successfully created
exim_lock: failed to open /var/mail/admin for writing: No such file or directory
exim_lock: lock file removed

本环境本地投递走 Maildir,/var/mail/admin 天然不存在——这个报错恰好演示了「mbox 才需要 exim_lock,Maildir 目录投递无需文件锁」。

6.7 exim_tidydb:提示库清理

长期运行的机器上 retry/等待库可能积累陈旧记录,exim_tidydb 定期清理(Debian 已通过 exim4-base.timer 每周自动执行,手工版如下):

root@mail1:~# exim_tidydb /var/spool/exim4 retry
Tidying Exim hints database /var/spool/exim4/db/retry
Tidying complete

6.8 exicyclog:日志轮转

exicyclog 轮转 mainlog/rejectlog(默认保留 10 代,编号 .01 最旧——首次轮转旧日志成为 .01)。执行成功无输出,用 ls 验证:

root@mail1:~# exicyclog
root@mail1:~# ls -l /var/log/exim4/
total 28
-rw-r----- 1 Debian-exim adm     0 Oct 26 11:47 mainlog
-rw-r----- 1 Debian-exim adm 17819 Oct 26 11:46 mainlog.01
-rw-r----- 1 Debian-exim adm     0 Oct 26 11:47 rejectlog
-rw-r----- 1 Debian-exim adm  1573 Oct 26 11:32 rejectlog.01

mainlog 归零、旧内容进入 mainlog.01。注意轮转后 eximstats 应指定新文件或合并多个代(如 eximstats /var/log/exim4/mainlog*)。

七、注意事项

  1. 权限:队列操作(-M*)、spool 查看(-Mvh/-Mvb)、exim_dumpdb 需要 root;exim4 -bt 等只读测试普通用户可执行,但结果可能因读不到受限配置而略有差异,运维脚本统一用 root 执行。
  2. 命令名用 exim4:Debian 系主命令是 exim4,文档与脚本不要用裸 exim(虽然存在链接),避免与其他发行版习惯混淆。
  3. -Mg 与 -Mrm 都不可逆:前者会向发件人退 NDR(对伪造发件人的垃圾队列会照退不误),后者静默删除;清理大批量垃圾队列推荐 exipick -i ... | xargs exim4 -Mrm 先抽样确认再执行。
  4. -qff 是恢复动作:只在故障恢复后使用;长期对同一批「暂时不可达」的目标反复 -qff 会压垮对端并打乱退避节奏。
  5. exicyclog 会清空当前 mainlog:做日志相关的演示或统计前先确认轮转时点,避免「日志怎么没了」的错觉。
  6. 选项文档入口是 man:man 8 exim 覆盖主命令全部选项,各配套工具各有 man 8 eximstats、man 8 exipick 等(清单见第二章 dpkg -L 输出)。

八、常见问题

Q1:exim4 -h 或 exim4 --help 报错? Exim 没有帮助选项,这是设计如此(选项太多且互相组合)。实测回显:

root@mail1:~# exim4 -h
exim abandoned: unknown, malformed, or incomplete option -h

查选项用 man 8 exim;只读族人门组合是 -bt/-bP/-bp,队列族是 -M*/-q*。

Q2:exim_msgdate 提示 command not found? 上游 Exim 自带该工具(把消息 ID 反解为收信时间),但 Ubuntu 22.04 的 exim4-base 未打包(第二章 dpkg -L 清单中确无此项)。实测:

root@mail1:~# exim_msgdate 1xLJAD-0005a9-2A
bash: exim_msgdate: command not found

替代方案:消息 ID 的时间段编码本身有序,用 exigrep <ID> /var/log/exim4/mainlog 看 <= 行的时间戳即可。

Q3:mailq/newaliases 属于哪个包、等价什么命令? 均由 exim4-daemon-light 提供的 /usr/bin 符号链接,最终指向 /usr/sbin/exim4:mailq = exim4 -bp,newaliases = exim4 -bi(重建别名索引,本环境别名是实时 lsearch,执行成功无输出、exit 0)。实测:

root@mail1:~# readlink -f /usr/bin/mailq /usr/bin/newaliases
/usr/sbin/exim4
/usr/sbin/exim4
root@mail1:~# newaliases
root@mail1:~# echo $?
0

Q4:-Mvh 输出的一大段是什么? 那是消息的 -H 头部 spool 文件原文(信封元数据 + 头部,行首 NNNx 为长度标记),-Mvb 对应 -D 正文文件。它们是排查「这封信到底长什么样」的最底层视图,见 5.5 节。

Q5:retry 库(exim_dumpdb)是空的,是不是坏了? 不是。全部目标投递正常时 retry 库保持为空;只有出现 defer/失败后才会写入记录(见 6.4 节两态对比)。exim_tidydb 只清理过期记录,不产生「修复」效果。

Q6:同一目标反复 defer,如何看还要等多久? exinext <主机或域名> 直接读 retry 库给出 first failed / last tried / next try 三个时刻(见 5.2 节回显);配合 exipick -c 看积压量,决定是等待还是 -M/-qff 强推。

九、参考链接