Dovecot 2.3.16 快速入门与 Postfix 集成示例
从零安装 Postfix 3.6.4 与 Dovecot 2.3.16 并打通本机真实收发闭环——LMTP 投递套接字配置、auth_username_format=%n 排障、sendmail/mail 发信、Maildir 落盘核对、IMAP 收取与 doveadm 查询,附 LDA 备选方案实测。
Dovecot 2.3.16 快速入门与 Postfix 集成示例
目标:在一台 Ubuntu 22.04 服务器上从零安装 Postfix + Dovecot,把 Postfix 的本地投递切到 Dovecot LMTP,实现「发送测试邮件 → Maildir 落盘 → IMAP 收取」的完整真实闭环。全部命令与回显已在 Ubuntu 22.04.5 LTS(Postfix 3.6.4 + Dovecot 2.3.16)实测通过。
一、环境信息
本篇所有命令均在以下环境中逐条实测:
- 操作系统:Ubuntu 22.04.5 LTS(x86_64,内核 6.8.0-138-generic)
- 软件版本:Postfix 3.6.4-1ubuntu1.4、Dovecot 2.3.16 (7e2e900c1a)、dovecot-lmtpd 1:2.3.16+dfsg1-3ubuntu2.9、bsd-mailx 8.1.2-0.20180807cvs-2build2
- 服务器主机名:mail.example.com(邮件域名同名)
- 测试用户:mailuser(系统用户,密码 q1w2e3r4,仅演示)
- 关键路径:Postfix 配置
/etc/postfix/main.cf;Dovecot 配置/etc/dovecot/conf.d/;LMTP 套接字/var/spool/postfix/private/dovecot-lmtp;邮件存储maildir:~/Maildir - 本篇收发测试全部在服务器本机(回环)完成,不涉及对外公网投递
二、概览:Postfix 与 Dovecot 的分工
一套最小可用的 Linux 邮件系统由两类进程组成:
- MTA(Mail Transfer Agent,邮件传输代理):Postfix。负责接收外部邮件、路由转发,并把邮件投递到用户邮箱;
- MDA/邮件存储与访问:Dovecot。负责管理邮箱存储(Maildir),对外提供 IMAP/POP3 读取,同时可扮演投递代理角色。
Postfix 本身自带的投递方式是直接写 mbox 文件(postfix local 投递代理);与 Dovecot 集成后,投递交给 Dovecot 执行,从而获得 Maildir、配额、Sieve 过滤、索引等能力。
💡 技术拓展:Postfix 交给 Dovecot 投递有两条主流通道,对比——
通道 原理 特点 LMTP(本篇主用) Postfix 把邮件投给 Dovecot 常驻的 lmtp 服务(Unix 套接字),每收件人一次会话 支持每收件人独立状态回报(部分成功部分退信)、配额逐人精确生效,官方推荐 LDA(dovecot-lda) Postfix 通过 mailbox_command调起一次性 dovecot-lda 进程投递配置直观(一条 main.cf),但每个收件人 fork 一次进程,且逐收件人状态回报能力弱于 LMTP 本文主用 LMTP(Dovecot 官方推荐,进程常驻、开销小),并附 LDA 完整实测作为备选。
三、前置条件与安装
3.1 前置条件
- Ubuntu 22.04 服务器,root 权限;
- 主机名已设置为邮件域名(本篇
mail.example.com); - 已放行本地回环发信(本篇全部测试走本机,无需对公网开放 SMTP;若后续要对外收信,需在防火墙放行 25 端口并配置 MX 记录)。
3.2 安装 Postfix、Dovecot 与发信工具
DEBIAN_FRONTEND=noninteractive apt-get install -y postfix dovecot-core dovecot-imapd dovecot-pop3d dovecot-lmtpd bsd-mailx
实测输出(节选):
Setting up postfix (3.6.4-1ubuntu1.4) ...
Adding group `postdrop' (gid 108) ...
Setting up dovecot-core (1:2.3.16+dfsg1-3ubuntu2.9) ...
Creating config file /etc/dovecot/conf.d/20-lmtp.conf with new version
Setting up dovecot-imapd (1:2.3.16+dfsg1-3ubuntu2.9) ...
Setting up dovecot-pop3d (1:2.3.16+dfsg1-3ubuntu2.9) ...
Setting up bsd-mailx (8.1.2-0.20180807cvs-2build2) ...
Processing triggers for dovecot-core (1:2.3.16+dfsg1-3ubuntu2.9) ...
💡 说明:
postfix安装时会弹出交互式配置面板选择「邮件服务器类型」,选Internet Site并填入邮件域名(mail.example.com)即可;DEBIAN_FRONTEND=noninteractive时默认也是该选项。bsd-mailx提供dovecot-lmtpd是 LMTP 协议的独立包(Ubuntu 特有的拆包方式,CentOS 无此拆分)。
3.3 确认版本与 Postfix 基线配置
postconf mail_version && dovecot --version
实测输出:
mail_version = 3.6.4
2.3.16 (7e2e900c1a)
postconf myhostname mydestination mailbox_transport mailbox_command
实测输出(安装后的默认值):
myhostname = mail.example.com
mydestination = $myhostname, mail.example.com, localhost.example.com, , localhost
mailbox_transport =
mailbox_command =
💡 说明:
mydestination列出 Postfix 认作「本地域」的域名清单——发往这些域名的邮件走本地投递;mailbox_transport与mailbox_command均为空,说明当前本地投递走 Postfix 自带 local 代理(直接写 /var/mail mbox),这正是我们接下来要改造的地方。
四、创建测试用户
Dovecot 默认用系统用户做认证与投递归属,创建收信测试用户:
useradd -m -s /bin/bash mailuser
echo 'mailuser:q1w2e3r4' | chpasswd
id mailuser
实测输出:
uid=1000(mailuser) gid=1000(mailuser) groups=1000(mailuser)
五、配置 Dovecot(存储 + LMTP 套接字)
5.1 配置邮件存储为 Maildir
编辑 /etc/dovecot/conf.d/10-mail.conf 第 30 行,把默认 mbox 改为 Maildir:
sed -i 's|^mail_location = mbox:~/mail:INBOX=/var/mail/%u|mail_location = maildir:~/Maildir|' /etc/dovecot/conf.d/10-mail.conf
grep -n '^mail_location' /etc/dovecot/conf.d/10-mail.conf
实测输出:
30:mail_location = maildir:~/Maildir
💡 说明:
%u是完整用户名变量,~为用户家目录。若不改这条,邮件会按默认 mbox 落到/var/mail/mailuser文件(实测如此,见第六章的旁证),与 Maildir 生态(配额、Sieve、索引)不兼容。
5.2 开放 LMTP 套接字给 Postfix
Postfix 以 chroot 方式运行投递进程,Dovecot 的 LMTP 套接字必须放在 /var/spool/postfix/private/ 下 Postfix 才能访问。编辑 /etc/dovecot/conf.d/10-master.conf,把 service lmtp 块改为:
service lmtp {
unix_listener /var/spool/postfix/private/dovecot-lmtp {
mode = 0600
user = postfix
group = postfix
}
}
逐项说明:unix_listener 指定套接字路径(放 Postfix chroot 目录内,Postfix 内部引用名即为 private/dovecot-lmtp);mode = 0600 仅属主可读写,不放开其他用户;user/group = postfix 把套接字属主设为 postfix——Postfix 的 lmtp 客户端进程以 postfix 用户连接,属主不匹配会报权限拒绝。
sed -i '54,65c\service lmtp {\n unix_listener /var/spool/postfix/private/dovecot-lmtp {\n mode = 0600\n user = postfix\n group = postfix\n }\n}' /etc/dovecot/conf.d/10-master.conf
grep -A6 '^service lmtp' /etc/dovecot/conf.d/10-master.conf
实测输出:
service lmtp {
unix_listener /var/spool/postfix/private/dovecot-lmtp {
mode = 0600
user = postfix
group = postfix
}
}
💡 说明:Ubuntu 的
10-master.conf默认自带一个service lmtp块(本地套接字unix_listener lmtp),直接整块替换成上面的内容即可。行号54,65对应实测版本的默认文件,编辑前先grep -n 'service lmtp' /etc/dovecot/conf.d/10-master.conf定位自己的行号范围。
5.3 认证用户名格式:去掉域名部分
Postfix 通过 LMTP 投递时,收件人会以完整邮件地址(如 mailuser@mail.example.com)传给 Dovecot;而系统用户库里的用户名是 mailuser。需要设置 auth_username_format = %n 让 Dovecot 截掉域名部分再查用户库。编辑 /etc/dovecot/conf.d/10-auth.conf 第 51 行:
sed -i 's|^#auth_username_format = %Lu|auth_username_format = %n|' /etc/dovecot/conf.d/10-auth.conf
grep -n '^auth_username_format' /etc/dovecot/conf.d/10-auth.conf
实测输出:
51:auth_username_format = %n
⚠️ 注意:这是本篇最容易被漏掉、也最难排查的一步。不设置它时的真实故障现象与定位过程见 8.2 排障实录。
5.4 重启服务并核对 LMTP 套接字
systemctl restart dovecot
systemctl restart postfix
systemctl is-active dovecot
systemctl is-active postfix
ls -l /var/spool/postfix/private/dovecot-lmtp
实测输出:
active
active
active
active
srw------- 1 postfix postfix 0 Oct 16 15:26 /var/spool/postfix/private/dovecot-lmtp
💡 说明:
doveconf -h protocols此时应显示imap lmtp pop3——dovecot-lmtpd包安装时通过/usr/share/dovecot/protocols.d/机制自动把 lmtp 加入协议列表,无需手工改 protocols。套接字srw-------且属主 postfix 即符合预期。
六、配置 Postfix 走 Dovecot LMTP 投递
6.1 切换本地投递通道
postconf -e 'mailbox_transport = lmtp:unix:private/dovecot-lmtp'
postconf mailbox_transport
实测输出:
mailbox_transport = lmtp:unix:private/dovecot-lmtp
💡 说明:
lmtp:unix:private/dovecot-lmtp三个字段——传输类型lmtp;套接字类型unix;路径private/dovecot-lmtp(相对 Postfix 队列目录/var/spool/postfix/,即 5.2 节创建的套接字)。postconf -e直接改写/etc/postfix/main.cf,等效于手工编辑文件。改完systemctl reload postfix或重启生效(本篇在 5.4 已整体重启)。
6.2 投递链路核对
改造后的完整链路:
mail 命令/sendmail → Postfix 队列(qmgr)→ mailbox_transport(lmtp:unix:private/dovecot-lmtp)
→ Dovecot lmtp 服务 → 校验收件人(userdb)→ 写入 ~/Maildir → 用户经 IMAP/POP3 读取
七、真实收发闭环测试
7.1 用 mail 命令发送第一封测试邮件
echo 'Hello via LMTP to Maildir' | mail -s 'Test Mail Maildir' mailuser
sleep 3
ls -l /home/mailuser/Maildir/new/
实测输出:
total 4
-rw------- 1 mailuser mailuser 712 Oct 16 15:27 1792164479.M820903P12374.mail.example.com,S=712,W=731
💡 说明:
new/目录里的文件名是标准 Maildir 命名——<时间戳>.<微秒>M<微秒>P<进程号>.<主机名>,S=<字节数>,W=<线宽字节>。文件属主是 mailuser、权限 0600,与 Dovecot 的最小权限存储策略一致。
7.2 查看投递日志(每一步的凭证)
journalctl --no-pager -n 30 | grep -iE 'postfix|lmtp' | tail -5
实测输出:
postfix/qmgr[12260]: 4A38C1DAB916: from=<root@mail.example.com>, size=471, nrcpt=1 (queue active)
postfix/local[12277]: 4A38C1DAB916: passing <mailuser@mail.example.com> to transport=lmtp
dovecot[12316]: lmtp(12329): Connect from local
dovecot[12316]: lmtp(mailuser)<12329><IN6eEmRC0mopMAAAYIgGOA>: msgid=<20261016152732.4A38C1DAB916@mail.example.com>: saved mail to INBOX
postfix/lmtp[12278]: 4A38C1DAB916: to=<mailuser@mail.example.com>, orig_to=<mailuser>, relay=mail.example.com[private/dovecot-lmtp], delay=0.02, delays=0.01/0/0.01/0.01, dsn=2.0.0, status=sent (250 2.0.0 <mailuser@mail.example.com> IN6eEmRC0mopMAAAYIgGOA Saved)
💡 说明:这条日志把 6.2 的链路完整走了一遍——qmgr 取件 → 决策 transport=lmtp → Dovecot lmtp 连入 →
saved mail to INBOX→ Postfix 收到 LMTP 的250 ... Saved后标记status=sent。排查投递问题永远从这条日志链入手。
7.3 核对邮件内容落盘
cat /home/mailuser/Maildir/new/*
实测输出(节选):
From root@mail.example.com Fri Oct 16 15:27:59 2026
Return-Path: <root@mail.example.com>
Delivered-To: mailuser@mail.example.com
Received: from mail.example.com
by mail.example.com with LMTP
id IN6eEmRC0mopMAAAYIgGOA
(envelope-from <root@mail.example.com>)
for <mailuser@mail.example.com>; Fri, 16 Oct 2026 15:27:59 +0000
Date: Fri, 16 Oct 2026 15:27:59 +0000
To: mailuser@mail.example.com
Subject: Test Mail Maildir
User-Agent: mailx (Ubuntu)
Hello via LMTP to Maildir
💡 说明:注意投递头
by mail.example.com with LMTP与Delivered-To——LMTP 投递的签名。旁证(同机未改 mail_location 时邮件去向):默认配置下同一条 mail 命令的邮件会落在/var/mail/mailuser(mbox 单文件,首行From root@...),这也是为什么 5.1 必须改 Maildir。
7.4 用 sendmail -t 发第二封(另一种发信入口)
sendmail -t <<'EOF'
From: root@mail.example.com
To: mailuser@mail.example.com
Subject: Test Mail via sendmail -t
This mail is sent by /usr/sbin/sendmail -t directly.
EOF
sleep 3
ls /home/mailuser/Maildir/new/
实测输出(此时 new/ 下已有两封):
1792164479.M820903P12374.mail.example.com,S=712,W=731
1792164502.M8086P12374.mail.example.com,S=639,W=655
💡 说明:
sendmail -t从邮件头里的 To/Cc/Bcc 自动提取收件人,
7.5 IMAP 收取(闭环最后一环)
printf 'a1 LOGIN mailuser q1w2e3r4\r\na2 SELECT INBOX\r\na3 FETCH 1 (BODY[HEADER.FIELDS (From Subject Date)])\r\na4 FETCH 1 (BODY[TEXT])\r\na5 LOGOUT\r\n' | openssl s_client -connect localhost:993 -quiet
实测输出:
* OK [CAPABILITY IMAP4rev1 SASL-IR LOGIN-REFERRALS ID ENABLE IDLE LITERAL+ AUTH=PLAIN] Dovecot (Ubuntu) ready.
a1 OK [CAPABILITY IMAP4rev1 SASL-IR LOGIN-REFERRALS ID ENABLE IDLE SORT SORT=DISPLAY THREAD=REFERENCES THREAD=REFS THREAD=ORDEREDSUBJECT MULTIAPPEND URL-PARTIAL CATENATE UNSELECT CHILDREN NAMESPACE UIDPLUS LIST-EXTENDED I18NLEVEL=1 CONDSTORE QRESYNC ESEARCH ESORT SEARCHRES WITHIN CONTEXT=SEARCH LIST-STATUS BINARY MOVE SNIPPET=FUZZY PREVIEW=FUZZY PREVIEW STATUS=SIZE SAVEDATE LITERAL+ NOTIFY SPECIAL-USE QUOTA] Logged in
* FLAGS (\Answered \Flagged \Deleted \Seen \Draft)
* OK [PERMANENTFLAGS (\Answered \Flagged \Deleted \Seen \Draft \*)] Flags permitted.
* 1 EXISTS
* 1 RECENT
* OK [UNSEEN 1] First unseen.
* OK [UIDVALIDITY 1792164072] UIDs valid
* OK [UIDNEXT 7] Predicted next UID
* OK [HIGHESTMODSEQ 11] Highest
a2 OK [READ-WRITE] Select completed (0.002 + 0.000 + 0.003 secs).
* 1 FETCH (FLAGS (\Seen \Recent) BODY[HEADER.FIELDS (FROM SUBJECT DATE)] {111}
Subject: Test Mail Maildir
Date: Fri, 16 Oct 2026 15:48:31 +0000 (UTC)
From: root <root@mail.example.com>
)
* 1 FETCH (BODY[TEXT] {27}
Hello via LMTP to Maildir
)
a3 OK Fetch completed (0.001 + 0.000 + 0.001 secs).
a4 OK Fetch completed (0.001 + 0.000 + 0.001 secs).
* BYE Logging out
a5 OK Logout completed (0.001 + 0.000 secs).
💡 说明:
1 EXISTS/1 RECENT确认邮件进入 INBOX;FETCH 取回的 Subject/正文与发送内容一致,闭环成立。真实邮件客户端(Thunderbird/Foxmail)同样以 IMAPS 993 收取。
7.6 doveadm 命令行查询邮箱
doveadm search -u mailuser ALL
实测输出:
1872650be840d26aa00d000060880638 1
1872650be840d26aa00d000060880638 2
doveadm mailbox list -u mailuser
doveadm fetch -u mailuser 'mailbox hdr.subject' mailbox INBOX ALL | head -4
实测输出:
INBOX
mailbox: INBOX
hdr.subject: Test Mail Maildir
⚠️ 注意:root 身份执行
doveadm search ALL(不带-u)会直接报错Fatal: USER environment is missing and -u option not used(实测如此)——必须用-u mailuser这类显式参数指定目标邮箱,或以该用户身份执行(su 切换后依赖 USER 环境变量)。
八、常见问题(FAQ,含完整排障实录)
Q1:发信后邮件没进 Maildir,日志里 LMTP 返回 550 5.1.1 User doesn't exist?
这是漏做 5.3(auth_username_format = %n)的典型现象。完整实录:发信后 new/ 为空,日志显示——
postfix/lmtp[12278]: A009D1DAB903: to=<mailuser@mail.example.com>, orig_to=<mailuser>, relay=mail.example.com[private/dovecot-lmtp], delay=0.03, ..., dsn=5.1.1, status=bounced (host mail.example.com[private/dovecot-lmtp] said: 550 5.1.1 <mailuser@mail.example.com> User doesn't exist: mailuser@mail.example.com (in reply to RCPT TO command))
定位过程:LMTP 会话里 Dovecot 收到的收件人是完整地址 mailuser@mail.example.com,直接拿它查 passwd 用户库当然查不到(库里只有 mailuser)。解决方案:10-auth.conf 设置 auth_username_format = %n(截掉 @domain 部分)并 systemctl restart dovecot。重发后日志变为 status=sent (250 2.0.0 ... Saved)。
Q2:邮件进了 /var/mail/mailuser 而不是 ~/Maildir?
mail_location 仍是默认 mbox(实测中重装 Dovecot 后 10-mail.conf 回到默认值即复现)。按 5.1 改回 maildir:~/Maildir 并重启。历史 mbox 邮件可用 dsync 迁移到 Maildir(见《Dovecot 2.3.16 进阶实战与运维》)。
Q3:Postfix 日志里 permission denied 连不上 LMTP 套接字?
套接字不在 Postfix chroot 内或属主不对。核对两点:10-master.conf 中 unix_listener 路径必须是 /var/spool/postfix/private/dovecot-lmtp(不是 /run/dovecot/lmtp);user/group = postfix 已设置且套接字已重新生成(systemctl restart dovecot)。
Q4:doveadm 命令报 Fatal: USER environment is missing?
见 7.6 注意项:root 下执行 doveadm 必须带 -u 指定用户。
Q5:想测试对外投递(发往真实互联网邮箱)? 本篇闭环刻意只走本机回环。对外投递还需:MX 记录、SPF/DKIM/DMARC 配置、25 端口出站(多数云厂商默认封 25,需申请解封或改用 relay),属于邮件系统运营主题,不在本篇范围内。
九、备选方案:LDA 投递(完整实测)
9.1 配置
LDA 方案下 Postfix 通过 mailbox_command 直接调起 Dovecot 的投递代理,不再需要 LMTP 套接字;但 LDA 会生成退信,必须先设置 postmaster_address(不设置时 LDA 会拒绝工作):
postconf -e 'mailbox_transport ='
postconf -e 'mailbox_command = /usr/lib/dovecot/dovecot-lda -f "$SENDER" -a "$RECIPIENT"'
sed -i 's|^#postmaster_address =|postmaster_address = postmaster@mail.example.com|' /etc/dovecot/conf.d/15-lda.conf
grep -n '^postmaster_address' /etc/dovecot/conf.d/15-lda.conf
systemctl reload postfix
实测输出:
7:postmaster_address = postmaster@mail.example.com
💡 说明:
mailbox_command里的$SENDER/$RECIPIENT是 Postfix 投递时注入的环境变量(发件人与最终收件人);mailbox_transport置空以关闭 LMTP 通道,两条通道不要同时启用(mailbox_transport 优先级更高,同时配会让 LDA 配置失效)。
9.2 发信验证
echo 'LDA test mail' | mail -s 'Test Mail via LDA' mailuser
sleep 3
ls /home/mailuser/Maildir/new/
实测输出(new/ 下新增一封):
1792164502.M8086P12374.mail.example.com,S=639,W=655
1792164528.M644079P12444.mail.example.com,S=527,W=542
投递日志:
dovecot[12444]: lda(mailuser)<12444><K85nJrBC0mqcMAAAYIgGOA>: msgid=<20261016152848.9967D1DAB91D@mail.example.com>: saved mail to INBOX
postfix/local[12442]: 9967D1DAB91D: to=<mailuser@mail.example.com>, orig_to=<mailuser>, relay=local, delay=0.02, ..., dsn=2.0.0, status=sent (delivered to command: /usr/lib/dovecot/dovecot-lda -f "$SENDER" -a "$RECIPIENT")
💡 说明:日志形态与 LMTP 明显不同——
relay=local(本机命令投递)且lda(mailuser)开头。两种方式都达成「Postfix → Dovecot → Maildir」的目标,按二节对比选型即可。本篇后续章节恢复 LMTP 主线(postconf -e 'mailbox_command ='清空并还原 mailbox_transport)。
十、注意事项与运维提示
- 本机闭环测试仅用回环与 Unix 套接字,不涉及对公网开放;未来对外提供服务前务必补齐 TLS 证书(见《Dovecot 2.3.16 进阶实战与运维》)、SPF/DKIM/DMARC 与反垃圾策略。
postconf -e每次追加一行配置到 main.cf 尾部,多次实验后建议postconf -n核对无冗余冲突项。- Dovecot 配置改动统一用
doveconf -n复核生效值,再systemctl restart dovecot。 - 用户首次 IMAP 登录前 Maildir 不存在属正常,登录或首封投递时自动创建;不要提前手工 mkdir 后用错属主。
十一、参考链接
- Dovecot LMTP 官方文档:
https://doc.dovecot.org/configuration_manual/lmtp/ - Postfix 官方文档:http://www.postfix.org/documentation.html
- Ubuntu Postfix 指南:https://ubuntu.com/server/docs/mail-postfix
- 相关文档:《Dovecot 2.3.16 Ubuntu 22.04 安装指南》《Dovecot 2.3.16 配置参数详解》《Dovecot 2.3.16 进阶实战与运维》