Dovecot 2.3.16 快速入门与 Postfix 集成示例

从零安装 Postfix 3.6.4 与 Dovecot 2.3.16 并打通本机真实收发闭环——LMTP 投递套接字配置、auth_username_format=%n 排障、sendmail/mail 发信、Maildir 落盘核对、IMAP 收取与 doveadm 查询,附 LDA 备选方案实测。

Dovecot2026/10/17

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 前置条件

  1. Ubuntu 22.04 服务器,root 权限;
  2. 主机名已设置为邮件域名(本篇 mail.example.com);
  3. 已放行本地回环发信(本篇全部测试走本机,无需对公网开放 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 提供 mail 命令用于发测试邮件。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 自动提取收件人,mail 命令底层也是调用 sendmail 投入 Postfix 队列——两种入口殊途同归。

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 后用错属主。

十一、参考链接