OpenSMTPD 常用 CLI 命令详解
OpenSMTPD 7.9.0p0 的命令行管理工具详解:smtpctl 15 个子命令与 show 10 个子命令按业务域逐条实测(队列查看/暂停恢复/重排删除/日志与运行时诊断/SPF 诊断/队列恢复),附 smtpd(8) 守护进程参数、sendmail 兼容层、离线队列闭环与排障实录
OpenSMTPD 常用 CLI 命令详解
目标:把 OpenSMTPD 7.9.0p0 的命令行管理工具讲透——
smtpctl的全部子命令按「状态与队列查看、队列管理、日志与运行时诊断、安全与队列恢复」四个业务域逐条给出实测语法与真实回显,smtpd(8)守护进程侧参数与 sendmail 兼容层一并覆盖,并沉淀使用过程中的排障实录(smtpctl stop已移除、pause 状态不持久化、monitor 管道缓冲无输出等)。本文所有命令与回显均在 Ubuntu 22.04(OpenSMTPD 7.9.0p0 源码编译版)实测通过,实测日期 2026-10-21。
一、环境信息
本文所有命令与回显均在以下环境实测通过:
| 项目 | 值 |
|---|---|
| 操作系统 | Ubuntu 22.04.5 LTS(x86_64) |
| 主机名 | mail.example.com |
| 软件版本 | OpenSMTPD 7.9.0p0(上游最新稳定版,2026-09-13 发布) |
| 安装方式 | 官方源码编译(安装前缀 /usr/local,安装过程见安装指南篇) |
| smtpctl 路径 | /usr/local/sbin/smtpctl(setgid _smtpq) |
| smtpd 路径 | /usr/local/sbin/smtpd |
| 配置文件 | /usr/local/etc/smtpd.conf |
| 队列目录 | /var/spool/smtpd |
| systemd 服务 | opensmtpd79.service |
| 特权分离用户 | _smtpd / _smtpq |
| 演示账号 | root / 普通用户 admin(密码 q1w2e3r4) |
| 测试工具 | swaks 20201014.0(制造队列流量) |
二、工具概览
OpenSMTPD 的命令行工具体系由「一个守护进程本体 + 一个控制命令 + 一组兼容层符号链接」构成。与多数 MTA 不同,OpenSMTPD 的常驻进程 smtpd 没有交互式管理子命令——运行时的全部控制都经 smtpctl 下发,改配置才需要重启 smtpd(配置语句见 《OpenSMTPD 7.9 配置参数详解》,站内链接见文末参考)。
2.1 子命令全景
不带参数运行 smtpctl,会打印全部顶级子命令(这是 7.9.0p0 内建的用法提示,也是最快的速查入口):
smtpctl
实测回显(退出码 255):
possibilities are:
discover
pause
resume
show
log
monitor
profile
remove
schedule
trace
unprofile
untrace
update
encrypt
spf
15 个顶级子命令中,show 自带第二级子命令。同样地,不带参数运行 smtpctl show:
smtpctl show
实测回显(退出码 255):
possibilities are:
show mta
show envelope
show hoststats
show message
show queue
show hosts
show relays
show routes
show stats
show status
按业务域整理后的完整命令语法清单如下(依 7.9.0p0 命令注册口径与内建用法提示整理,<msgid> 为 8 位十六进制消息 ID、<evpid> 为 16 位十六进制信封 ID):
| 域 | 命令语法 | 职责 |
|---|---|---|
| 查看 | show status | SMTP/MTA/MDA 三职能运行状态 |
| 查看 | show queue [msgid] | 队列信封列表(管道分隔格式) |
| 查看 | show envelope <evpid> | 单个信封全部字段 |
| 查看 | show message <msgid|evpid> | 消息正文(含头) |
| 查看 | show stats | 内部计数器(key=value) |
| 查看 | show hosts / show relays / show routes | MTA 出站主机/中继/路由表 |
| 查看 | show hoststats | 按域名统计的中继结果 |
| 查看 | show mta paused | MTA 域级阻断列表 |
| 队列 | pause mda|mta|smtp / resume mda|mta|smtp | 职能级暂停/恢复 |
| 队列 | pause|resume envelope <msgid|evpid|all> | 信封级暂停/恢复 |
| 队列 | pause|resume mta from <addr> for <domain> | MTA 域级阻断/解除 |
| 队列 | schedule <msgid|evpid|all> | 立即重排投递 |
| 队列 | remove <msgid|evpid|all> | 删除队列信封 |
| 队列 | update table <name> | 热加载 file 表 |
| 队列 | discover <msgid|evpid> | 从盘上重扫描信封注入调度器 |
| 诊断 | log verbose|brief | 日志详细/简洁模式 |
| 诊断 | monitor | 每秒采样的实时监控 |
| 诊断 | trace <子系统> / untrace <子系统> | 分级调试跟踪开关 |
| 诊断 | profile <imsg|queue> / unprofile <…> | 内部剖析计数开关 |
| 安全 | encrypt [明文] | 生成 crypt 口令散列 |
| 安全 | spf walk | 从 stdin 逐行做 SPF 记录展开 |
💡 说明:命令分「特权」与「非特权」两档。特权命令(上表绝大多数)要求以 root 身份执行;非特权命令只有
encrypt与spf walk。这是由控制通道的权限模型决定的,2.3 节有实测。
2.2 权限模型与调用前提
smtpctl 通过 /var/run/smtpd.sock 控制套接字与 smtpd 主进程通信。非 root 用户执行特权命令时直接拒绝,实测(以 admin 用户执行):
su - admin -c '/usr/local/sbin/smtpctl show queue'
实测回显(退出码 1):
smtpctl: need root privileges
而非特权命令 encrypt 以普通用户可正常执行:
su - admin -c 'echo q1w2e3r4 | /usr/local/sbin/smtpctl encrypt'
实测回显(退出码 0):
$y$jCT$wNtkeQQL5PvTqy797iYqW0$hy8w05Pgtt2xrlPbNwFP22FeSzbUujm6Wm2sX33LDu1
另外 smtpctl 二进制必须保持 setgid _smtpq 位(源码版若手工补过权限,用 ls -l 确认 -r-xr-sr-x 1 root _smtpq),否则报 smtpctl: this program must be setgid _smtpq——这是源码安装篇踩过并修复过的坑。
2.3 输出约定
掌握三条输出约定,所有子命令的回显就都能读懂:
- 成功应答:多数控制类命令成功时输出固定一行
command succeeded(如 pause/resume/log/trace/update table); - 计数应答:作用于信封集合的命令输出动作计数,如
1 envelope scheduled、2 envelopes removed、0 envelope paused——0 开头不代表失败,只是命中的信封数为 0(比如队列已空或信封处于不可操作状态); - 用法提示:子命令拼写或参数形态不对时,输出
possibilities are:+ 合法形态列表,退出码 255——这份提示本身就是权威的参数清单(2.1 节即由此取证)。
三、前置条件
复现本文操作需要:
- 一台已安装 OpenSMTPD 7.9.0p0 并正在运行的 Ubuntu 22.04 服务器(安装流程见 《Ubuntu 22.04 安装 OpenSMTPD 指南》),运行状态验证:
systemctl is-active opensmtpd79
/usr/local/sbin/smtpctl show status
实测回显:
active
MDA running
MTA running
SMTP running
MDA(本地投递)、MTA(出站中继)、SMTP(收信会话)三个职能全部 running 即服务健康。
- 演示用户与别名就绪(OpenSMTPD 默认拒绝向 root 直接投递,需在
/etc/aliases中把 root 别名到普通用户):
id admin
grep '^root:' /etc/aliases
实测回显:
uid=1000(admin) gid=1000(admin) groups=1000(admin)
root: admin
- 测试工具 swaks(用于向队列注入真实邮件,观察队列类命令的实际效果):
swaks --version 2>&1 | head -1
实测回显:
swaks version 20201014.0
四、状态与队列查看类子命令详解
查看类命令是日常巡检与排障的第一入口。本章逐条给出 show 各子命令的语法、回显与字段解析。
4.1 show status:三职能运行状态
show status 回答「服务现在能收、能发、能投吗」。三个职能分别对应配置章节的 listen(SMTP)、relay 动作(MTA)、maildir/mbox/mda 动作(MDA):
smtpctl show status
实测回显(正常态):
MDA running
MTA running
SMTP running
职能被 pause 后状态变为 paused(见第五章),例如暂停 MDA 后:
smtpctl pause mda
smtpctl show status
实测回显:
command succeeded
MDA paused
MTA running
SMTP running
排障时若某个职能长期 paused 而无人承认操作过,优先怀疑上一次 restart 前的 pause 被忽略(pause 不持久化,见 9.2),或配置语法变更后动作未挂载。
4.2 show queue:队列列表与管道格式逐列解析
show queue 列出当前队列中的全部信封(envelope)。OpenSMTPD 的队列模型是「消息(message)—信封(envelope)」两级:一封邮件是一个消息,其每个收件人对应一个信封,独立投递、独立重试。先向队列注入两封测试邮件(暂停 MDA 使其滞留):
smtpctl pause mda
swaks --to root@mail.example.com --from admin@mail.example.com --server 127.0.0.1:25 --h-Subject 'sched-test-1' 2>&1 | grep '250 2.0.0'
swaks --to admin@mail.example.com --from admin@mail.example.com --server 127.0.0.1:25 --h-Subject 'sched-test-2' 2>&1 | grep '250 2.0.0'
smtpctl show queue
实测回显(swaks 只保留接受行,队列两条完整列出):
command succeeded
<- 250 2.0.0 cffeb9ea Message accepted for delivery
<- 250 2.0.0 04b0c493 Message accepted for delivery
04b0c49383d17823|inet4|mda||admin@mail.example.com|admin@mail.example.com|admin@mail.example.com|1792578756|1792578756|0|0|pending|0|
cffeb9ea644cdae7|inet4|mda||admin@mail.example.com|root@mail.example.com|root@mail.example.com|1792578756|1792578756|0|0|pending|0|
每行是一个信封,竖线 | 分隔共 13 列,逐列含义如下(依 smtpctl(8) 手册口径与 7.9.0p0 实测回显对照整理):
| 列 | 本例取值 | 含义 |
|---|---|---|
| 1 | 04b0c49383d17823 | 信封 ID(evpid),16 位十六进制,前 8 位即消息 ID(msgid) |
| 2 | inet4 | 发件来源套接字族:inet4 / inet6 / local(本机 socket 注入) |
| 3 | mda | 投递代理类型:mda / mta / bounce |
| 4 | (空) | 信封标志组合(逗号连接):bounce、auth、internal、suspend、hold;空即无标志 |
| 5 | admin@mail.example.com | 发件人 |
| 6 | admin@mail.example.com | 收件人(RCPT TO 原值) |
| 7 | admin@mail.example.com | 实际投递目标(经别名/虚拟域展开后的最终信箱) |
| 8 | 1792578756 | 信封创建时间(epoch 秒) |
| 9 | 1792578756 | 过期时间(创建时间 + ttl;ttl 为 0 时不过期,与创建时间相同) |
| 10 | 0 | 上次投递尝试时间(0 = 从未尝试) |
| 11 | 0 | 已重试次数 |
| 12 | pending|0 | 运行状态|秒数,三种形态见下方说明 |
| 13 | (空) | 最近一次错误行(如远端 SMTP 应答),多数情况为空 |
第 12 列的运行状态是排障时最值得关注的一列,三种实测形态:
pending|N——等待投递,N 为距下次尝试的秒数;负值表示已过期待试(如pending|-5即 5 秒前就该重试)、pending|0表示即刻尝试;inflight|N——投递进行中,N 为距上次尝试的秒数;offline|——smtpd 停止时经show queue离线读盘看到的信封(7.4 节实测)。
4.3 show queue <msgid>:按消息过滤
show queue 支持追加一个 msgid 参数,只列出该消息的信封。用 4.2 中第二封邮件的 msgid cffeb9ea:
smtpctl show queue cffeb9ea
实测回显:
cffeb9ea644cdae7|inet4|mda||admin@mail.example.com|root@mail.example.com|root@mail.example.com|1792578756|1792578756|0|0|pending|0|
一封邮件有多个收件人时,该命令会列出同 msgid 的多个信封行——这是定位「某封信的某个收件人为什么没收到」的标准入口。
4.4 show envelope <evpid>:单个信封全字段
show envelope 展开单个信封的全部内部字段,比队列行的 13 列更细(多出 dispatcher、helo、sockaddr、ttl、mda-user 等)。注意它只接受完整的 16 位 evpid,传 8 位 msgid 会报用法提示(实测排障,见 9.1):
smtpctl show envelope c8a469afecb34628
实测回显:
version: 3
dispatcher: local
type: mda
smtpname: mail.example.com
helo: mail.example.com
hostname: localhost
sockaddr: 127.0.0.1
sender: admin@mail.example.com
rcpt: admin@mail.example.com
dest: admin@mail.example.com
ctime: 1792578599
last-try: 0
last-bounce: 0
ttl: 0
retry: 0
dsn-notify: 0
mda-user: admin
逐项解读:version 为信封结构版本号;dispatcher/type 标识信封归属的投递路径(local 派发器 + mda 类型);smtpname 是收下该信封的监听口名;helo/hostname/sockaddr 记录 SMTP 会话来源;sender/rcpt/dest 与队列行第 5–7 列一致;ctime/last-try/last-bounce/ttl/retry 是调度计数字段;dsn-notify 标记 DSN 请求位;mda-user 是本地投递将使用的系统用户(本例经别名展开为 admin)——排查「信投给了谁、以谁的身份投」就看这一行。
4.5 show message <msgid|evpid>:查看消息正文
show message 输出消息的完整内容(头部 + 正文),既接受 msgid 也接受 evpid,是确认「队列里这封信到底是什么内容」的直读手段:
smtpctl show message c8a469af
实测回显:
Received: from mail.example.com (localhost [127.0.0.1])
by mail.example.com (OpenSMTPD) with ESMTP id c8a469af
for <admin@mail.example.com>;
Wed, 21 Oct 2026 10:29:59 +0000 (UTC)
Date: Wed, 21 Oct 2026 10:29:59 +0000
To: admin@mail.example.com
From: admin@mail.example.com
Subject: cli-d1-test-2
Message-Id: <20261021102959.020138@mail.example.com>
X-Mailer: swaks v20201014.0 jetmore.org/john/code/swaks/
This is a test mailing
💡 技术拓展:MTA 的可观测手段不止
show家族,三层手段各司其职,横向对比——
手段 粒度 时效 典型用途 show status/show queue队列与状态快照 当下 巡检、定位滞留信封 monitor每秒采样的计数增量 实时流 观察流量趋势、确认注信是否进队 trace <子系统>会话级调试行 开关期间 深挖 SMTP 会话/规则命中细节 show stats进程启动以来的累计计数 累计值 容量与健康度复盘 本文实操顺序通常是:
show queue定位 →monitor看增量 → 必要时trace深挖。
4.6 show stats:内部计数器
show stats 输出全部内部计数器(key=value 形式),覆盖控制通道、队列缓存、调度器、MDA/MTA 各子系统。输出较长,以下为实测节选(首部):
smtpctl show stats | head -24
实测回显:
control.session=1
mda.envelope=0
mda.pending=0
mda.running=0
mda.user=0
mta.connector=0
mta.domain=0
mta.envelope=0
mta.host=0
mta.relay=0
mta.route=0
mta.session=0
mta.source=0
mta.task=0
mta.task.running=0
queue.evpcache.load.hit=34
queue.evpcache.size=0
scheduler.delivery.ok=11
scheduler.envelope=0
scheduler.envelope.incoming=0
scheduler.envelope.inflight=0
scheduler.envelope.removed=1
scheduler.ramqueue.envelope=0
scheduler.ramqueue.message=0
关键指标解读:scheduler.delivery.ok 是投递成功累计次数(本例 11 次,与此前投递闭环次数一致);scheduler.envelope.inflight/incoming 是当前在投/入队中信封数,稳定为 0 说明队列排空;queue.evpcache.load.hit 是信封缓存命中数,异常暴涨通常意味着反复重读盘上信封(调度异常征兆)。mta.* 各项在无出站流量时为 0,出站活跃时用于观察连接与任务堆积。
4.7 show hosts / show relays / show routes / show hoststats:出站中继四件套
这四个命令展示 MTA 出站侧的实时拓扑。为了让它们有内容可看,先触发一笔真实的中继事务(本环境配置了 action "smart" relay host smtp://127.0.0.1:2525,将 relaytest.example 域的信转发到本机 2525 口的接收端):
swaks --to someone@relaytest.example --from admin@mail.example.com --server 127.0.0.1:25 --h-Subject 'mta-show-test' 2>&1 | grep '250 2.0.0'
smtpctl show hosts
smtpctl show relays
smtpctl show routes
smtpctl show hoststats
实测回显:
<- 250 2.0.0 cc605ffc Message accepted for delivery
127.0.0.1 localhost refcount=2 nconn=1 lastconn=0s
[relay:127.0.0.1,port=2525,smtp,mx] refcount=2 ntask=0 nconn=1 lastconn=0s timeout=1s wait=connector
connector [] refcount=0 nconn=1 lastconn=0s timeout=- flags=
1. [] <-> 127.0.0.1 (localhost) ---- nconn=1 nerror=0 penalty=0 timeout=-
relaytest.example|1792578908|250 OK
逐条解读:
show hosts——MTA 已感知的对端主机表:127.0.0.1 localhost refcount=2 nconn=1 lastconn=0s,nconn 为活跃连接数,lastconn 为距上次连接的秒数;show relays——中继端点(relay URI)与连接器状态:[relay:127.0.0.1,port=2525,smtp,mx]是中继标识,wait=connector表示正处在连接阶段,任务/连接计数随事务推进变化;show routes——源地址到对端的「路由」表,nerror=0 penalty=0表示无失败记录、无惩罚降权(MTA 对连续失败的主机会累计 penalty 并拉长重试间隔,这里是健康态);show hoststats——按收件域汇总最近一次中继结果:relaytest.example|1792578908|250 OK即该域最后一次成功中继的时间与远端应答。
排障口诀:出站卡住时按 show hoststats(哪个域最近一次结果是什么)→ show routes(penalty 是否暴涨)→ show relays(卡在 connector 还是 mx 等待)→ show queue(信封 runstate)的顺序走。
4.8 show mta paused:域级阻断列表
show mta paused 列出被 pause mta from <addr> for <domain> 阻断的「来源地址 → 目标域」对(操作见 5.3),无阻断时输出为空:
smtpctl show mta paused
实测回显(无阻断,空输出):
五、队列管理类子命令详解
队列管理类命令解决四类日常诉求:临时停掉某个方向的投递(pause/resume)、把滞留信封提前重排(schedule)、删除问题信封(remove)、热加载数据表(update table)。每条命令都在真实队列上闭环验证。
5.1 pause/resume mda|mta|smtp:职能级暂停与恢复
pause 冻结一个投递职能,resume 恢复。三个对象:mda(本地投递)、mta(出站中继)、smtp(入站会话)。最常用的场景是暂停 MDA 制造受控滞留——本章多数实验的前置步骤:
smtpctl pause mda
smtpctl show status
实测回显:
command succeeded
MDA paused
MTA running
SMTP running
暂停期间新信照常入队但不投递。恢复后滞留信封立即投出,日志中留下两条证据(info: mda resumed 与投递行,投递行的 delay 字段即滞留时长):
smtpctl resume mda
journalctl -t smtpd --no-pager -n 3 | grep -E 'resumed|mda delivery' | tail -2
实测回显:
command succeeded
Oct 21 10:33:01 mail.example.com smtpd[20218]: info: mda resumed
Oct 21 10:33:01 mail.example.com smtpd[20220]: 3f89b096506423fd mda delivery evpid=cffeb9ea644cdae7 from=<admin@mail.example.com> to=<root@mail.example.com> rcpt=<root@mail.example.com> user=admin delay=25s result=Ok stat=Delivered
delay=25s 正是该信封从入队到 resume 的滞留时长,pause/resume 闭环成立。
⚠️ 排障实录:pause 状态不持久化。实测把 MDA pause 后执行
systemctl restart opensmtpd79,重启完成即回到 running,滞留信封随即全部投出——MDA running / MTA running / SMTP running。若你的运维流程依赖「重启期间冻结投递」,restart 后要重新 pause,不能假设暂停状态跨重启保留。
5.2 pause/resume envelope:信封级暂停与恢复
比职能级更细一格,可把单个消息/信封钉在队列里不动(其余信封照常投递)。传 msgid 作用于该消息全部信封,传 evpid 作用于单个信封,all 作用于全队列。实测先 pause mda 制造 pending 信封,再按 msgid 暂停:
smtpctl pause mda
swaks --to root@mail.example.com --from admin@mail.example.com --server 127.0.0.1:25 --h-Subject 'envhold-test' 2>&1 | grep '250 2.0.0'
smtpctl pause envelope bd1f6679
smtpctl show queue
实测回显:
command succeeded
<- 250 2.0.0 bd1f6679 Message accepted for delivery
1 envelope paused
bd1f6679407685fe|inet4|mda|suspend|admin@mail.example.com|root@mail.example.com|root@mail.example.com|1792579007|1792579007|0|0|pending|-10|
对比 4.2 的队列格式:第 4 列标志位从空变为 suspend,即信封级暂停的落点。恢复并放开职能级暂停后信封正常投出:
smtpctl resume envelope bd1f6679
smtpctl resume mda
smtpctl show queue
实测回显:
1 envelope resumed
command succeeded
(队列已空,无输出。)
两个实测边界:
- 对不在 pending 态的信封操作会得到 0 计数。实测队列信封已进入
inflight(投递进行中)时执行pause envelope all:
smtpctl pause envelope all
实测回显:
0 envelope paused
resume envelope同理按 msgid/evpid/all 三种粒度计数(1 envelope resumed)。
5.3 pause/resume mta from <addr> for <domain>:出站域级阻断
这是 MTA 方向的「熔断开关」:指定来源地址对指定目标域的中继全部冻结,常用于「上游域名故障/被投诉,先止损再排查」。实测阻断 127.0.0.1 → example.com:
smtpctl pause mta from 127.0.0.1 for example.com
smtpctl show mta paused
smtpctl resume mta from 127.0.0.1 for example.com
smtpctl show mta paused
实测回显:
command succeeded
127.0.0.1 -> example.com
command succeeded
(解除后 show mta paused 空输出。)阻断列表是运行时状态,重启后清空(同 5.1 的不持久化口径)。
5.4 schedule:立即重排投递
schedule 把信封的下次尝试时间改成立即,用于「配置已修复/上游已恢复,不等重试间隔了」。接受 msgid、evpid、all 三种粒度。实测(队列中留有一封滞留信):
smtpctl schedule cffeb9ea
实测回显:
1 envelope scheduled
按 evpid 亦可:
smtpctl schedule cffeb9ea644cdae7
实测回显:
1 envelope scheduled
队列空时 schedule all 返回 0 计数(成功但无事可做):
smtpctl schedule all
实测回显:
0 envelope scheduled
注意:若信封被 5.2 的 envelope 级 suspend 钉住,schedule 不会解除 suspend,先 resume envelope 再 schedule。
5.5 remove:删除队列信封
remove 从队列删除信封(不产生退信),是处理死信、误发件、垃圾洪水的主要手段。按 evpid 删除单个信封:
smtpctl remove f8b952bc7b3a7ef6
实测回显:
1 envelope removed
按 msgid 删除整封消息的全部信封;remove all 清空队列。实测注入两封滞留信后一键清队:
smtpctl remove all
smtpctl show queue
实测回显:
2 envelopes removed
(队列空,无输出。)被删除的信封直接消失,不生成退信——若需要让发件人知道邮件失败,应让 ttl 过期走 bounce 流程而不是手工 remove。
5.6 update table:热加载文件表
update table 通知守护进程重新加载指定 file 表(别名、凭据、黑白名单等),无需重启。实测(修改 /etc/aliases 后):
smtpctl update table aliases
实测回显:
command succeeded
配合 newaliases 使用(重建别名库 + 通知热加载),以及凭据表热加载后的认证验证闭环,见 《OpenSMTPD 7.9 配置参数详解》5.2 节的实测。
六、日志与运行时诊断类子命令详解
诊断类命令回答三个问题:日志现在记多细(log)、流量此刻长什么样(monitor)、某个子系统的内部过程能否展开看(trace/profile)。
6.1 log verbose / log brief:日志详细度开关
log verbose 把守护进程日志切到详细模式,log brief 切回。两命令本身只回一句成功,效果体现在 journal 的日志密度上:
smtpctl log verbose
smtpctl log brief
实测回显:
command succeeded
command succeeded
详细模式主要多出进程间消息与调度细节,用于「日志里找不到线索」的场合;排查完记得切回 brief,避免长期刷盘。
6.2 monitor:每秒采样实时监控
monitor 每秒输出一行计数增量,表头分四段:client(连接)、envelope(队列进出)、relay/delivery(投递结果)、misc(过期/删除/退信):
timeout 6 smtpctl monitor
实测回显(6 秒采样窗口,保留前几行):
--- client --- -- envelope -- ---- relay/delivery --- ------- misc -------
curr conn disc curr enq deq ok tmpfail prmfail loop expire remove bounce
0 8 8 0 11 11 10 0 0 0 0 1 0
0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0
列序解读:curr conn disc 为当前连接数/连接累计/断开累计;curr enq deq 为队列中信封数/入队/出队;ok tmpfail prmfail loop 为投递成功/临时失败/永久失败/环路;expire remove bounce 为过期/删除/退信。首行是进程启动以来的累计值(对零做差),后续每行是上一秒增量——首行 conn=8 enq=11 ok=10 bounce=1 即为该实例启动至今的真实吞吐。
⚠️ 排障实录:把 monitor 的输出重定向到文件或管道时,
timeout N smtpctl monitor > 文件会得到空文件——printf 在非终端上是全缓冲,timeout 的 SIGTERM 直接把未刷新的缓冲区一起带走。实测两种可用姿势:① 直接在真实服务器上交互运行(本就是 monitor 的正常用法);② 采样期间另开一个会话注信,回看交互输出。真实服务器上 monitor 就是设计为交互使用的,上述坑只影响「采集到文件」的自动化场景——此时建议改用show stats做两次快照取差值。
6.3 trace / untrace:子系统级调试开关
trace 打开指定子系统的调试日志(输出进守护进程日志,journalctl -t smtpd 可见),untrace 关闭。合法关键字共 13 个(7.9.0p0 实测口径):
| 关键字 | 跟踪对象 |
|---|---|
| imsg | 进程间消息 |
| io | 底层 IO 事件 |
| smtp | SMTP 会话(收发两侧协议行) |
| filters | 过滤器评估 |
| mta | 出站中继 |
| bounce | 退信 |
| scheduler | 调度决策 |
| lookup | 表查询 |
| stat | 计数器变化 |
| rules | match 规则命中 |
| mproc | 进程间管道 |
| expand | 别名/虚拟域展开 |
| all | 以上全部(不含 debug 级) |
最有实用价值的是 trace smtp——把 SMTP 会话的每一步协议交互展开成日志。实测开 trace 后注入一封信,抓取日志中的会话展开:
smtpctl trace smtp
swaks --to admin@mail.example.com --from admin@mail.example.com --server 127.0.0.1:25 --h-Subject 'trace-cap2' >/dev/null 2>&1
journalctl -t smtpd --no-pager -n 25 | tail -14
smtpctl untrace smtp
实测回显(journal 节选,trace 期间):
Oct 21 10:40:28 mail.example.com smtpd[20220]: <<< [MSG]
Oct 21 10:40:28 mail.example.com smtpd[20220]: <<< [MSG]
Oct 21 10:40:28 mail.example.com smtpd[20220]: <<< [MSG] .
Oct 21 10:40:28 mail.example.com smtpd[20220]: <<< [EOM]
Oct 21 10:40:28 mail.example.com smtpd[20220]: smtp: 0x5f355474e3a0: >>> 250 2.0.0 4c53f259 Message accepted for delivery
Oct 21 10:40:28 mail.example.com smtpd[20220]: 3f89b0b424cb05ad smtp message msgid=4c53f259 size=614 nrcpt=1 proto=ESMTP
Oct 21 10:40:28 mail.example.com smtpd[20220]: 3f89b0b424cb05ad smtp envelope evpid=4c53f2596be2c1d8 from=<admin@mail.example.com> to=<admin@mail.example.com>
Oct 21 10:40:28 mail.example.com smtpd[20220]: smtp: 0x5f355474e3a0: STATE_BODY -> STATE_HELO
Oct 21 10:40:28 mail.example.com smtpd[20222]: debug: scheduler: evp:4c53f2596be2c1d8 scheduled (mda)
Oct 21 10:40:28 mail.example.com smtpd[20220]: smtp: 0x5f355474e3a0: <<< QUIT
Oct 21 10:40:28 mail.example.com smtpd[20220]: smtp: 0x5f355474e3a0: >>> 221 2.0.0 Bye
Oct 21 10:40:28 mail.example.com smtpd[20220]: smtp: 0x5f355474e3a0: STATE_HELO -> STATE_QUIT
Oct 21 10:40:28 mail.example.com smtpd[20220]: 3f89b0b424cb05ad smtp disconnected reason=quit
<<< 是对端发来的协议行、>>> 是本端应答,msgid/evpid/会话状态机(STATE_*)一目了然。传非法关键字会被直接点名(实测 smtpctl trace bogus):
smtpctl: invalid trace keyword: bogus
trace rules 排查 match 规则命中顺序的实测(rule #1 matched 日志形态)见 《OpenSMTPD 7.9 配置参数详解》4.3 节。
6.4 profile / unprofile:内部剖析计数
profile 打开两个剖析维度之一:imsg(进程间消息量)或 queue(队列操作量),打开后 show stats 中会出现对应剖析计数;unprofile 关闭。实测:
smtpctl profile queue
smtpctl unprofile queue
实测回显:
command succeeded
command succeeded
传其他值报错(实测 smtpctl profile bogus,退出码 1):
smtpctl: invalid profile keyword: bogus
profile 的定位是性能排查辅助(哪个内部通道消息量异常),日常巡检用不到,保持关闭。
七、安全与队列恢复类子命令详解
本章覆盖三个「低频但关键」的命令:encrypt(凭据散列)、spf walk(SPF 记录诊断)、discover(队列信封重扫描),以及与 discover 配套的离线队列机制。
7.1 encrypt:生成凭据散列
encrypt 把明文口令转成 crypt 散列,用于生成 auth 凭据表内容。既可作位置参数也可从 stdin 读入(盐随机,每次输出不同):
smtpctl encrypt q1w2e3r4
echo q1w2e3r4 | smtpctl encrypt
实测回显(两次运行,散列值不同属正常):
$y$jCT$rYz5EvgWB5A.4zQ2cKYvZ0$1lwVmj0mJ5opDiQ86SsdFjEMr9SBJPpXGSWT7OjZyj7
$y$jCT$wNtkeQQL5PvTqy797iYqW0$hy8w05Pgtt2xrlPbNwFP22FeSzbUujm6Wm2sX33LDu1
散列写入凭据表文件(用户名 散列 空格分隔)并 smtpctl update table 热加载的完整闭环见 《OpenSMTPD 7.9 配置参数详解》5.2 节。
7.2 spf walk:SPF 记录展开诊断
spf walk 从 stdin 逐行读域名,解析其 SPF(Sender Policy Framework)TXT 记录,输出该域授权发信的 IP 网段(含 include 展开与重定向),可选 -4/-6 只看 IPv4/IPv6 段。用 gmail.com 实测:
echo gmail.com | smtpctl spf walk
实测回显:
74.125.0.0/16
209.85.128.0/17
2001:4860:4864::/56
2404:6800:4864::/56
2607:f8b0:4864::/56
2800:3f0:4864::/56
2a00:1450:4864::/56
2c0f:fb50:4864::/56
⚠️ 实测说明:SPF 展开结果随对端记录随时变化(上方为 2026-10-21 实测值,不同时间、不同 include 展开深度下输出会有出入),命令用法与输出形态以本文为准,具体网段以当场执行结果为准。
💡 技术拓展:发信域身份校验不止 SPF 一项,主流三件套的分工——
机制 校验什么 失败表现 SPF 信封发件域授权哪些 IP 发信 域名 TXT 记录比对不通过 DKIM 邮件头/正文的密码学签名 签名验证失败或头被篡改 DMARC 前两者结果与 From 头域的对齐策略 按 p= 策略拒收/隔离 OpenSMTPD 7.9 内建
filterDSL 可做 SPF 阶段校验,DKIM/DMARC 需外挂(rspamd 等);spf walk是运维侧的记录诊断工具,用于「这个域到底授权了哪些网段」的核对。
7.3 discover:队列信封重扫描
discover 让队列子系统从盘上重读指定消息的信封文件并重新注入调度器,接受 msgid 或 evpid,输出发现的信封数。注意:它不是「域名/MX 发现」工具(旧资料有此误传,实测确认其操作对象是队列信封)。
对在队消息实测(返回其信封数,重读幂等):
smtpctl pause mda
swaks --to root@mail.example.com --from admin@mail.example.com --server 127.0.0.1:25 --h-Subject 'discover-live' >/dev/null 2>&1
smtpctl show queue
smtpctl discover 6d74e7db
实测回显:
6d74e7db53969bb6|inet4|mda||admin@mail.example.com|root@mail.example.com|root@mail.example.com|1792579534|1792579534|0|0|pending|0|
1 envelope discovered
对已投递/不存在的消息返回 0(实测 smtpctl discover deadbeef):
0 envelopes discovered
典型用途:从备份恢复过 /var/spool/smtpd/queue 子树、或怀疑调度器内存队列与盘上文件脱节时,用 discover 把盘上信封重新拉回调度视野。清理本次实验信封:
smtpctl remove all
smtpctl resume mda
实测回显:
1 envelope removed
command succeeded
7.4 离线队列与停机期间的队列查看
OpenSMTPD 的 sendmail 兼容层在 smtpd 停止时不会报错拒信,而是把信件落成离线文件,待服务恢复后自动接管投递。完整闭环实测:
第一步,停服务后本机提交一封信:
systemctl stop opensmtpd79
echo 'offline-queue-d1-test' | sendmail root
ls -l /var/spool/smtpd/offline/
实测回显:
total 4
-rw------- 1 root _smtpq 27 Oct 21 10:40 1792579257.XXXXTYLviV
第二步,启动服务,离线信自动投递:
systemctl start opensmtpd79
journalctl -t smtpd --no-pager -n 6 | grep 'mda delivery' | tail -1
实测回显:
Oct 21 10:40:59 mail.example.com smtpd[20578]: c96d92aa87bf6f63 mda delivery evpid=75c912050fa732e0 from=<root@mail.example.com> to=<root@mail.example.com> rcpt=<root@mail.example.com> user=admin delay=0s result=Ok stat=Delivered
第三步(灾检场景):若队列中已有滞留信封时服务停了,show queue 依然可用——此时 smtpctl 连不上控制套接字,会直接读盘上 /queue 目录列出持久化信封,runstate 显示为 offline|:
smtpctl pause mda
swaks --to root@mail.example.com --from admin@mail.example.com --server 127.0.0.1:25 --h-Subject 'offline-read-test' >/dev/null 2>&1
systemctl stop opensmtpd79
smtpctl show queue
实测回显:
66be77696e0c13d4|inet4|mda||admin@mail.example.com|root@mail.example.com|root@mail.example.com|1792579335|1792579335|0|0|offline||
恢复服务并放开投递,信封照常投出:
systemctl start opensmtpd79
smtpctl resume mda
journalctl -t smtpd --no-pager -n 8 | grep 'mda delivery' | tail -1
实测回显:
Oct 21 10:42:16 mail.example.com smtpd[20679]: 01b391065d95b6bd mda delivery evpid=66be77696e0c13d4 from=<admin@mail.example.com> to=<root@mail.example.com> rcpt=<root@mail.example.com> user=admin delay=1s result=Ok stat=Delivered
这套行为意味着:停机不影响队列数据安全,盘上队列 + 离线提交文件 + show queue 离线读盘三层兜底,排障时即使服务起不来也能看清队列积压内容。
八、smtpd(8) 守护进程与 sendmail 兼容层命令
管理 OpenSMTPD 除 smtpctl 外还有两个入口:smtpd 本体的启动参数,以及 sendmail 兼容层。
8.1 smtpd 启动参数
smtpd -h 给出版本与参数全景:
smtpd -h
实测回显:
version: OpenSMTPD 7.9.0-portable
usage: smtpd [-dFhnv] [-D macro=value] [-f file] [-P system] [-T trace]
常用参数逐项说明(均实测或源码语义核实):
| 参数 | 作用 | 实测要点 |
|---|---|---|
-f file | 指定配置文件路径 | 源码版默认 /usr/local/etc/smtpd.conf,包版 /etc/smtpd.conf |
-n | 仅做配置语法检查后退出 | 输出 configuration OK,改配置的第一道安全网 |
-d | 前台运行不脱离终端 | 配 -v 加详细度,排障利器 |
-v | 详细日志 | 常与 -d 组合为 smtpd -dv |
-D macro=value | 命令行覆盖配置宏 | 测试不同宏取值时免改文件 |
-P system | 启动时即暂停指定投递代理 | 取值 smtp/mta/mda,如 -P mda 启动即冻结本地投递 |
-T trace | 启动时即打开某 trace | 等价启动后 smtpctl trace |
-F | 前台运行(不 fork) | 配 systemd 简单托管时可考虑 |
smtpd -n 实测:
smtpd -n
实测回显:
configuration OK
⚠️ 注意:7.9.0p0 的 smtpd 不支持 SIGHUP 热重载——实测对主进程发 HUP 会导致进程退出(
smtpd: unexpected signal)。改配置的标准闭环是smtpd -n语法检查 →systemctl restart→show status确认,完整排障实录见 《OpenSMTPD 7.9 配置参数详解》3.2 节。
8.2 sendmail 兼容层:sendmail / newaliases / mailq
安装后系统里存在指向 smtpctl 的兼容符号链接,本机程序(cron、PHP mail() 等)无需改造即可投信。实测本环境(源码版 + 曾装过 apt 包版的服务器):
ls -l /usr/local/sbin/sendmail /usr/sbin/sendmail /usr/sbin/newaliases
ls -l /usr/sbin/smtpctl
实测回显:
lrwxrwxrwx 1 root root 23 Oct 21 00:50 /usr/local/sbin/sendmail -> /usr/local/sbin/smtpctl
lrwxrwxrwx 1 root root 7 Nov 29 2021 /usr/sbin/sendmail -> smtpctl
lrwxrwxrwx 1 root root 7 Nov 29 2021 /usr/sbin/newaliases -> smtpctl
-rwxr-sr-x 1 root opensmtpq 174904 Nov 29 2021 /usr/sbin/smtpctl
要点解读:
- smtpctl 按程序名分支行为(argv[0] 机制):名为
sendmail时走提交逻辑(含停机时的离线落盘,见 7.4),名为newaliases时重建别名库,名为mailq时等价smtpctl show queue(本环境源码版未建 mailq 链接,包版提供); - 曾装过发行版包的服务器上并存两套 smtpctl:
/usr/sbin/smtpctl(包版)与/usr/local/sbin/smtpctl(源码版)。PATH 决定裸smtpctl命中谁,实测:
which smtpctl
实测回显:
/usr/local/sbin/smtpctl
⚠️ 注意:两套版本的 smtpctl 操作的是同一个
/var/spool/smtpd队列,但二进制逻辑有版本差。运维脚本中一律写绝对路径指向你实际运行的那一套(本文统一/usr/local/sbin/smtpctl),避免 PATH 差异导致「命令跑了、对端不是这版」的隐形错位。
8.3 smtpctl stop 已移除(排障实录)
旧版资料(6.x 时代)常用 smtpctl stop 停服务。7.9.0p0 中该子命令已不存在,实测执行只会得到子命令清单,服务不受任何影响:
smtpctl stop
systemctl is-active opensmtpd79
实测回显:
possibilities are:
discover
pause
resume
show
log
monitor
profile
remove
schedule
trace
unprofile
untrace
update
encrypt
spf
active
正确停止方式是 systemd:systemctl stop opensmtpd79(对应服务名按实际单元)。这同时说明 2.1 的 possibilities are: 清单是完整闭集——清单里没有的子命令就是不存在,不必再翻旧文档。
九、注意事项
汇总使用中的高频坑位,每条都有实测依据:
- pause/resume 不持久化:职能级暂停、域级阻断在 restart 后全部回到 running/清空。依赖「重启期间冻结」的流程要在重启后重新执行 pause。
- monitor 只适合交互使用:管道/文件重定向场景因 stdio 全缓冲 + timeout 截断会拿到空输出(6.2 排障实录)。采集类需求用
show stats前后快照取差值。 - show envelope 只认完整 evpid:传 8 位 msgid 会得到
possibilities are: show envelope <evpid>用法提示(9.1);而show message两.ID 都收。记法:envelope 看信封(必须指到具体信封),message 看内容(消息级即可)。 - 两套 smtpctl 并存的服务器写绝对路径:见 8.2,PATH 命中错版本是隐形事故源。
- schedule 不解除 suspend:被
pause envelope钉住的信封 schedule 无效,先 resume envelope。 - remove 不产生退信:需要让发件人收到失败通知时,走 ttl 过期的 bounce 流程(队列行第 9 列过期时间),不要用 remove 兜底。
- trace/profile 用完即关:trace 打开后调试行持续写日志(
smtpctl untrace <子系统>关闭),trace all 的量级足以刷爆日志盘。 - smtpctl 需 setgid _smtpq:手工补权限时用
chgrp _smtpq smtpctl && chmod g+s smtpctl,丢了该位任何队列类命令都跑不起来。 - 停机排障有兜底:服务起不来时
show queue仍能离线读盘列队列(7.4),结合discover可把盘上信封重新纳入调度。
十、常见问题(FAQ)
Q1:smtpctl show envelope c8a469af 为什么报 possibilities are: show envelope <evpid>?
show envelope 只接受 16 位完整 evpid。8 位 msgid 先用 smtpctl show queue(或日志里的 evpid 字段)换算:msgid 的 16 位形态就是队列行第 1 列。本文 4.4 有正确用法与完整回显。
Q2:timeout 5 smtpctl monitor > /tmp/out 采集到的文件是空的?
stdio 全缓冲导致样本滞留在缓冲区,timeout 杀进程时未刷新。monitor 设计为交互使用;自动化采集改用两次 show stats 快照做差(6.2)。
Q3:smtpd 停止时 smtpctl show queue 会报错吗?
不会。实测退出码 0:队列空时无输出;盘上 /queue 有信封时以 offline| 状态列出(7.4 实测)。只有 smtpctl 本身缺 setgid 位或队列目录损坏才会报错。
Q4:smtpctl pause mda 之后重启服务,怎么又投递了?
pause 不持久化,restart 后三职能回到 running(5.1 排障实录)。重启后需重新 pause。
Q5:smtpctl profile bogus / smtpctl trace bogus 报错是什么口径?
两者都是 smtpctl: invalid <profile|trace> keyword: bogus、退出码 1。合法关键字:profile 仅 imsg/queue;trace 共 13 个(6.3 表)。
Q6:普通用户执行 smtpctl show queue 报 need root privileges?
特权命令模型(2.2)。用 root 或 sudo 执行;encrypt、spf walk 两个非特权命令除外。
Q7:老资料里的 smtpctl stop 怎么不可用?
7.9.0p0 已移除该子命令(8.3 实测,服务不受影响)。停止/重启一律走 systemd 单元。
Q8:想确认某封信卡在队列的原因,按什么顺序查?
show queue 看 runstate(pending 负值=欠调度,inflight=在投,suspend=被钉住)→ 第 13 列错误行 → show envelope <evpid> 看 mda-user/dest 是否解析对了 → 必要时 trace mda/trace mta 展开投递过程 → 修复后 schedule 提前重试或 remove 清理。
十一、参考链接
- smtpctl(8) 官方手册:https://man.openbsd.org/smtpctl
- smtpd(8) 官方手册:https://man.openbsd.org/smtpd
- OpenSMTPD 官方手册索引:https://www.opensmtpd.org/manual.html
- portable 版源码仓库(本文命令注册表与字段语义的取证来源):https://github.com/OpenSMTPD/OpenSMTPD
- 7.9.0p0 发布说明:https://github.com/OpenSMTPD/OpenSMTPD/releases/tag/7.9.0p0
- 站内参考:《OpenSMTPD 7.9.0p0 介绍与选型指南》、《Ubuntu 22.04 安装 OpenSMTPD 指南》、《OpenSMTPD 7.9 配置参数详解》、《Mox v0.0.17 介绍与选型指南》(同赛道邮件服务器)。