Ubuntu 22.04 安装 Maddy 0.9.6 指南

Ubuntu 22.04 从零部署 Maddy Mail Server 0.9.6 全流程实测:官方预编译 tarball 与源码编译双方案、maddy 系统用户与 systemd 托管、TLS 证书与首个邮箱账号、五端口监听验证、升级/多实例与卸载,排障实录含 DKIM 密钥权限与启动频率限制

Maddy邮件服务器2026/10/19

Ubuntu 22.04 安装 Maddy 0.9.6 指南

目标:在一台全新 Ubuntu 22.04 服务器上从零部署 Maddy Mail Server 0.9.6,覆盖官方预编译 tarball 与源码编译两条完整安装路径,完成 maddy 专用系统用户创建、systemd 服务托管、TLS 证书配置、管理员与测试邮箱账号开通,并验证 SMTP(25)、Submission(465/587)、IMAP(143/993) 五端口监听与开机自启。本文所有命令与回显均在 Ubuntu 22.04 实测通过,可直接照做。

一、环境信息

本文所有操作在以下环境中实测通过,写文档时请对照自己的实际情况调整 IP 与域名。

1.1 系统与软件版本

  • 操作系统:Ubuntu 22.04(PRETTY_NAME="Ubuntu 22.04.5 LTS")
  • Maddy 版本:0.9.6(2026-10-19 从 GitHub Releases 实查的最新稳定版,发布于 2026-10-03,为安全修复版)
  • 二进制构建信息:0.9.6 linux/amd64 go1.27.1(Go 1.27.1 编译的静态二进制,运行时无动态库依赖)
  • systemd 版本:249(Ubuntu 22.04 自带,服务托管必需)

1.2 网络与域名约定

邮件服务器强依赖 DNS 与主机名,本文使用如下测试约定(真实部署时替换为你的公网域名):

  • 服务器主机名:mail.lab.example.org(Maddy 默认配置取 $(hostname) 作 EHLO 标识与证书查找键)
  • 测试邮件域:lab.example.org(下文邮箱均在该域下创建)
  • 管理员邮箱:admin@lab.example.org,密码 q1w2e3r4(生产务必换强口令)
  • 测试邮箱:demo@lab.example.org,密码同为 q1w2e3r4
  • 测试环境无公网 DNS 权威,域名解析用 /etc/hosts 与本地 dnsmasq 模拟(收发闭环实测见《Maddy 0.9.6 配置参数详解》)

1.3 端口规划与占用复核

Maddy 默认监听 5 个端口,动工前必须复核占用:

端口用途默认绑定
25SMTP(MTA/MX 对外收信)0.0.0.0
465SMTPS(隐式 TLS 提交)0.0.0.0
587Submission(STARTTLS 提交)0.0.0.0
143IMAP(STARTTLS)0.0.0.0
993IMAPS(隐式 TLS)0.0.0.0

复核命令:

ss -ltn | grep -E ':(25|465|587|143|993)\b'

实测输出(动工前执行,端口全部空闲时无输出):

(无输出,即 25/465/587/143/993 均未被占用)

⚠️ 注意:25 端口是邮件 MX 专用端口,不能随意更换(外部服务器按 MX 记录回源投递);若被 Postfix/Dovecot 遗留服务占用,先 systemctl disable --now postfix dovecot 释放。测试环境可将 25 端口绑定改写为 tcp://127.0.0.1:25 仅监听本地回环,避免测试服务器对外暴露(本文实测即采用该改法,对应配置见第七章)。若确需改用高位端口(如 2525),外部发件方无法直连,仅适用于明确知晓端口的内网互发场景,须在文档与 DNS 约定中说明。

二、前置条件

本章准备系统依赖与主机名,为两条安装路径打好共同基础。

2.1 系统依赖

Maddy 预编译二进制为静态链接,运行时零依赖。安装环节仅需要解压工具 zstd、证书库与下载工具:

apt-get update
apt-get install -y zstd ca-certificates curl

实测输出(末尾节选):

Running hooks in /etc/ca-certificates/update.d...
done.

2.2 主机名设置

Maddy 用主机名作 SMTP EHLO 标识与 TLS 证书匹配对象,先设为 FQDN:

hostnamectl set-hostname mail.lab.example.org
hostname

实测输出:

mail.lab.example.org

2.3 TLS 证书准备

测试环境用 openssl 自签一张证书(路径与默认配置的查找规则一致):

mkdir -p /etc/maddy/certs/mail.lab.example.org
openssl req -newkey rsa:2048 -nodes \
  -keyout /etc/maddy/certs/mail.lab.example.org/privkey.pem \
  -x509 -days 365 -out /etc/maddy/certs/mail.lab.example.org/fullchain.pem \
  -subj "/CN=mail.lab.example.org"

实测输出(openssl 首行):

-----

💡 技术拓展:TLS 证书获取方式对比——

方式原理适用
Let's Encrypt(certbot)ACME 验证,免费 90 天自动续期公网域名 + 80 端口可达
acme.sh纯 Shell ACME 客户端,支持 DNS APIDNS 托管在云厂商
自签证书openssl 本地生成内网测试、无公网域名
商业证书付费 CA 签发企业正式对外

本文测试环境采用自签;生产环境建议 Let's Encrypt——Maddy 每分钟自动重读磁盘证书,续期零重启,官方教程给出 certbot 证书目录软链与 ACL 授权方案(见 7.2 节)。

三、版本选型说明

Maddy 无官方 deb/rpm 软件仓库(Ubuntu 官方源也没有该包),可选安装形态如下:

安装形态来源适用
预编译 tarball(推荐)GitHub Releases / maddy.email/builds标准部署,含 systemd unit,开箱即用
源码编译GitHub Releases src.tar.zst + build.sh非官方架构或需改代码
AUR 包Arch AUR maddy / maddy-git仅 Arch 系
官方镜像foxcpp/maddy容器化部署(本文不涉及)

关键差异与选型建议:官方 tarball 为静态编译、附带 systemd 单元,x86_64 与 aarch64 两种架构直接可用,属首选;源码编译仅在小众架构(如 riscv64)或需要打补丁时采用。

版本查询方法(未来读者以查询时点最新稳定版为准,Pre-release 不用于生产):

curl -s https://api.github.com/repos/foxcpp/maddy/releases | python3 -c "import json,sys;[print(r['tag_name'],r['published_at'][:10],'prerelease' if r['prerelease'] else '') for r in json.load(sys.stdin)[:5]]"

2026-10-19 实查输出:

v0.9.6 2026-10-03
v0.9.5 2026-05-23
v0.9.4 2026-04-30
v0.9.3 2026-04-12
v0.9.2 2026-03-26

四、方案 A:官方预编译 tarball 安装(推荐)

tarball 内含四样东西:静态编译的 maddy 主程序、默认配置 maddy.conf、两个 systemd 单元(单实例 maddy.service 与多实例模板 maddy@.service)。按"下载校验 → 解压 → 安装 → 建用户 → 托管 → 验证"完整执行。

4.1 下载与完整性校验

cd /tmp
curl -fSL -o maddy-0.9.6-x86_64-linux-musl.tar.zst https://github.com/foxcpp/maddy/releases/download/v0.9.6/maddy-0.9.6-x86_64-linux-musl.tar.zst
ls -la maddy-0.9.6-x86_64-linux-musl.tar.zst
sha256sum maddy-0.9.6-x86_64-linux-musl.tar.zst

实测输出:

-rw-r--r-- 1 root root 23782034 Oct 19 07:28 maddy-0.9.6-x86_64-linux-musl.tar.zst
96eb6945c3b08b09602c5965abeafa7143430afd826892d4446c5a9c3f880cdd  maddy-0.9.6-x86_64-linux-musl.tar.zst

⚠️ 注意:海外网络下载缓慢或失败时走代理:curl -x http://192.168.1.62:7897 -fSL -o ... <URL>。官方同时发布 .sig 签名文件,可用发布者 GPG 公钥进一步验签;最小化环境以 sha256 比对为准。

4.2 解压并核对包内容

cd /tmp
tar --zstd -xf maddy-0.9.6-x86_64-linux-musl.tar.zst
find maddy-0.9.6-x86_64-linux-musl -type f | sort

实测输出:

maddy-0.9.6-x86_64-linux-musl/maddy
maddy-0.9.6-x86_64-linux-musl/maddy.conf
maddy-0.9.6-x86_64-linux-musl/systemd/maddy.service
maddy-0.9.6-x86_64-linux-musl/systemd/maddy@.service

四个文件各就各位:主程序、默认配置、单实例与多实例 systemd 单元。

4.3 安装二进制与配置文件

install -m 0755 /tmp/maddy-0.9.6-x86_64-linux-musl/maddy /usr/local/bin/maddy
mkdir -p /etc/maddy
install -m 0644 /tmp/maddy-0.9.6-x86_64-linux-musl/maddy.conf /etc/maddy/maddy.conf
ls -la /etc/maddy/

实测输出:

total 20
drwxr-xr-x 2 root root 4096 Oct 19 07:28 .
drwxr-xr-x 1 root root 4096 Oct 19 07:28 ..
-rw-r--r-- 1 root root 4739 Oct 19 07:28 maddy.conf

验证主程序(注意:Maddy 用 version 子命令,不是 -version 长参数):

maddy version

实测输出:

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

⚠️ 注意:执行 maddy -version 会报错,实测回显:

Incorrect Usage: flag provided but not defined: -version

版本信息一律用 maddy version。同理,全局选项 --config 必须放在子命令之前:maddy --config /etc/maddy/relay.conf verify-config,写在子命令之后会报 flag provided but not defined: -config。

4.4 创建 maddy 系统用户

Maddy 从不以 root 运行(官方教程原文 "maddy never runs as root"),低端口绑定由 systemd 授予 CAP_NET_BIND_SERVICE 能力完成。创建专用系统用户:

useradd -mrU -s /sbin/nologin -d /var/lib/maddy -c "maddy mail server" maddy
id maddy

实测输出:

uid=999(maddy) gid=999(maddy) groups=999(maddy)

参数含义:-m 建家目录、-r 系统账户、-U 建同名组、-s /sbin/nologin 禁登录、-d /var/lib/maddy 家目录即数据目录。

4.5 安装 systemd 单元

install -m 0644 /tmp/maddy-0.9.6-x86_64-linux-musl/systemd/maddy.service /etc/systemd/system/maddy.service
install -m 0644 /tmp/maddy-0.9.6-x86_64-linux-musl/systemd/maddy@.service /etc/systemd/system/maddy@.service
systemctl daemon-reload
grep -E '^(User|WorkingDirectory|StateDirectory|ExecStart|ExecReload|Restart)=' /etc/systemd/system/maddy.service

实测输出:

User=maddy
WorkingDirectory=/var/lib/maddy
StateDirectory=maddy
ExecStart=/usr/local/bin/maddy run
ExecReload=/bin/kill -USR2 $MAINPID
Restart=on-failure

官方 unit 内置严格沙箱(ProtectSystem=strict、PrivateDevices、MemoryDenyWriteExecute 等)与 AmbientCapabilities=CAP_NET_BIND_SERVICE;Type=notify 精确感知就绪;配置错误时进程以退出码 2 结束,RestartPreventExitStatus=2 阻止无意义的重启循环。maddy@.service 模板固定读取 /etc/maddy/<实例名>.conf,多实例用法见 8.3 节。

4.6 修改域名配置并启动

默认配置第 11-12 行是官方示例值,先改成本环境域名:

sed -i 's/^\$(hostname) = .*/\$(hostname) = mail.lab.example.org/;s/^\$(primary_domain) = .*/\$(primary_domain) = lab.example.org/' /etc/maddy/maddy.conf
head -13 /etc/maddy/maddy.conf | tail -3

实测输出:

$(hostname) = mail.lab.example.org
$(primary_domain) = lab.example.org
$(local_domains) = $(primary_domain)

启动前先做配置校验(这一步会自动生成 DKIM 密钥对):

maddy verify-config

实测输出(节选):

modify.dkim: generating a new rsa2048 keypair...
modify.dkim: generated a new rsa2048 keypair, private key is in dkim_keys/lab.example.org_default.key, TXT record with public key is in dkim_keys/lab.example.org_default.dns,
put its contents into TXT record for default._domainkey.lab.example.org to make signing and verification work
No errors detected

No errors detected 即校验通过;生成的 DKIM 公钥 TXT 记录(dkim_keys/lab.example.org_default.dns 文件内容)需要发布到 DNS 的 default._domainkey.lab.example.org。随后启动:

chown -R maddy:maddy /var/lib/maddy /etc/maddy/certs
systemctl start maddy
systemctl is-active maddy

实测输出:

active

⚠️ 注意:若先以 root 跑过 maddy verify-config 再启动,常见报错为:

Error: open dkim_keys/lab.example.org_default.key: permission denied

根因是 DKIM 密钥由 root 生成、服务以 maddy 用户运行读不了。修复:chown -R maddy:maddy /var/lib/maddy 后 systemctl reset-failed maddy && systemctl start maddy。连续失败 5 次后 systemd 会以 Start request repeated too quickly 拒绝启动,必须先 reset-failed 清状态。

4.7 端口与开机自启验证

ss -ltn | grep -E ':(25|465|587|143|993)\b'
systemctl enable maddy

实测输出:

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             *:*
Created symlink /etc/systemd/system/multi-user.target.wants/maddy.service → /etc/systemd/system/maddy.service.

(本环境已将 25 端口改绑 127.0.0.1 仅监听本地回环;默认配置下应显示 *:25,测试环境强烈建议同样的改法。)五端口监听 + 开机自启,方案 A 完成。

五、方案 B:源码编译安装

源码编译适合无预编译包的架构或需要修改源码的场景。Maddy 用 Go 编写,v0.9.6 起最低要求 Go 1.26;SQLite 驱动需要 CGO 与 gcc。

5.1 安装构建工具链

apt-get install -y build-essential

Go 工具链官方下载(版本不低于 1.26):

cd /tmp
curl -fSL -o go1.27.1.linux-amd64.tar.gz https://go.dev/dl/go1.27.1.linux-amd64.tar.gz
tar -C /usr/local -xzf go1.27.1.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin
go version

实测输出:

go version go1.27.1 linux/amd64

5.2 获取源码并执行编译

cd /tmp
curl -fSL -o maddy-0.9.6-src.tar.zst https://github.com/foxcpp/maddy/releases/download/v0.9.6/maddy-0.9.6-src.tar.zst
tar --zstd -xf maddy-0.9.6-src.tar.zst
cd maddy-0.9.6-src
chmod +x build.sh
./build.sh

实测输出(节选:模块下载与构建告警):

go: downloading github.com/philhofer/fwd v1.2.0
go: downloading github.com/hashicorp/go-cleanhttp v0.5.2
-- [!] No scdoc utility found. Skipping man pages building.
-- Copying misc files...

scdoc 缺失只影响 man 手册构建(告警跳过),不影响二进制。编译产物在 build/ 目录:

./build/maddy version

实测输出:

0.9.6 linux/amd64 go1.27.1

与官方 tarball 构建信息一致。

5.3 安装编译产物

./build.sh install

build.sh install 把二进制装入 /usr/local/bin、unit 装入 systemd 目录并写入默认配置(可用 --prefix、--destdir 控制路径),随后执行 systemctl daemon-reload 并按方案 A 的 4.4(建用户)、4.6(域名配置与启动)继续即可。

⚠️ 注意:编译需要约 2GB 可用内存(Go 编译峰值),低配实例先加 swap;模块下载量大,慢时走代理 export http_proxy=http://192.168.1.62:7897 https_proxy=http://192.168.1.62:7897 再执行 build.sh。源码编译产物缺少官方签名保障,生产环境优先方案 A。

六、方案对比与选择建议

维度方案 A:官方 tarball方案 B:源码编译
耗时分钟级(23MB 下载 + 解压)十分钟级(Go 工具链 + 模块编译)
依赖仅 zstdGo 1.26+、gcc(CGO)、可选 scdoc
systemd unit官方附带build.sh install 安装
二进制形态静态链接无动态库依赖按构建机 libc 形态
版本一致性与 Release 完全一致与源码 tag 一致,可自定义
适用绝大多数 Ubuntu/Debian 部署小众架构、二次开发

选择建议:首选方案 A。两方案装出的服务行为一致、配置与运维方式完全相同;方案 B 的价值仅在小众架构与源码定制。

七、配置说明

安装层面需要关心的配置共四处:域名、TLS、账号、别名。

7.1 主机名与域名

/etc/maddy/maddy.conf 顶部三个变量决定全站行为:

$(hostname) = mail.lab.example.org     # SMTP EHLO 标识与证书查找键,应与 MX 记录指向一致
$(primary_domain) = lab.example.org    # 主邮件域,DKIM 默认签名域
$(local_domains) = $(primary_domain)   # 本地投递域列表,多域空格追加

改动后 maddy verify-config 校验再 systemctl restart maddy 生效。参数级详解见《Maddy 0.9.6 配置参数详解》。

7.2 TLS 证书与 Let's Encrypt

Maddy 按 /etc/maddy/certs/<hostname>/fullchain.pem 与 privkey.pem 查找证书。生产用 certbot 时官方推荐软链 + ACL:

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

Maddy 每分钟自动重读证书,续期零重启;也可 systemctl reload maddy(即向主进程发 USR2)手动触发。

7.3 创建首个邮箱账号

Maddy 的"认证凭据"与"IMAP 存储账户"是两个独立概念(官方教程明确要求分别创建,这是与 Postfix/Dovecot 虚拟用户模型最大的差异点):

maddy creds create -p q1w2e3r4 admin@lab.example.org
maddy imap-acct create admin@lab.example.org
maddy creds create -p q1w2e3r4 demo@lab.example.org
maddy imap-acct create demo@lab.example.org
maddy creds list

实测输出:

admin@lab.example.org
demo@lab.example.org

⚠️ 注意:只建 creds 不建 imap-acct,外部投递会在 RCPT 阶段被拒,实测回显 501 5.1.1 User does not exist (msg ID = f9c508aa)。用户名必须是完整邮箱地址。creds create -p 会把密码留在 shell 历史,生产建议用交互式(不带 -p)或 maddy hash 生成 bcrypt 哈希后写入表。imap-acct create 会同步创建带 special-use 标记的默认文件夹(Sent/Trash/Junk/Drafts/Archive)。

7.4 别名文件

默认配置的 local_rewrites 链含 optional_step file /etc/maddy/aliases,文件不存在仅打日志不影响运行:

table.file: ignoring non-existent file: /etc/maddy/aliases

需要 postmaster 类角色别名时创建 /etc/maddy/aliases(每行 别名 目标)后 systemctl reload maddy。

八、升级与多实例管理

8.1 升级流程

Maddy 升级就是替换二进制并重启,数据目录向后兼容:

cd /tmp
curl -fSL -o maddy-0.9.6-x86_64-linux-musl.tar.zst https://github.com/foxcpp/maddy/releases/download/v0.9.6/maddy-0.9.6-x86_64-linux-musl.tar.zst
tar --zstd -xf maddy-0.9.6-x86_64-linux-musl.tar.zst
install -m 0755 /tmp/maddy-0.9.6-x86_64-linux-musl/maddy /usr/local/bin/maddy
systemctl restart maddy
systemctl is-active maddy
maddy version | head -1

实测输出(本文以同版本重装演示流程):

active
0.9.6 linux/amd64 go1.27.1

⚠️ 注意:升级前备份 /var/lib/maddy(SQLite 双库 + messages + dkim_keys,备份方法见《Maddy 0.9.6 日常运维手册》);跨大版本先读官方 Upgrade 文档核对配置兼容性。

8.2 降级与多版本管理

无仓库包管理,多版本并存的方式就是把各版本二进制改名放置,需要哪个切换哪个:

cp /usr/local/bin/maddy /opt/maddy-0.9.6
# 回退旧版本示例(版本号按实际持有二进制替换):
install -m 0755 /opt/maddy-0.9.5 /usr/local/bin/maddy && systemctl restart maddy

降级前同样先备份 /var/lib/maddy(新版可能已做数据迁移)。

8.3 多实例管理(maddy@ 模板单元)

包内附带的 maddy@.service 固定读取 /etc/maddy/<实例名>.conf。以一个监听 127.0.0.2:25 的第二 MX 实例为例(配置要点:独立 hostname、独立监听地址、state 目录用绝对路径指向 /var/lib/maddy-relay 避免双实例 SQLite 锁冲突):

cp /etc/maddy/maddy.conf /etc/maddy/relay.conf
# 编辑 relay.conf:改 hostname/primary_domain、smtp 监听改 tcp://127.0.0.2:25、存储 dsn 改绝对路径
maddy --config /etc/maddy/relay.conf verify-config
systemctl start maddy@relay
systemctl is-active maddy@relay
ss -ltn | grep ':25'

实测输出:

No errors detected
active
LISTEN 0      4096       127.0.0.1:25        0.0.0.0:*
LISTEN 0      4096       127.0.0.2:25        0.0.0.0:*

⚠️ 注意:第二实例常见三类启动报错(均实测):① 证书路径对 maddy 用户不可读 → permission denied,修复 chown -R maddy:maddy /etc/maddy/certs;② state 目录属主不对 → imapsql: NewBackend (schemaVersion): attempt to write a readonly database,修复 chown -R maddy:maddy /var/lib/maddy-relay;③ 配置块定义了却没被引用 → unused configuration block relay_authdb (auth.pass_table),删除多余块或接上引用。

九、常见问题(FAQ)

Q1:启动失败 Error: open dkim_keys/lab.example.org_default.key: permission denied?

root 身份跑过 maddy verify-config 导致 DKIM 密钥属主为 root。修复三连:

chown -R maddy:maddy /var/lib/maddy /etc/maddy/certs
systemctl reset-failed maddy
systemctl start maddy

Q2:Job for maddy.service failed 如何快速定位?

三步走:systemctl status maddy --no-pager 看退出码(status=2 多为配置错误);journalctl -u maddy -n 20 --no-pager 看具体报错;改完先 maddy verify-config 再 start。实测配置少一个右括号时的报错:

Error: failed to read config /etc/maddy/maddy.conf: /etc/maddy/maddy.conf:182 - Error during parsing: unexpected EOF when looking for }

Q3:外部投递被 501 5.1.1 拒绝?

storage 账户没建。凭据(creds)与存储账户(imap-acct)分离,投递检查的是后者,两者都要创建(见 7.3 节)。

Q4:143 端口登录被拒 Authentication disabled?

默认禁止在明文连接上传输密码:143 端口必须先 STARTTLS 再认证,或直接用 993(隐式 TLS)。客户端配置"使用 SSL/TLS"即对应 993。

Q5:本域用户通过 25 端口发信被拒?

默认配置的安全设计,实测回显:

501 5.1.8 Use Submission for outgoing SMTP (msg ID = a1816d2a)

本域发件人必须走 587/465 认证提交,防止 25 端口被滥用伪造本域信件。

Q6:SMTP 会话报 550 5.7.1 DMARC check failed?

默认 25 端口开启 dmarc yes 与 check { require_mx_record; dkim; spf },发件域 DMARC 策略为 reject 且 DKIM/SPF 均未通过时拒收——这是正确的防伪造行为;测试环境换无 DMARC 记录的域即可通过(详见《Maddy 0.9.6 配置参数详解》实测章节)。

十、卸载与清理

完整卸载需清理服务、二进制、配置、数据与用户五部分:

systemctl disable --now maddy
rm -f /etc/systemd/system/maddy.service /etc/systemd/system/maddy@.service
systemctl daemon-reload
rm -f /usr/local/bin/maddy
rm -rf /etc/maddy
rm -rf /var/lib/maddy
userdel maddy

⚠️ 注意:/var/lib/maddy 删除后所有邮箱、账户库与 DKIM 私钥不可恢复;如需保留邮件先备份(见《Maddy 0.9.6 日常运维手册》备份章节)。有第二实例的先 systemctl disable --now maddy@relay 并删除对应 /etc/maddy/relay.conf 与 state 目录。

验证卸载结果:

maddy version

实测输出:

-bash: /usr/local/bin/maddy: No such file or directory

十一、参考链接