Maddy 0.9.6 日常运维手册

Maddy 0.9.6 日常运维手册全流程实测:systemd 巡检与资源基线、磁盘队列管理与重试机制、journald 日志排障与 msg_id 链路、TLS 证书热重载与握手探活、账号/邮箱/消息 CLI 管理、SQLite 热备与冷备恢复演练、openmetrics 监控端点能力边界,附队列闭环与 DSN 退信实测回显

Maddy邮件服务器2026/10/19

Maddy 0.9.6 日常运维手册

目标:面向已上线的 Maddy Mail Server 0.9.6 单实例部署,给出日常运维的完整操作面——服务巡检、队列管理、日志排障、TLS 证书运维、账号与存储管理、备份恢复、监控接入与常见故障处置。本文所有命令与回显均在 Ubuntu 22.04 + Maddy 0.9.6 实测通过(队列重试、备份恢复、改密、reload 均为真机操作实录),可直接照做。

一、环境信息

  • 操作系统:Ubuntu 22.04(systemd 249,journald 持久化开启)
  • Maddy 版本:0.9.6(maddy version → 0.9.6 linux/amd64 go1.27.1),安装见《Ubuntu 22.04 安装 Maddy 0.9.6 指南》
  • 配置文件:/etc/maddy/maddy.conf;数据目录:/var/lib/maddy(属主 maddy:maddy)
  • 测试域名约定:服务器 mail.lab.example.org、邮件域 lab.example.org;模拟远端 MX relayhost.mailtest.internal;本地 DNS 由 dnsmasq 模拟(见《Maddy 0.9.6 配置参数详解》4.7 节)
  • 测试账号:admin@lab.example.org、demo@lab.example.org,密码均 q1w2e3r4
  • 监控端点:openmetrics tcp://127.0.0.1:9749 { }(默认配置未含,本文已按 8.1 节启用)

二、服务巡检与资源基线

2.1 服务状态与端口巡检

每日巡检第一步:确认服务活跃与五端口监听。

systemctl is-active maddy
systemctl status maddy --no-pager | sed -n '1,10p'
ss -ltn | grep -E ':(25|465|587|143|993)\b'

实测输出:

active
● maddy.service - maddy mail server
     Loaded: loaded (/etc/systemd/system/maddy.service; enabled; vendor preset: enabled)
     Active: active (running) since Mon 2026-10-19 09:20:31 UTC; 3h 16min ago
       Docs: man:maddy(1)
             man:maddy.conf(5)
             https://maddy.email
   Main PID: 11773 (maddy)
     Status: "Configuration running."
      Tasks: 11 (limit: 5753)
     Memory: 15.6M
LISTEN 0      4096       127.0.0.1:25        0.0.0.0:*
LISTEN 0      4096               *:587             *:*
LISTEN 0      4096               *:993             *:*
LISTEN 0      4096               *:143             *:*
LISTEN 0      4096               *:465             *:*

巡检要点:Loaded ... enabled 确认开机自启;Status: "Configuration running." 是 Type=notify 服务的就绪标志;Memory 为 systemd 统计的服务内存(实测 15.6M 量级,含队列缓冲波动);端口表需与配置预期一致(本文 25 端口按测试约定绑定 127.0.0.1,生产应为 *:25)。

进程级资源基线(ps 视角的常驻 RSS,供容量规划参考):

ps -o pid,user,rss,etime,cmd -C maddy

实测输出(主实例 + 同机第二实例场景):

  PID USER       RSS     ELAPSED CMD
12027 maddy    27892       05:56 /usr/local/bin/maddy --config /etc/maddy/relay.conf run
12298 maddy    31640       00:09 /usr/local/bin/maddy run

单实例 RSS 约 28-31MB,属低资源常驻服务;多实例按配置文件区分(maddy@ 模板单元,见《Ubuntu 22.04 安装 Maddy 0.9.6 指南》8.3 节)。

2.2 版本与配置巡检

maddy version
maddy verify-config

实测输出:

0.9.6 linux/amd64 go1.27.1

default config: /etc/maddy/maddy.conf
default state_dir: /var/lib/maddy
default runtime_dir: /run/maddy
table.file: ignoring non-existent file: /etc/maddy/aliases
No errors detected

No errors detected 即配置健康;table.file: ignoring non-existent file 是别名文件未创建的提示性日志(见《Maddy 0.9.6 配置参数详解》4.4 节),不影响运行。版本巡检用于核对线上二进制与预期版本一致(升级流程见《Ubuntu 22.04 安装 Maddy 0.9.6 指南》8.1 节)。

2.3 账号与邮箱 CLI 巡检

Maddy 无 Web 管理台,账号/邮箱巡检走四个 CLI 子命令(子命令全集见 6.1 节):

maddy creds list
maddy imap-acct list
maddy imap-mboxes list admin@lab.example.org
maddy imap-msgs list admin@lab.example.org INBOX

实测输出:

admin@lab.example.org
demo@lab.example.org
admin@lab.example.org
demo@lab.example.org
INBOX   [\HasNoChildren]
Sent    [\Sent \HasNoChildren]
Trash   [\Trash \HasNoChildren]
Junk    [\Junk \HasNoChildren]
Drafts  [\Drafts \HasNoChildren]
Archive [\Archive \HasNoChildren]
UID 1:  <news@client.mailtest.internal> - Clean inbox delivery
  [\Recent], 2026-10-19 07:39:03 +0000 UTC

UID 2:  <MAILER-DAEMON@lab.example.org> - Undelivered Mail Returned to Sender
  [\Recent], 2026-10-19 07:39:21 +0000 UTC
...

巡检要点:creds list 与 imap-acct list 数量应一致(凭据与存储账户分离,只建凭据不建存储账户会造成"能登录但收不了信",见《Maddy 0.9.6 配置参数详解》4.2 节);imap-msgs list 是不登录客户端即可核对投递结果的快速手段;Undelivered Mail Returned to Sender(DSN 退信)数量突增通常意味着出站投递异常,应转入第三章队列排查。

2.4 数据目录巡检

du -sh /var/lib/maddy
ls -la /var/lib/maddy

实测输出:

272K    /var/lib/maddy
total 180
drwxr-xr-x 7 maddy maddy  4096 Oct 19 09:20 .
drwxr-xr-x 1 root  root   4096 Oct 19 07:55 ..
drwx------ 2 maddy maddy  4096 Oct 19 07:29 buffer
-rw-r--r-- 1 maddy maddy 12288 Oct 19 07:30 credentials.db
drwxr-xr-x 2 maddy maddy  4096 Oct 19 07:29 dkim_keys
-rw-r----- 1 maddy maddy 86016 Oct 19 09:20 imapsql.db
-rw-r----- 1 maddy maddy 32768 Oct 19 09:20 imapsql.db-shm
-rw-r----- 1 maddy maddy  4152 Oct 19 09:20 imapsql.db-wal
drwxr-xr-x 2 maddy maddy  4096 Oct 19 09:20 messages
drwxr-xr-x 2 maddy maddy  4096 Oct 19 07:29 mtasts_cache
drwxr-xr-x 2 maddy maddy  4096 Oct 19 09:20 remote_queue
路径用途运维关注点
credentials.db认证凭据库(SQLite)备份对象;增长与账号数线性相关
imapsql.db + -wal/-shmIMAP 索引与元数据(SQLite,WAL 模式)备份必须用 SQLite backup API(见 7.2 节),不可直接拷贝含 WAL 的库文件
messages/消息本体(blob,文件名即内容哈希)容量主体;丢失会导致投递 554(见 9.2 节案例)
dkim_keys/DKIM 私钥与 DNS 记录文件备份对象;丢失需重新发布 DNS 记录
remote_queue/出站磁盘队列队列积压观察点(见第三章)
mtasts_cache/MTA-STS 策略缓存可再生,无需备份
buffer/会话数据缓冲服务自管理

三、队列管理

Maddy 0.9.6 的 CLI 子命令中没有队列管理命令(maddy --help 实查:verify-config / run / version / hash / imap-mboxes / imap-msgs / imap-acct / creds),队列的可观测与可操作面是"目录 + 日志 + DSN"三件套。

3.1 队列磁盘布局与 meta 解读

出站队列位于 /var/lib/maddy/remote_queue/,每封滞留消息三个文件(<msg_id> 为 8 位十六进制消息 ID):

  • <msg_id>.header:完整消息头(DKIM 签名已在入队前完成,实测 header 首行即 DKIM-Signature:)
  • <msg_id>.body:消息体
  • <msg_id>.meta:投递状态 JSON

实测制造一次队列滞留(防火墙 DROP 对端 MX 的出站流量,提交一封经 587 认证的外域信)后,msg.meta 原文:

{"MsgMeta":{"ID":"591a074f","OriginalFrom":"admin@lab.example.org","DontTraceSender":true,"Quarantine":false,"OriginalRcpts":{},"SMTPOpts":{"Body":"","Size":0,"RequireTLS":false,"UTF8":false,"Return":"","EnvelopeID":"","Auth":null},"Conn":null,"TLSRequireOverride":false},"From":"admin@lab.example.org","To":["relayuser@mailtest.internal"],"FailedRcpts":null,"TemporaryFailedRcpts":null,"RcptErrs":{},"TriesCount":null,"FirstAttempt":"2026-10-19T12:38:36.938212519Z","LastAttempt":"2026-10-19T12:40:50.903972862Z"}

运维关注字段:From/To(积压主体)、TriesCount(按收件人计的尝试次数,滞留后变为 {"relayuser@mailtest.internal":1})、FirstAttempt/LastAttempt(滞留时长计算)、FailedRcpts(已永久失败的收件人)。巡检脚本可按 TriesCount 是否非空筛出"重试中"的积压。

3.2 临时失败与永久失败:行为对照(实测)

同一收件域两种故障形态的日志对照(journalctl -u maddy 实测原文):

形态一:网络不可达(DROP 丢包)→ 临时失败 → 自动重试。

cannot use MX        {"domain":"mailtest.internal","msg_id":"591a074f-6ad60f4c","reason":"dialer: dial tcp 127.0.0.2:25: connect: connection timed out","remote_server":"relayhost.mailtest.internal."}
delivery attempt failed        {"domain":"mailtest.internal","msg_id":"591a074f","rcpt":"relayuser@mailtest.internal","reason":"dialer: dial tcp 127.0.0.2:25: connect: connection timed out","remote_server":"relayhost.mailtest.internal.","smtp_code":451,"smtp_enchcode":"5.4.0","smtp_msg":"No usable MXs, last err: dialer: dial tcp 127.0.0.2:25: connect: connection timed out","target":"remote"}
will retry        {"attempts_count":{"relayuser@mailtest.internal":1},"msg_id":"591a074f","next_try_delay":"14m59.999998332s","rcpts":["relayuser@mailtest.internal"]}

形态二:端口无监听(connection refused)→ 永久失败 → 立即 DSN 退信。

cannot use MX        {"domain":"mailtest.internal","msg_id":"cac1b963-6ad60f1a","reason":"dialer: dial tcp 127.0.0.3:25: connect: connection refused","remote_server":"relayhost.mailtest.internal."}
delivery attempt failed        {"domain":"mailtest.internal","msg_id":"cac1b963","rcpt":"relayuser@mailtest.internal","reason":"dialer: dial tcp 127.0.0.3:25: connect: connection refused","remote_server":"relayhost.mailtest.internal.","smtp_code":550,"smtp_enchcode":"5.4.0","smtp_msg":"No usable MXs, last err: dialer: dial tcp 127.0.0.3:25: connect: connection refused","target":"remote"}
not delivered, permanent error        {"msg_id":"cac1b963","rcpt":"relayuser@mailtest.internal"}
generated failed DSN        {"dsn_id":"eee7af5a","msg_id":"cac1b963"}

对照结论:

维度connection timed outconnection refused
SMTP 语义451 临时失败(smtp_code=451)550 永久失败(smtp_code=550)
队列行为留队重试,will retry 声明下次尝试延迟(实测首次约 15 分钟)不留队,直接 generated failed DSN 退信
运维处置排查到对端的网络/对端服务恢复即可,无需干预检查收件域 MX 记录是否指向真实在线的主机

⚠️ 注意:MX 记录指向错误主机是"永久失败退信"的常见根因。本文实测中模拟远端 MX 的 DNS 记录(127.0.0.3)与实际监听地址(127.0.0.2)不一致时,出站立即以 connection refused 被判永久失败并退信——生产环境修改 MX 记录前务必核对目标主机的 25 端口可达性。

3.3 重试计划与重启行为

实测确认两个关键行为:

  1. 重启不重置重试计划。 临时失败的 next_try_delay 持久化在 meta 中,服务重启后日志显示 target.queue: loaded 1 saved queue entries(队列从磁盘恢复),但重试仍按原计划时间执行——实测 12:43:34 重启服务后,消息直到 12:55:51(首次尝试 + 15 分钟)才发起重试。因此"重启服务来加速队列"无效,加速出站的唯一办法是修复对端可达性。
  2. 重试到期后自动投递。 实测 12:55:51 到期重试的完整日志(对端当时有另一个故障,见 9.2 节):
TLS verify error, trying without authentication        {"domain":"mailtest.internal","msg_id":"591a074f-6ad61356","reason":"tls: failed to verify certificate: x509: certificate relies on legacy Common Name field, use SANs instead","remote_server":"relayhost.mailtest.internal."}
delivery attempt failed        {"msg_id":"591a074f","rcpt":"relayuser@mailtest.internal","reason":"SMTP error 554: Internal server error (msg ID = 445bdec1)","remote_server":"relayhost.mailtest.internal.","smtp_code":554,"smtp_enchcode":"5.0.0","smtp_msg":"relayhost.mailtest.internal. said: Internal server error (msg ID = 445bdec1)"}
not delivered, permanent error        {"msg_id":"591a074f","rcpt":"relayuser@mailtest.internal"}
generated failed DSN        {"dsn_id":"c327fa46","msg_id":"591a074f"}

本次重试收到的对端 554 属永久错误,消息出队并生成 DSN,remote_queue/ 目录随即清空(ls | wc -l 实测为 0)。首次重试延迟实测约 15 分钟;后续重试按官方 target.queue 的退避策略逐步延长(重试策略调参见官方文档,参考链接一节)。

3.4 DSN 退信解读

DSN 自动投回原发件人信箱(对应配置 target.queue 的 bounce 块),maddy imap-msgs dump 导出实测(节选):

Subject: Undelivered Mail Returned to Sender
From: MAILER-DAEMON@lab.example.org
To: admin@lab.example.org
Auto-Submitted: auto-replied

This is the mail delivery system at mail.lab.example.org.
...
Contact the postmaster for further assistance, provide the Message ID (below):

Message ID: 591a074f
Arrival: 2026-10-19 12:38:36 +0000 UTC
Last delivery attempt: 2026-10-19 12:40:50 +0000 UTC

Delivery to relayuser@mailtest.internal failed with error: SMTP error 554: relayhost.mailtest.internal. said: Internal server error (msg ID = 445bdec1)

Last-Attempt-Date: Mon, 19 Oct 2026 12:40:50 +0000
Arrival-Date: Mon, 19 Oct 2026 12:38:36 +0000
X-Maddy-Msgid: 591a074f

排障入口即 Message ID / X-Maddy-Msgid:拿它回 journalctl 检索本端全链路日志(见 4.2 节),对端 554 时再向对端管理员提供其 msg ID。

3.5 队列清理

无 CLI 子命令,文件级操作(先停服避免与运行中队列状态竞争):

systemctl stop maddy
ls /var/lib/maddy/remote_queue/
rm /var/lib/maddy/remote_queue/<msg_id>.{header,body,meta}   # 清指定消息
# 或 rm /var/lib/maddy/remote_queue/*                          # 清空全部
systemctl start maddy

启动日志中的 target.queue: loaded N saved queue entries 计数应与保留文件数一致(实测 loaded 1 saved queue entries)。生产慎用全清——更好的做法是先按 3.1 节分析 meta 确认积压原因。

四、日志排障

4.1 日志承载:journald 单一出口

Maddy 全部日志走 stdout 由 systemd 接管进 journald,不写独立日志文件。实测 /var/log/maddy/ 目录存在但为空(安装时创建的惯例目录),日志查询统一用:

journalctl -u maddy --no-pager                      # 全量
journalctl -u maddy -n 50 --no-pager                # 最近 50 行
journalctl -u maddy --since "-15 min" --no-pager    # 最近 15 分钟
journalctl -u maddy -f                              # 跟踪

journald 持久化需 /var/log/journal 存在(Ubuntu 22.04 默认开启,实测在);若系统为 volatile 模式,重启后历史日志丢失,排障前先确认。

4.2 msg_id 链路检索法

每封消息从入站到出站的所有日志行都携带 msg_id(投递子尝试带 -<8位> 后缀,如 cac1b963-6ad60f1a)。排障标准动作是按 msg_id 检索全链路:

journalctl -u maddy --no-pager --since "30 minutes ago" | grep "cac1b963"

实测输出(一封永久失败信的完整生命周期):

submission: incoming message        {"msg_id":"cac1b963","sender":"admin@lab.example.org","src_host":"mail.lab.example.org","src_ip":"127.0.0.1:38852","username":"admin@lab.example.org"}
submission: RCPT ok        {"msg_id":"cac1b963","rcpt":"relayuser@mailtest.internal"}
submission: accepted        {"msg_id":"cac1b963"}
cannot use MX        {"domain":"mailtest.internal","msg_id":"cac1b963-6ad60f1a","reason":"dialer: dial tcp 127.0.0.3:25: connect: connection refused","remote_server":"relayhost.mailtest.internal."}
delivery attempt failed        {"domain":"mailtest.internal","msg_id":"cac1b963",...,"smtp_code":550,...}
not delivered, permanent error        {"msg_id":"cac1b963","rcpt":"relayuser@mailtest.internal"}

六行日志依次呈现:认证提交 → 收件人通过 → 入队 → MX 拨测失败 → 投递失败(含错误码与原因) → 终局判定。对入站问题同样适用(来信 msg_id 可从拒收回执 550 5.7.1 DMARC check failed (msg ID = ...) 中取得)。

4.3 关键日志事件字典

日志事件含义处置
smtp: listening on tcp://... / submission: listening on ... / imap: listening on ...端点启动监听(启动/重载时逐端口输出)与端口规划核对
server started {"version":"0.9.6"}服务就绪正常
target.queue: loaded N saved queue entries启动时从磁盘恢复队列N 与 remote_queue/ 文件数/3 对账
submission: accepted / smtp: accepted消息接收入队正常
delivered {"attempt":1,...}出站投递成功正常
quarantined {"check":...}检查未通过被隔离进收件人 Junk按 check 字段定位策略(require_mx_record/spf 等)
smtp: DATA error {"check":"dmarc",...}入站 DMARC 拒收确认为防伪造行为即无碍
delivery attempt failed {...,"smtp_code":4xx,...} + will retry临时失败,留队重试观察对端恢复,无需干预
not delivered, permanent error + generated failed DSN永久失败并退信按 reason/MX 记录排查
TLS verify error, trying without authentication对端证书校验失败降级生产对端应部署含 SAN 的有效证书
storage.imapsql/updpipe: failed to remove socket停服竞态的无害日志忽略(实测 stop 时偶现)

4.4 日志轮转与保留

journald 的保留策略由 systemd-journald 统一管理(/etc/systemd/journald.conf 的 SystemMaxUse 等),Maddy 侧无需配置轮转。需要长期留存的合规场景,用 journalctl -u maddy --since ... > 存档文件 导出即可。

五、TLS 证书运维

5.1 有效期巡检

openssl x509 -in /etc/maddy/certs/mail.lab.example.org/fullchain.pem -noout -subject -dates

实测输出(自签证书):

subject=CN = mail.lab.example.org
notBefore=Oct 19 07:29:14 2026 GMT
notAfter=Oct 19 07:29:14 2027 GMT

巡检脚本建议对 notAfter 做 30 天阈值告警。Let's Encrypt 证书 90 天有效期,续期联动见 5.4 节。

5.2 热重载(不中断服务)

Maddy 每分钟自动重读磁盘证书(官方教程明确),证书续期后可零操作;也可手动触发:

systemctl reload maddy

实测日志(unit 的 ExecReload=/bin/kill -USR2 $MAINPID):

reloading server...
submission: listening on tls://0.0.0.0:465
imap: listening on tls://0.0.0.0:993
systemd[1]: Reloaded maddy mail server.

reloading server... 后端点重新广播监听,既有连接不中断。注意 reload 的生效范围是 TLS 证书等运行时资源;监听地址、模块实例变更仍需 systemctl restart maddy(见《Maddy 0.9.6 配置参数详解》第二章)。

5.3 握手探活

把 TLS 巡检做成端口级探活(不依赖邮件客户端):

echo | openssl s_client -connect 127.0.0.1:993 2>/dev/null | grep -E 'subject=|Protocol|Verify return code'
echo | openssl s_client -starttls smtp -connect 127.0.0.1:587 2>/dev/null | grep -E 'subject=|Verify return code'

实测输出:

subject=CN = mail.lab.example.org
New, TLSv1.3, Cipher is TLS_AES_128_GCM_SHA256
Verify return code: 18 (self-signed certificate)
subject=CN = mail.lab.example.org
Verify return code: 18 (self-signed certificate)

993(隐式 TLS)与 587(STARTTLS)双路握手均应成功返回协议与证书 subject。Verify return code: 18 (self-signed certificate) 在自签环境属预期;生产证书应返回 0 (ok)——若仍为 18/20/21 类错误,检查证书链是否完整(fullchain 是否包含了中间证书)。

5.4 Let's Encrypt 续期联动

certbot 续期后需要让 Maddy 读到新证书。由于 Maddy 每分钟自动重读磁盘证书,理论上无需钩子;稳妥做法是加 deploy-hook 立即触发 reload:

ln -s /etc/letsencrypt/live/mail.lab.example.org /etc/maddy/certs/mail.lab.example.org
setfacl -R -m u:maddy:rX /etc/letsencrypt/live /etc/letsencrypt/archive
certbot renew --deploy-hook "systemctl reload maddy"

证书目录权限是证书运维的最高频故障点(maddy 永不以 root 运行,证书必须对 maddy 用户可读,见《Ubuntu 22.04 安装 Maddy 0.9.6 指南》7.2 节)。

六、账号与存储运维

6.1 CLI 子命令全集

maddy --help 实查的管理子命令分组(运维操作均落在这些子命令上):

COMMANDS:
   verify-config  Check configuration file for errors
   run            Start the server
   version        Print version and build metadata, then exit
   hash           Generate password hashes for use with pass_table
   imap-mboxes    IMAP mailboxes (folders) management
   imap-msgs      IMAP messages management
   imap-acct      IMAP storage accounts management
   creds          Local credentials management

各组二级子命令(maddy creds --help 等实测):

组二级子命令用途
credslist / create / remove / password认证凭据管理
imap-acctlist / create / remove / appendlimit存储账户管理(appendlimit 即配额)
imap-mboxeslist / create / remove / rename文件夹管理
imap-msgsadd / add-flags / rem-flags / set-flags / remove / copy / move / list / dump消息级管理

6.2 密码修改与账号移除

改密用 creds password,默认从 stdin 读密码(避免密码进 shell 历史):

printf '%s' 'q1w2e3r4' | maddy creds password demo@lab.example.org

实测输出(管道场景的无害告警 + 退出码):

table.file: ignoring non-existent file: /etc/maddy/aliases
Failed to disable terminal output: TurnOnRawIO: failed to get flags: inappropriate ioctl for device
Enter new password: exit=0

改密后立即验证登录(IMAP 993 实测 login OK)。帮助文本明确 --password/-p 仅作调试用途(会留在 shell 历史),生产一律走 stdin 或交互输入。

账号移除(凭据与存储账户分别删,用户名是完整邮箱地址):

maddy creds remove --yes <用户名>
maddy imap-acct remove --yes <用户名>

⚠️ 注意:creds remove 与 imap-acct remove 均无回收站;删除存储账户前先用 7.x 节备份,或用 imap-msgs move/copy 迁移该账号重要邮件。删除凭据但保留存储账户时,该邮箱只收不发(无法登录客户端)。

6.3 存储配额(appendlimit)

maddy imap-acct appendlimit admin@lab.example.org
maddy imap-acct appendlimit admin@lab.example.org 52428800   # 设置 50MB(字节)

appendlimit 控制该账户 IMAP APPEND 的单消息上限,是 0.9.6 提供的账户级配额手段;空值表示不限制。批量账户的配额巡检可脚本化遍历 imap-acct list 输出。

6.4 消息级操作与故障注入

imap-msgs 的 move/copy/remove/dump 组合可直接做服务端邮件处置(无需客户端):抽样导出原文排障(dump)、清理超大附件(list 定位 → remove)、跨文件夹归档(move)。add 子命令可向指定邮箱注入测试邮件,配合 25/587 端口排障做"旁路投递"验证(存储侧正常与否一测便知)。

七、备份与恢复

7.1 备份对象清单

对象路径备份方式
认据库/var/lib/maddy/credentials.dbSQLite backup API(在线)或停服拷贝
IMAP 索引库/var/lib/maddy/imapsql.db(+WAL/SHM)SQLite backup API(在线),不可直接 cp(WAL 模式下热拷贝主文件会丢已提交事务)
消息本体/var/lib/maddy/messages/tar/rsync(纯文件)
DKIM 私钥/var/lib/maddy/dkim_keys/tar/rsync(丢失需重新生成并改 DNS)
配置/etc/maddy/(maddy.conf、aliases、certs)tar/rsync
可再生项mtasts_cache/、buffer/不备份

7.2 在线热备(不停服)

SQLite 官方 backup API 在线导出双库,再做完整性校验(实测通过):

mkdir -p /tmp/maddy-bak
python3 - <<'PYEOF'
import sqlite3, os
for db in ["imapsql.db", "credentials.db"]:
    src = sqlite3.connect(f"/var/lib/maddy/{db}")
    dst = sqlite3.connect(f"/tmp/maddy-bak/{db}")
    src.backup(dst); dst.close(); src.close()
    chk = sqlite3.connect(f"/tmp/maddy-bak/{db}").execute("PRAGMA integrity_check").fetchone()[0]
    print(db, "backup+integrity:", chk)
PYEOF

实测输出:

imapsql.db backup+integrity: ok
credentials.db backup+integrity: ok

随后打包纯文件部分(消息本体、DKIM 密钥、缓存目录):

tar -C /var/lib/maddy -czf /tmp/maddy-bak/files.tgz messages dkim_keys mtasts_cache
tar -C /etc -czf /tmp/maddy-bak/etc.tgz maddy

实测 files.tgz 内含消息 blob(messages/<内容哈希>)与 dkim_keys/lab.example.org_default.key/.dns。热备全程服务保持 active,对在线投递无影响(SQLite backup API 的一致性由引擎保证)。

7.3 冷备(停服全量)

维护窗口内的最简单方案——停服打包整个数据目录:

systemctl stop maddy
tar -C /var/lib -czf /tmp/maddy-bak/cold-full.tgz maddy
systemctl start maddy

实测输出(本测试环境数据量小,供方法参考):

cold backup: 20K 28 entries

冷备包内含 SQLite 双库 + WAL + messages + dkim_keys + remote_queue,恢复即整体回滚到该时点。生产环境建议"每日热备 + 每周冷备"组合。

7.4 恢复演练(实测通过)

备份的价值在可恢复。以下演练流程实测执行(停服 → 移走现目录 → 解包备份 → 起服验证):

systemctl stop maddy
mv /var/lib/maddy /var/lib/maddy.pre-restore
tar -C /var/lib -xzf /tmp/maddy-bak/cold-full.tgz
chown -R maddy:maddy /var/lib/maddy
systemctl start maddy

恢复后验证(实测输出):

active
== creds list ==
admin@lab.example.org
demo@lab.example.org
== admin INBOX 消息数 ==
9
== 队列文件数 ==
3

验证点:服务 active、账号清单还原、INBOX 消息数与备份时点一致(9 封)、滞留队列文件随备份还原。演练通过后删除 /var/lib/maddy.pre-restore。

⚠️ 注意:属主是恢复演练的常见翻车点——tar 解包后务必 chown -R maddy:maddy /var/lib/maddy,否则服务启动报 permission denied 或 attempt to write a readonly database(见《Ubuntu 22.04 安装 Maddy 0.9.6 指南》FAQ)。

7.5 备份策略建议

八、监控指标(openmetrics / Prometheus)

8.1 端点启用

默认配置不含监控端点,在 /etc/maddy/maddy.conf 追加一行模块实例后重启:

openmetrics tcp://127.0.0.1:9749 { }
maddy verify-config && systemctl restart maddy
journalctl -u maddy -n 5 --no-pager | grep openmetrics

实测输出:

openmetrics: listening on tcp://127.0.0.1:9749

端点仅监听本机回环(生产建议维持 127.0.0.1,由 Prometheus 本机 exporter 或抓取代理转发,避免指标面暴露公网)。

8.2 指标抓取与内容实测

curl -s http://127.0.0.1:9749/metrics

实测概览:全量输出 128 行、48 个指标样本,全部为 Go 运行时与进程指标——go_gc_duration_seconds、go_goroutines、go_memstats_*、go_info{version="go1.27.1"}、process_*、promhttp_metric_handler_requests_total。节选:

# HELP go_goroutines Number of goroutines that currently exist.
# TYPE go_goroutines gauge
go_goroutines 26
# HELP go_info Information about the Go environment.
# TYPE go_info gauge
go_info{version="go1.27.1"} 1

8.3 能力边界(如实说明)

对指标名做业务关键词检索(实测):

curl -s http://127.0.0.1:9749/metrics | grep -iE 'queue|smtp|delivery|imap|dkim|msg'

实测无任何匹配行——0.9.6 的 openmetrics 端点未暴露 Maddy 业务级指标(无队列深度、无收发计数、无 IMAP 会话数)。因此业务监控的实践方案是"指标 + 日志 + 文件系统"三路合成:

监控项采集方式告警建议
服务存活/端口黑盒探活(TCP 25/465/587/143/993)+ TLS 握手探测(5.3 节)任一端口不可达即告警
进程资源process_* / go_memstats_* 指标 + systemd MemoryRSS 持续倍增告警
队列积压cron 巡检 `ls /var/lib/maddy/remote_queue/wc -l`(除以 3 即滞留消息数)
投递失败journalctl 抓 not delivered, permanent error / delivery attempt failed 频次频次突增告警
账号/邮箱规模maddy creds list / imap-acct list 行数周报级巡检

Prometheus 抓取配置示例:

scrape_configs:
  - job_name: maddy
    static_configs:
      - targets: ['mail.example.com:9749']

抓到的 go_*/process_* 指标可直接入《Prometheus 3.15.0 介绍与选型指南》/《Grafana 13.2.4 介绍与选型指南》看板(进程存活、内存、GC),业务面指标按上表以脚本采集,或接《Grafana 13.2.4 介绍与选型指南》的日志数据源做 journal 关键字告警。

九、常见故障排查(实测案例)

Q1:出站投递持续重试不成功?

三步定位:① journalctl -u maddy | grep "will retry" 看重试计划与 next_try_delay;② 按 msg_id 检索完整失败原因(4.2 节),区分 timed out(网络/对端不在线)与业务拒信(如 SMTP error 554);③ 有对端 msg ID 时联系对端管理员查其日志。记住:重启本机服务不会提前重试(3.3 节实测),修复对端可达性才是解法。

Q2:对端报 554 Internal server error,本端看起来一切正常?

这是对端的故障,但根因模式值得收藏:实测第二实例曾因 blob 目录缺失,收信时在 DATA 阶段报错——

smtp: DATA error        {"msg_id":"445bdec1","reason":"external: open messages/0142468c36c9a8ec6a736c2f1725b7ce: no such file or directory"}

messages/ 目录丢失或属主错误时,索引库写入成功而消息本体落盘失败,对外表现就是 554。修复:mkdir -p /var/lib/maddy/messages && chown -R maddy:maddy /var/lib/maddy。本端出现同样日志时按同法处理。

Q3:connection refused 与 connection timed out 都出现在 MX 拨测,处置有何不同?

见 3.2 节对照表:refused 是永久失败立即退信(查 MX 记录指向),timed out 是临时失败留队重试(等网络恢复)。生产中最常见的 refused 根因是 MX 记录指向了已下线或未监听 25 端口的主机。

Q4:来信进 Junk 而不是被拒收,是故障吗?

不是。检查链未通过的默认动作是 quarantine(投进收件人 Junk),日志特征 quarantined {"check":"require_mx_record",...};只有 DMARC 执行到 p=reject 才当场拒收(smtp: DATA error {"check":"dmarc",...})。两者都是防伪造策略生效的表现(回显详见《Maddy 0.9.6 配置参数详解》4.5 节实测一、二)。

Q5:恢复备份后服务起不来?

九成是属主:chown -R maddy:maddy /var/lib/maddy 后再 systemctl reset-failed maddy && systemctl start maddy。连续失败 5 次后 systemd 以 Start request repeated too quickly 拒绝启动,必须先 reset-failed(见《Ubuntu 22.04 安装 Maddy 0.9.6 指南》FAQ Q1)。

Q6:出站日志反复出现 x509: certificate relies on legacy Common Name field?

对端证书只有 CN 没有 SAN 扩展(自签证书常见),Go TLS 校验告警后降级继续。不影响 Maddy 间互通,但生产对端应换含 SAN 的证书(自签生成时加 -addext "subjectAltName=DNS:<主机名>" 可消除,见《Maddy 0.9.6 配置参数详解》FAQ Q7)。

Q7:停服时日志报 storage.imapsql/updpipe: failed to remove socket ... no such file or directory?

停服竞态的无害日志(实测 systemctl stop maddy 时偶现),socket 已被先行清理,服务正常停止,无需处理。

十、参考链接