Ubuntu 22.04 安装 Mox 指南

在 Ubuntu 22.04 从零安装 Mox v0.0.17:预编译二进制/go install/离线编译三方案、quickstart 初始化与 DNS 记录、systemd 安全托管、端口与 Web 验证、backup/verifydata 备份闭环与 8 例排障实录。

MoxMox邮件服务器2026/10/21

Ubuntu 22.04 安装 Mox 指南

目标:在 Ubuntu 22.04 服务器上从零安装 Mox 邮件服务器(v0.0.17),完成预编译二进制/源码编译安装、quickstart 初始化、systemd 服务托管、收发端口验证与数据备份校验的完整闭环。本文全部命令与回显均在 Ubuntu 22.04.5 LTS(x86_64)服务器实测通过。

一、环境信息

本文实测环境如下,读者可按自己的服务器替换主机名、域名与 IP。

  • 操作系统:Ubuntu 22.04.5 LTS(x86_64)
  • 软件版本:Mox v0.0.17(官方最新稳定版,2026-10-21 实查)
  • 编译工具链:Go 1.25.0(go version → go version go1.25.0 linux/amd64)
  • 主机名:mail.example.com(邮件服务器主机名,需为 FQDN)
  • 邮件域名:example.com
  • 服务器内网 IP:10.89.58.2
  • 安装目录:/home/mox(mox 官方推荐的家目录布局,二进制/配置/数据都在其下)
  • 数据目录:/home/mox/data,配置目录:/home/mox/config
  • 运行用户:mox(mox 以 root 绑定端口后自动降权到该用户)
  • 相关组件:unbound 1.13.1(DNSSEC 验证解析器,quickstart 强烈推荐)
  • 站内背景知识:Mox 的定位、架构与同类选型对比见 《Mox v0.0.17 介绍与选型指南》

二、前置条件

安装前把「机器、端口、DNS、依赖」四件事确认清楚,可避免安装后一半的常见故障。

2.1 服务器与端口要求

Mox 是一体化邮件服务器,一台服务器(或独占的虚拟机)即可运行全部服务。官方安装页明确建议使用一台专门跑邮件的机器,原因有两点:一是 80/443 端口最好由 mox 独占(用于 ACME 自动 TLS 与 MTA-STS/autoconfig);二是邮件服务器 IP 的信誉度非常关键,与 Web 业务混跑容易互相影响。

需要独占的端口清单(安装完成后用 ss -ltnp 核对):

端口服务用途
25SMTP服务器间邮件投递(MX 互投)
465Submissions用户提交邮件(SMTP over TLS)
993IMAPS用户读取邮件(IMAP over TLS)
80/443HTTP/HTTPSWebmail、管理后台、autoconfig、MTA-STS、ACME

⚠️ 注意:25 端口在多数公有云上默认封禁,需在云控制台申请解封;否则只能对外发信测试,无法接收互联网来信。

2.2 DNS 要求

  • 主机名 mail.example.com 需有 A/AAAA 记录指向服务器 IP,反向解析(PTR)最好也配置一致;
  • 域名建议开启 DNSSEC,并配置 DNSSEC 验证的本地解析器(unbound),否则 DANE 不生效、出站 TLS 退化为 MTA-STS 或机会式加密(quickstart 运行时会给出明确警告,见第八章实录);
  • 首次安装可以先不创建任何邮件 DNS 记录,quickstart 会打印完整记录清单,照单创建即可。

2.3 系统依赖

Ubuntu 22.04 最小安装即可满足运行需要;选择「源码编译」安装方案时需要 Go 工具链(≥1.23,见官方安装页要求)。本文在安装主线开始前把系统包更新到位:

apt update && apt -y upgrade

输出节选(实测):

Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
Calculating upgrade... Done

再装一组基础工具(curl 用于下载二进制,ss 已内置于 iproute2):

apt -y install curl ca-certificates

输出节选(实测):

Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following NEW packages will be installed:
  ca-certificates curl
...
Setting up curl (7.81.0-2ubuntu1.21) ...

三、版本选型说明

版本双源实查于 2026-10-21:

  1. 官方 GitHub releases.atom(https://github.com/mjl-/mox/releases.atom)最新条目为 v0.0.17;
  2. 官方安装页推荐的预编译服务 beta.gobuilds.org 的 @latest 页面标题同样解析为 mox@v0.0.17,其历史构建列表从 v0.0.17 依次回溯到 v0.0.5。

两个独立来源互证,v0.0.17 为当前最新稳定版,本文全文按该版本实测。

同一版本号在不同安装形态下,mox version 的输出后缀不同(工具链不同),实测对照:

安装形态mox version 实测输出说明
预编译二进制(gobuilds.org)v0.0.17-go1.27.3官方用 Go 1.27.3 工具链构建
go install @v0.0.17 编译v0.0.17-go1.25.0版本号由 go install 模块机制注入
源码 tar.gz 解压后 go build(devel)-go1.25.0不在 git 仓库内编译,无版本标签,功能与正式版一致

三种形态的功能完全一致,仅版本字符串后缀(构建工具链版本)不同。关键差异:需要「一眼看出版本号」选方案 A 或 B;离线内网环境选方案 C((devel) 后缀属正常现象,详见 6.3 节)。

四、安装方案 A:官方预编译二进制(在线,推荐)

官方没有 apt/deb 软件仓库,预编译二进制通过官方安装页指定的 beta.gobuilds.org 服务分发,@latest 路径永远指向最新稳定版。本方案两条命令完成下载与落位。

4.1 下载二进制

先访问 @latest 页面确认当前版本与文件名:

curl -sSL "https://beta.gobuilds.org/github.com/mjl-/mox@latest/linux-amd64-latest/" -o /tmp/moxpage.html
grep -oE 'href="mox-v[^"]+"' /tmp/moxpage.html | head -2

实测输出:

href="mox-v0.0.17-go1.27.3"
href="mox-v0.0.17-go1.27.3.gz"

⚠️ 排障实录:直接下载 https://beta.gobuilds.org/github.com/mjl-/mox@latest/linux-amd64-latest/mox-v0.0.17-go1.27.3 会得到 80 字节的 404 - File Not Found(Missing slash at end of URL or unknown build/result page)。该服务的真实文件存放在带构建哈希的「结果页」路径下,需要从 @latest 页面跳转。实际下载 URL 形如 https://beta.gobuilds.org/github.com/mjl-/mox@v0.0.17/linux-amd64-go1.27.3/0TbBLLO4fdtOLE4jVPvCAGyLfQok/mox-v0.0.17-go1.27.3,其中末段哈希从 @latest 页面内的链接(href="/github.com/mjl-/mox@v0.0.17/linux-amd64-go1.27.3/<哈希>/")获得。

带哈希路径下载(用上一步页面里取到的哈希替换):

curl -sSL -o /root/mox \
  "https://beta.gobuilds.org/github.com/mjl-/mox@v0.0.17/linux-amd64-go1.27.3/0TbBLLO4fdtOLE4jVPvCAGyLfQok/mox-v0.0.17-go1.27.3"
chmod +x /root/mox
ls -la /root/mox

实测输出:

-rwxr-xr-x 1 root root 39959298 Oct 21 10:32 /root/mox

⚠️ 排障实录:不要把二进制放在 /tmp 下执行——Ubuntu 22.04 的 /tmp 挂载为 tmpfs (rw,nosuid,nodev,noexec,...),执行会报 Permission denied(mount | grep /tmp 可见 noexec 选项)。下载后先移到家目录或 /root 再执行。

4.2 验证二进制可运行

/root/mox version

实测输出:

v0.0.17-go1.27.3
linux/amd64

输出首行即版本号,与第三章版本对照一致,方案 A 完成。

五、安装方案 B:go install 在线编译(在线)

适合服务器上已有 Go 工具链、或希望用指定工具链构建的场景。go install 会从 Go 模块代理拉取 v0.0.17 源码与依赖,编译产物自动带版本号。

5.1 安装 Go 工具链

Ubuntu 22.04 仓库自带的 golang 版本过旧(1.18),不满足 mox 的 ≥1.23 要求,需从 go.dev 安装官方工具链:

curl -sSLO https://go.dev/dl/go1.25.0.linux-amd64.tar.gz
rm -rf /usr/local/go && tar -C /usr/local -xzf go1.25.0.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin
go version

实测输出:

go version go1.25.0 linux/amd64

💡 说明:export PATH 只对当前会话生效,长期使用应把该行写入 /root/.bashrc。

5.2 go install 编译安装

按官方安装页给出的命令,指定 v0.0.17 定版编译(GOBIN 指向当前目录,产物为 mox 单文件):

GOBIN=$PWD CGO_ENABLED=0 go install github.com/mjl-/mox@v0.0.17

实测输出(依赖下载阶段,节选):

go: downloading golang.org/x/sys v0.47.0
go: downloading github.com/mjl-/xfmt v0.0.2
go: downloading github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0

⚠️ 排障实录:直连 Go 模块代理报 Get "https://proxy.golang.org/...": dial tcp 142.251.45.145:443: connect: connection refused(proxy.golang.org 属 Google 基础设施,境内网络不可达)。解决方式二选一:其一,走 HTTP 代理 export http_proxy=http://<代理地址>:<端口> https_proxy=http://<代理地址>:<端口> 后重试;其二,改用国内模块代理 export GOPROXY=https://goproxy.cn,direct。本文实测走 HTTP 代理后编译成功。

编译完成验证:

ls -la mox && ./mox version

实测输出:

-rwxr-xr-x 1 root root 38468466 Oct 21 10:38 mox
v0.0.17-go1.25.0
linux/amd64

版本号 v0.0.17-go1.25.0 完整可见(本机工具链为 Go 1.25.0),方案 B 完成。

六、安装方案 C:源码 tar.gz 离线编译(离线/内网)

适合不能访问外网的场景:在有网的机器上下载一次源码包,拷贝到内网服务器编译。Ubuntu 22.04 的 build-essential 已含 gcc,只需 Go 工具链。

6.1 下载源码包

curl -sSLO https://github.com/mjl-/mox/releases/download/v0.0.17/mox-v0.0.17.tar.gz
ls -la mox-v0.0.17.tar.gz

实测输出:

-rw-r--r-- 1 root root 6016889 Oct 21 08:54 mox-v0.0.17.tar.gz

6.2 解压并编译

tar -xzf mox-v0.0.17.tar.gz
cd mox-0.0.17
CGO_ENABLED=0 go build -o /root/mox .

编译约 1~3 分钟,成功时无任何输出(Go 的惯例:无报错即成功)。验证:

ls -la /root/mox && /root/mox version

实测输出:

-rwxr-xr-x 1 root root 38456050 Oct 21 10:39 /root/mox
(devel)-go1.25.0
linux/amd64

6.3 关于 (devel) 版本标签的说明

源码 tar.gz 不含 git 元数据,go build 无法注入版本号,mox version 显示 (devel) 属正常现象,功能与 v0.0.17 正式版完全一致(源码即 v0.0.17 tag 的快照)。若必须让离线编译产物带版本号,可在有网机器上用方案 B 的 go install github.com/mjl-/mox@v0.0.17 编译后,把二进制拷入内网,mox version 即显示 v0.0.17-go1.25.0。

七、方案对比与选择建议

维度A 预编译二进制B go install 编译C tar.gz 离线编译
是否需联网需要(下载约 40MB)需要(拉源码与依赖)仅下载源码包一次,编译全程离线
版本号显示v0.0.17-go1.27.3v0.0.17-go1.25.0(devel)-go1.25.0
需装 Go不需要需要(≥1.23)需要(≥1.23)
耗时秒级下载约 2~4 分钟约 1~3 分钟
可控性固定工具链不可选可选任意工具链完全离线、可审源码

选择建议:能联网优先方案 A(最省事、官方直接分发);需要内网合规留存或审计源码选方案 C;运维团队统一工具链版本时选方案 B。三个方案产出的都是同一个 /root/mox 二进制,后续章节完全一致。

八、quickstart 初始化配置

quickstart 是 mox 的核心初始化命令:创建 mox 用户、生成 mox.conf/domains.conf 配置、生成管理密码与账号密码、打印全部待创建的 DNS 记录、生成 systemd 服务文件。这是官方唯一推荐的初始化方式,不要手写配置文件起步。

8.1 创建 mox 用户并落位二进制

按官方安装页流程,先建用户与家目录,把二进制放到 /home/mox/mox:

useradd -m -d /home/mox mox
mv /root/mox /home/mox/mox
chown root:mox /home/mox/mox
chmod 750 /home/mox/mox
ls -la /home/mox/mox

实测输出:

-rwxr-x--- 1 root mox 39959298 Oct 21 10:42 /home/mox/mox

💡 说明:二进制属主保持 root、组为 mox、权限 750,与 mox 官方快速上手的安全建议一致——mox 用户运行时只能读写自己的 config/data 目录,二进制本身不可改写。

8.2 设置主机名

quickstart 用机器主机名推导邮件主机名,确保其为 FQDN:

hostnamectl set-hostname mail.example.com
hostname

实测输出:

mail.example.com

若不想改主机名,可用 ./mox quickstart -hostname mail.example.com admin@example.com 显式指定。

8.3 运行 quickstart

cd /home/mox
./mox quickstart admin@example.com

实测输出(全文实录,密码为 quickstart 随机生成——每台机器都不同):

Checking if DNS resolvers are DNSSEC-verifying...

WARNING: It looks like the DNS resolvers configured on your system do not
verify DNSSEC, or aren't trusted (by having loopback IPs or through "options
trust-ad" in /etc/resolv.conf).  Without DNSSEC, outbound delivery with SMTP
used unprotected MX records, and SMTP STARTTLS connections cannot verify the TLS
certificate with DANE (based on a public key in DNS), and will fall back to
either MTA-STS for verification, or use "opportunistic TLS" with no certificate
verification.

Recommended action: Install unbound, a DNSSEC-verifying recursive DNS resolver,
ensure it has DNSSEC root keys (see unbound-anchor), and enable support for
"extended dns errors" (EDE, available since unbound v1.16.0, see below; not
required, but it gives helpful error messages about DNSSEC failures instead of
generic DNS SERVFAIL errors).

cat <<EOF >/etc/unbound/unbound.conf.d/ede.conf
server:
    ede: yes
    val-log-level: 2
EOF

Troubleshooting hints:
- Ensure /etc/resolv.conf has "nameserver 127.0.0.1". If the IP is 127.0.0.53,
  DNS resolving is done by systemd-resolved. Make sure "resolvconf" isn't
  overwriting /etc/resolv.conf (Debian has a package "openresolv" that makes this
  easier). "dig" also shows to which IP the DNS request was sent.
- Ensure unbound has DNSSEC root keys available. See unbound config option
  "auto-trust-anchor-file" and the unbound-anchor command. Ensure the file exists.
- Run "./mox dns lookup ns com." to simulate the DNSSEC check done by mox. The
  output should say "with dnssec".
- The "delv" command can check whether a domain is DNSSEC-signed, but it does
  its own DNSSEC verification instead of relying on the resolver, so you cannot
  use it to check whether unbound is verifying DNSSEC correctly.
- Increase logging in unbound, see options "verbosity" and "log-queries".

Looking up IPs for hostname mail.example.com...

NOTE: It looks like the DNS records of your domain (zone) are not DNSSEC-signed.
Mail servers that send email to your domain, or receive email from your domain,
cannot verify that the MX/SPF/DKIM/DMARC/MTA-STS records they receive are
authentic. DANE, for authenticated delivery without relying on a pool of
certificate authorities, requires DNSSEC, so will not be configured at this
time.
Recommended action: Continue now, but consider enabling DNSSEC for your domain
later at your DNS operator, and adding DANE records for protecting incoming
messages over SMTP.

Checking if outgoing smtp connections can be made by connecting to gmail.com mx on port 25... OK

Checking if domain example.com was registered recently... 31 years, 75 days OK

Checking whether host name IPs are listed in popular DNS block lists... OK

Admin password: mXp%rQm:Xr%;
IMAP, SMTP submission and HTTP account password for admin@example.com: ,fYxt1<>FSI^

When configuring your email client, use the email address as username. If
autoconfig/autodiscover does not work, use these settings:
Protocol             Host                            Port Listener        Note
Submission (SMTP)    mail.example.com                 465 public          with TLS
IMAP                 mail.example.com                 993 public          with TLS

To prevent authentication mechanism downgrade attempts that may result in
clients sending plain text passwords to a MitM, clients should always be
explicitly configured with the most secure authentication mechanism supported,
the first of: SCRAM-SHA-256-PLUS, SCRAM-SHA-1-PLUS, SCRAM-SHA-256, SCRAM-SHA-1,
CRAM-MD5.

Configuration files have been written to config/mox.conf and
config/domains.conf. You should review them. Then create the DNS records below,
by adding them to your zone file or through the web interface of your DNS
operator. You can also skip creating the DNS records and start mox immediately.
The admin interface can show these same records, and has a page to check they
have been configured correctly. The DNS records to add:


; Time To Live of 5 minutes, may be recognized if importing as a zone file.
; Once your setup is working, you may want to increase the TTL.
$TTL 300

; DANE: These records indicate that a remote mail server trying to deliver email
; with SMTP (TCP port 25) must verify the TLS certificate with DANE-EE (3), based
; on the certificate public key ("SPKI", 1) that is SHA2-256-hashed (1) to the
; hexadecimal hash. DANE-EE verification means only the certificate or public
; key is verified, not whether the certificate is signed by a (centralized)
; certificate authority (CA), is expired, or matches the host name.
;
; NOTE: Create the records below only once: They are for the machine, and apply
; to all hosted domains.
;
; WARNING: Domain does not appear to be DNSSEC-signed. To enable DANE, first
; enable DNSSEC on your domain, then add the TLSA records. Records below have been
; commented out.
;; _25._tcp.mail.example.com.   TLSA 3 1 1 7fc387983fac1c4ff8c9f9ef5e60bf5637b5357a660bd365ff4ac6ba08c1a424
;; _25._tcp.mail.example.com.   TLSA 3 1 1 08a704ee16876b350e8e01ecec63ed4ec242977e7d29b29b97bf45301ae63aac

; For the machine, only needs to be created once, for the first domain added:
; 
; SPF-allow host for itself, resulting in relaxed DMARC pass for (postmaster)
; messages (DSNs) sent from host:
mail.example.com.               TXT "v=spf1 a -all"

; For the machine, only needs to be created once, for the first domain added:
; 
; Request reporting about success/failures of TLS connections to (MX) host, for DANE.
_smtp._tls.mail.example.com.            TXT "v=TLSRPTv1; rua=mailto:tlsreports@mail.example.com"

; Deliver email for the domain to this host.
example.com.                    MX 10 mail.example.com.

; Outgoing messages will be signed with the first two DKIM keys. The other two
; configured for backup, switching to them is just a config change.
; NOTE: The following is a single long record split over several lines for use
; in zone files. When adding through a DNS operator web interface, combine the
; strings into a single string, without ().
2026a._domainkey.example.com.   TXT (
 		"v=DKIM1;h=sha256;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7f+BkLIhH34dktBfoFScADtK8VFnO0V9DrcnH"
 		"HpcjQ8HJjhZIVbitWGI79Riw3lWp6UnHEpB0sDi629NR9b9ThDLBm9IJki+dJUeBPzfaOwOvMrUi2eiNLc2YfR7rZ4y3CZ3REGFC"
 		"3Uf6wu4IXmkRQ38nVl7Xtl/HlCo2J1i4gSb45PqyJFxcSLJuhFmNQ+Jo3YFjimYn3lmAjFQ0JUcKZWA1O7Tz9x/DdV0ZCll05ESe"
 		"2cCcaTpqPPJ3/UpZQXqOUR5YPqNPe0LP4vHz5802Rg9DKIyo3uTxU0eFwUyunN0x7GhkYdQFLRkw/KL9n4+/thixeT26mtcL1dv4"
 		"Bc+QQIDAQAB"
	)
; NOTE: The following is a single long record split over several lines for use
; in zone files. When adding through a DNS operator web interface, combine the
; strings into a single string, without ().
2026b._domainkey.example.com.   TXT (
 		"v=DKIM1;h=sha256;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzvNZMEnGD2bN9tU7uYP6DonQ3DVd2dEH3RG7X"
 		"Jbozd/NkOeSU46yolrCg8mQuyyqyIA3hs+BOq357ooKD/Y0lyR77A1xhtAhU3CSUdqY/FJeeAG+Tl/+hk9lABDBkdEVHPILFJtQ5"
 		"8QkI+liV854gV7Md5Z0x7hQFMVJ7qZiOtsE8F7W1dkdXTR85SQYJIiAHt6NpPEbAvDG2DeWKDwk8mpYUN7aX9woTFe97644kPNm3"
 		"33yF3yh3981dEqjIWDRgbE76GQhaYgnOogSqDoRJ+kumCh9j4m0k3OxmuKaeIJtdSTclgmJ0+y3luk151KT1I8MGjzAizZrxPZZr"
 		"Q1yYQIDAQAB"
	)

; Specify the MX host is allowed to send for our domain and for itself (for DSNs).
; ~all means softfail for anything else, which is done instead of -all to prevent older
; mail servers from rejecting the message because they never get to looking for a dkim/dmarc pass.
example.com.                    TXT "v=spf1 ip4:10.89.58.2 mx ~all"

; Emails that fail the DMARC check (without aligned DKIM and without aligned SPF)
; should be rejected, and request reports. If you email through mailing lists that
; strip DKIM-Signature headers and don't rewrite the From header, you may want to
; set the policy to p=none.
_dmarc.example.com.             TXT "v=DMARC1;p=reject;rua=mailto:dmarcreports@example.com!10m"

; Remote servers can use MTA-STS to verify our TLS certificate with the
; WebPKI pool of CA's (certificate authorities) when delivering over SMTP with
; STARTTLS.
mta-sts.example.com.            CNAME mail.example.com.
_mta-sts.example.com.           TXT "v=STSv1; id=20261021T004510"

; Request reporting about TLS failures.
_smtp._tls.example.com.         TXT "v=TLSRPTv1; rua=mailto:tlsreports@example.com"

; Autoconfig is used by Thunderbird. Autodiscover is (in theory) used by Microsoft.
autoconfig.example.com.         CNAME mail.example.com.
_autodiscover._tcp.example.com. SRV 0 1 443 mail.example.com.

; For secure IMAP and submission autoconfig, point to mail host.
_imaps._tcp.example.com.        SRV 0 1 993 mail.example.com.
_submissions._tcp.example.com.  SRV 0 1 465 mail.example.com.

; Next records specify POP3 and non-TLS ports are not to be used.
; These are optional and safe to leave out (e.g. if you have to click a lot in a
; DNS admin web interface).
_imap._tcp.example.com.         SRV 0 0 0 .
_submission._tcp.example.com.   SRV 0 0 0 .
_pop3._tcp.example.com.         SRV 0 0 0 .
_pop3s._tcp.example.com.        SRV 0 0 0 .

; Optional:
; You could mark Let's Encrypt as the only Certificate Authority allowed to
; sign TLS certificates for your domain.
example.com.                    CAA 0 issue "letsencrypt.org"
;
; Note: After starting up, once an ACME account has been created, CAA records
; that restrict issuance to the account will be suggested.



WARNING: The configuration and DNS records above assume you do not currently
have email configured for your domain. If you do already have email configured,
or if you are sending email for your domain from other machines/services, you
should understand the consequences of the DNS records above before
continuing!

You can now start mox with "./mox serve", as root.

File ownership and permissions are automatically set correctly by mox when
starting up. On linux, you may want to enable mox as a systemd service.

See mox.service for a systemd service file. To enable and start:

	sudo chmod 644 mox.service
	sudo systemctl enable $PWD/mox.service
	sudo systemctl start mox.service
	sudo journalctl -f -u mox.service # See logs

After starting mox, the web interfaces are served at:

http://localhost/         - account (email address as username)
http://localhost/webmail/ - webmail (email address as username)
http://localhost/admin/   - admin (empty username)

To access these from your browser, run
"ssh -L 8080:localhost:80 you@yourmachine" locally and open
http://localhost:8080/[...].

If you run into problem, have questions/feedback or found a bug, please let us
know. Mox needs your help!

Enjoy!

PS: If you want to run mox along side an existing webserver that uses port 443
and 80, see "mox help quickstart" with the -existing-webserver option.

(output is also written to quickstart.log)

💡 技术拓展:quickstart 顶部反复出现 DNSSEC/DANE 字样,二者是现代邮件安全的信任链——DNSSEC 为域名 DNS 记录本身提供密码学签名验证;DANE 则基于 DNSSEC 发布的 TLSA 记录验证 SMTP 对端的 TLS 证书,不再依赖商业 CA。二者之外还有 MTA-STS(用 HTTPS 发布 TLS 策略,DNSSEC 非必需)与 TLSRPT(TLS 失败报告)。主流方案对比:

机制依赖作用适用场景
DNSSEC域名运营商开启签名DNS 记录防篡改一切 DANE/高可信 DNS 的基础
DANEDNSSEC + TLSA 记录按公钥验证 SMTP TLS 证书双方都支持时最严格的传输认证
MTA-STSHTTPS + DNS TXT策略化的强制 STARTTLS不依赖 DNSSEC 的主流方案
TLSRPTDNS TXT接收对端发来的 TLS 失败报告可观测性,常与上两者同用

Mox 对四者全部原生支持(quickstart 一次性打印全部记录)。本文实测环境域名未开 DNSSEC,故 DANE 记录被自动注释,走 MTA-STS 路线;生产环境建议按警告提示安装 unbound 并开启域名 DNSSEC。

8.4 按 quickstart 建议安装 unbound(推荐)

quickstart 明确推荐安装 DNSSEC 验证解析器 unbound 并启用 EDE(扩展 DNS 错误)。按其打印的命令操作:

apt -y install unbound
cat <<EOF >/etc/unbound/unbound.conf.d/ede.conf
server:
    ede: yes
    val-log-level: 2
EOF
systemctl restart unbound

实测输出(安装段节选):

Setting up unbound (1.13.1-1ubuntu5.16) ...
Generating unbound configuration file for DNSSEC validation...

确认解析器在本地监听:

ss -ltn | grep unbound

实测输出:

LISTEN 0      256        127.0.0.1:53        0.0.0.0:*     users:(("unbound",pid=7509,fd=4))
LISTEN 0      256        127.0.0.1:8953      0.0.0.0:*     users:(("unbound",pid=7509,fd=6))

再把 /etc/resolv.conf 的 nameserver 指向 127.0.0.1(按 quickstart 排障提示,若当前是 127.0.0.53 则解析走 systemd-resolved,无法验证 DNSSEC)。完成后可用 quickstart 给出的自检命令验证:

cd /home/mox && ./mox dns lookup ns com.

实测输出:

ns1.gtld-servers.net. a.iana-servers.net. b.iana-servers.net. c.iana-servers.net. d.iana-servers.net. e.iana-servers.net. f.iana-servers.net. g.iana-servers.net. h.iana-servers.net. i.iana-servers.net. j.iana-servers.net. k.iana-servers.net. l.iana-servers.net. m.iana-servers.net., with dnssec

输出末尾出现 with dnssec 即表示 DNSSEC 验证链路已打通。

8.5 修改初始密码为测试口令

quickstart 生成的两个随机密码建议尽快替换为自己管理的口令(本文演示环境统一使用测试口令 q1w2e3r4,生产环境请用强口令)。管理后台密码用 mox setadminpassword(从 stdin 读入,回显输入),账号密码用 mox setaccountpassword:

cd /home/mox
printf 'q1w2e3r4\nq1w2e3r4\n' | ./mox setadminpassword
printf 'q1w2e3r4\nq1w2e3r4\n' | ./mox setaccountpassword admin

实测输出(两条命令同构):


Type new password. Password WILL echo.

WARNING: Bots will try to bruteforce your password. Connections with failed
authentication attempts will be rate limited but attackers WILL find passwords
reused at other services and weak passwords. If your account is compromised,
spammers are likely to abuse your system, spamming your address and the wider
internet in your name. So please pick a random, unguessable password, preferably
at least 12 characters.

password: 

密码立即生效(存储的是 SCRAM/bcrypt 派生密钥,不是明文)。验证方式见 10.3 节的 Web 登录测试。

九、systemd 服务托管

quickstart 已在 /home/mox/mox.service 生成服务文件,把它装入 systemd 即可实现开机自启与崩溃自动拉起。

9.1 启用服务

cd /home/mox
chmod 644 mox.service
systemctl enable /home/mox/mox.service
systemctl start mox.service

实测输出:

Created symlink /etc/systemd/system/multi-user.target.wants/mox.service → /home/mox/mox.service.

确认状态:

systemctl is-active mox
systemctl is-enabled mox

实测输出:

active
enabled

9.2 服务文件内容

quickstart 生成的 mox.service 全文(实测 /etc/systemd/system/mox.service):

[Unit]
Description=mox mail server
After=network-online.target
Wants=network-online.target

[Service]
UMask=007
LimitNOFILE=65535
Type=simple
# Mox starts as root, but drops privileges after binding network addresses.
WorkingDirectory=/home/mox
ExecStart=/home/mox/mox serve
RestartSec=5s
Restart=always
ExecStop=/home/mox/mox stop
SyslogFacility=mail

# Isolate process, reducing attack surface.
PrivateDevices=yes
PrivateTmp=yes
ProtectSystem=strict
ReadWritePaths=/home/mox/config /home/mox/data
ProtectKernelTunables=yes
ProtectControlGroups=yes
AmbientCapabilities=
CapabilityBoundingSet=CAP_SETUID CAP_SETGID CAP_NET_BIND_SERVICE CAP_CHOWN CAP_FSETID CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_FOWNER CAP_KILL
NoNewPrivileges=yes
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX AF_NETLINK
ProtectProc=invisible
RestrictNamespaces=yes
RestrictRealtime=yes
RemoveIPC=yes
ProtectHostname=yes
ProtectClock=yes
ProtectKernelLogs=yes
ProtectKernelModules=yes
MemoryDenyWriteExecute=yes
LockPersonality=yes
DevicePolicy=closed
SystemCallArchitectures=native
SystemCallFilter=@system-service

# Cannot have RestrictSUIDSGID with setgid directories.
#RestrictSUIDSGID=yes

# To check security-related settings:
# sudo systemd-analyze security mox.service

[Install]
WantedBy=multi-user.target

逐项解读关键行:

  • WorkingDirectory=/home/mox:mox 的工作目录,相对路径配置(config/mox.conf)都基于它;
  • ExecStart=/home/mox/mox serve:以 root 启动、绑定 25/465/993/80/443 等特权端口后自动降权到 mox 用户(服务日志会打印 starting as unprivileged user ... user=mox uid=1000 gid=1000);
  • ExecStop=/home/mox/mox stop:优雅停机走 mox 自身的 ctl 通道;
  • Restart=always + RestartSec=5s:异常退出 5 秒后自动拉起;
  • ProtectSystem=strict + ReadWritePaths=...:除 config/data 目录外整个文件系统只读,大幅收窄入侵后的可写面;
  • CapabilityBoundingSet=...:只保留绑定低端口(CAP_NET_BIND_SERVICE)与降权(CAP_SETUID/SETGID)等最小能力集;
  • SystemCallFilter=@system-service:系统调用白名单过滤。

可用 systemd 自带评分工具查看安全加固效果:

systemd-analyze security mox.service | tail -3

实测输出:

→ Overall exposure level for mox.service: 1.7 SAFE 🙂

十、运行验证

服务启动后,按「进程与监听 → 协议端口 → Web 界面 → 日志」四步验证安装是否完整成功。

10.1 服务进程与监听端口

ss -ltnp | grep mox

实测输出:

LISTEN 0      4096      10.89.58.2:993        0.0.0.0:*     users:(("mox",pid=7419,fd=14),("mox",pid=7406,fd=11))
LISTEN 0      4096      10.89.58.2:465        0.0.0.0:*     users:(("mox",pid=7419,fd=13),("mox",pid=7406,fd=9))
LISTEN 0      4096      10.89.58.2:443        0.0.0.0:*     users:(("mox",pid=7419,fd=22),("mox",pid=7406,fd=23))
LISTEN 0      4096      10.89.58.2:80         0.0.0.0:*     users:(("mox",pid=7419,fd=21),("mox",pid=7406,fd=21))
LISTEN 0      4096      10.89.58.2:25         0.0.0.0:*     users:(("mox",pid=7419,fd=12),("mox",pid=7406,fd=3))
LISTEN 0      4096      127.0.0.1:8010        0.0.0.0:*     users:(("mox",pid=7419,fd=19),("mox",pid=7406,fd=17))
LISTEN 0      4096      127.0.0.1:80          0.0.0.0:*     users:(("mox",pid=7419,fd=17),("mox",pid=7406,fd=13))
LISTEN 0      4096      [::1]:8010            [::]:*        users:(("mox",pid=7419,fd=20),("mox",pid=7406,fd=19))
LISTEN 0      4096      [::1]:80              [::]:*        users:(("mox",pid=7419,fd=18),("mox",pid=7406,fd=15))

解读:对外网卡 IP(10.89.58.2)监听邮件四端口 25/465/993 与 Web 双端口 80/443(对应配置里的 public 监听器);回环地址监听管理/账号/Webmail 界面与 Prometheus 指标端 8010(对应 internal 监听器,只在 localhost 可达)。每个端口能看到两个 mox 进程 PID——root 主进程与降权后的工作进程。

10.2 SMTP/IMAP 端口握手验证

用系统自带的 openssl 验证 465(SMTP over TLS)与 993(IMAP over TLS)的真实握手:

timeout 5 openssl s_client -connect 10.89.58.2:465 -brief </dev/null 2>&1 | head -5
timeout 5 openssl s_client -connect 10.89.58.2:993 -brief </dev/null 2>&1 | head -5

实测输出(465):

CONNECTION ESTABLISHED
Protocol version: TLSv1.3
Ciphersuite: TLS_AES_256_GCM_SHA384
Peer certificate: mail.example.com
Hash used: SHA384

实测输出(993):

CONNECTION ESTABLISHED
Protocol version: TLSv1.3
Ciphersuite: TLS_AES_256_GCM_SHA384
Peer certificate: mail.example.com
Hash used: SHA384

两个端口均完成 TLS 1.3 握手并出示 mail.example.com 证书(ACME 自动签发),协议栈工作正常。

10.3 Web 界面验证

curl -sS -o /dev/null -w "account(webmail): %{http_code}\n" http://localhost/
curl -sS -o /dev/null -w "webmail: %{http_code}\n" http://localhost/webmail/
curl -sS -o /dev/null -w "admin: %{http_code}\n" http://localhost/admin/
curl -sS -o /dev/null -w "account-login: %{http_code}\n" -u "admin@example.com:q1w2e3r4" http://localhost/

实测输出:

account(webmail): 200
webmail: 200
admin: 200
account-login: 200

前三个 200 说明三套 Web 界面(账号页/Webmail/管理后台)可达;最后一行带 -u admin@example.com:q1w2e3r4 的 200 说明 8.5 节设置的账号密码已生效。管理后台用户名为空、直接输管理密码;账号页与 Webmail 用完整邮箱地址作用户名。

10.4 服务日志

journalctl -u mox -n 12 --no-pager

实测输出:

Oct 21 10:36:17 mail.example.com mox[8301]: l=print m="listening for smtp" pkg=smtpserver listener=public address=10.89.58.2:465 protocol=submissions
Oct 21 10:36:17 mail.example.com mox[8301]: l=print m="listening for imap" pkg=imapserver listener=public addr=10.89.58.2:993 protocol=imaps
Oct 21 10:36:17 mail.example.com mox[8301]: l=print m="http listener" pkg=http name=internal kinds="account-http at /,admin-http at /admin/,webapi-http at /webapi/,webmail-http at /webmail/" address=127.0.0.1:80
Oct 21 10:36:17 mail.example.com mox[8301]: l=print m="http listener" pkg=http name=internal kinds="account-http at /,admin-http at /admin/,webapi-http at /webapi/,webmail-http at /webmail/" address=[::1]:80
Oct 21 10:36:17 mail.example.com mox[8301]: l=print m="http listener" pkg=http name=internal kinds=metrics-http address=127.0.0.1:8010
Oct 21 10:36:17 mail.example.com mox[8301]: l=print m="http listener" pkg=http name=internal kinds=metrics-http address=[::1]:8010
Oct 21 10:36:17 mail.example.com mox[8301]: l=print m="http listener" pkg=http name=public kinds=webserver-http address=10.89.58.2:80
Oct 21 10:36:17 mail.example.com mox[8301]: l=print m="https listener" pkg=http name=public kinds=autoconfig-https,mtasts-https,webserver-https address=10.89.58.2:443
Oct 21 10:36:17 mail.example.com mox[8313]: l=print m="starting as unprivileged user" pkg=serve user=mox uid=1000 gid=1000 pid=8313
Oct 21 10:36:17 mail.example.com mox[8313]: l=info m="sending tls reports" pkg=tlsrptsend day=20261020 cid=1a15e088086
Oct 21 10:36:17 mail.example.com mox[8313]: l=info m="finished sending tls reports" pkg=tlsrptsend cid=1a15e088086
Oct 21 10:36:17 mail.example.com mox[8313]: l=print m="ready to serve" pkg=serve

ready to serve 出现即服务就绪。日志同时印证了 9.2 节的降权行为(PID 8301 root 绑定端口 → PID 8313 mox 用户继续服务)。

十一、数据备份与校验

mox 内置在线备份命令 mox backup(不停服、不锁收发),配合 mox verifydata 校验备份完整性,两步构成最小备份闭环。

11.1 在线备份

cd /home/mox
./mox backup data/tmp/testupgrade
echo "backup exit=$?"
ls data/tmp/testupgrade/

实测输出:

backup exit=0
config
data

⚠️ 排障实录:备份目标目录必须是 mox 用户可写的路径。把目标写成 /root/moxbackup 会得到成片的 open /root/moxbackup/...: permission denied 错误——因为 mox 进程在服务期已降权为 mox 用户(uid 1000),对 /root 无写权限。把备份放在 /home/mox/data/tmp/ 下(mox 用户家目录内)即可;若想备份到其他路径,先 mkdir <dir> && chown mox:mox <dir>。

11.2 校验备份数据

cd /home/mox
./mox verifydata data/tmp/testupgrade/data
echo "verify exit=$?"
ls data/tmp/testupgrade/data/

实测输出:

data/tmp/testupgrade/data: OK
verify exit=0
accounts
acme
auth.db
dmarceval.db
dmarcrpt.db
moxversion
mtasts.db
queue
receivedid.key
tlsrpt.db
tlsrptresult.db

data: OK 一行即校验通过:备份包含全部账号数据库(auth.db)、队列(queue/)、各报表库(dmarc/tlsrpt/mtasts)与 moxversion 版本标记,可直接用于恢复演练或升级前快照。

十二、配置说明

安装后的全部配置收敛在两个文件,位于 /home/mox/config/:

  • mox.conf——静态配置:服务器级参数(数据目录、日志级别、监听器、ACME、TLS、Webserver 等)。官方文档明确:运行期不会热重载,修改后需 systemctl restart mox 生效;
  • domains.conf——动态配置:域名、账号、投递规则等。文件保存后自动重载,出错则放弃本次重载、沿用旧配置。

两个文件的格式为 sconf:只能用 Tab 缩进、注释必须独占一行(以 # 开头)、字符串不加引号。改配置前后都用内置语法检查兜底:

cd /home/mox
./mox config test

实测输出:

config OK

查看带注释的完整参数参考(生成空模板,注释即官方文档):

./mox config describe-static > mox-static-reference.conf   # mox.conf 全量参考
./mox config describe-domains > domains-reference.conf     # domains.conf 全量参考
wc -l mox-static-reference.conf domains-reference.conf

实测输出:

  1044 mox-static-reference.conf
   743 domains-reference.conf

当前环境实际生效的关键配置(/home/mox/config/mox.conf,节选实测):

DataDir: ../data
LogLevel: debug
User: mox
Hostname: mail.example.com
  • DataDir: ../data:数据目录,相对 mox.conf 所在目录解析;
  • LogLevel: debug:默认日志级别,生产可改 info 减量(修改需重启,实验实录见 12.1);
  • User: mox:root 绑定端口后的降权目标用户;
  • Hostname: mail.example.com:服务器对外主机名(MX/HELO/TLS 证书主体)。

12.1 实测:静态配置修改 → 重启 → 验证

以日志级别为例走一遍 mox.conf 的修改闭环。把 LogLevel: debug 改为 info:

cd /home/mox
sed -i 's/^LogLevel: debug$/LogLevel: info/' config/mox.conf
grep -n '^LogLevel' config/mox.conf
./mox config test
systemctl restart mox
sleep 2
systemctl is-active mox

实测输出:

15:LogLevel: info
config OK
active

验证日志级别确实生效——分别统计重启前后进程的 debug 日志条数:

journalctl -u mox -q --no-pager _PID=8301 | grep -c 'l=debug'   # 重启后(info)
journalctl -u mox -q --no-pager _PID=7419 | grep -c 'l=debug'   # 重启前(debug)

实测输出:

0
110

重启后 debug 日志从 110 条降为 0 条,修改闭环验证完成。演示完把级别改回 debug 并重启即可(sed -i 's/^LogLevel: info$/LogLevel: debug/' config/mox.conf && systemctl restart mox)。

12.2 实测:动态配置自动重载

domains.conf 无需重启。实测用 mox config domain add 添加域名(该命令改写 domains.conf 并触发重载):

cd /home/mox
./mox config domain add example.net admin

实测输出:

domain added, remember to add dns records, see:

mox config dnsrecords example.net
mox config dnscheck example.net

确认文件与日志同步变化:

grep -c 'example.net' config/domains.conf
journalctl -u mox -n 3 --no-pager -q | grep -oE 'm="[a-z ]+"' | head -2
./mox config domain rm example.net
grep -c 'example.net' config/domains.conf

实测输出:

1
m="domain added"
domain removed, remember to remove dns records for example.net
0

域名 add/rm 即时写入并即时生效(日志 domain added/domain removed,全程未重启服务)。配置参数的逐段详解见 《Mox v0.0.17 配置参数详解》,运维命令的逐条实测见 《Mox 常用 CLI 命令详解》。

十三、常见问题(FAQ)

Q1:./mox version 或运行二进制时报 Permission denied? 二进制放在了 /tmp 下。Ubuntu 22.04 的 /tmp 以 noexec 挂载(mount | grep ' /tmp ' 可见 noexec),把二进制移到家目录即可:mv /tmp/mox /root/。

Q2:go install 报 dial tcp ...443: connect: connection refused? Go 默认模块代理 proxy.golang.org 在境内不可达。走 HTTP 代理(export https_proxy=http://<代理>:<端口>)或换国内代理(export GOPROXY=https://goproxy.cn,direct)后重试。

Q3:mox backup 报成片 permission denied? 备份目标目录对 mox 运行用户(服务期已降权)不可写。目标目录须满足:mkdir -p <dir> && chown mox:mox <dir>,或直接放在 /home/mox/data/tmp/ 下。

Q4:mox backup 报 open config/mox.conf: no such file or directory (hint: use mox -config ... or set MOXCONF=...)? mox 子命令按当前工作目录找 config/mox.conf。先 cd /home/mox 再执行,或显式 ./mox -config /home/mox/config/mox.conf backup <目录>。

Q5:mox setaccountpassword admin@example.com 报 open account: no such account? 该命令第一个参数是账号名(quickstart 里 admin@example.com 对应的账号名是 admin),不是邮箱地址:./mox setaccountpassword admin。

Q6:quickstart 警告 DNS resolvers 不验证 DNSSEC? 系统解析器是 systemd-resolved(/etc/resolv.conf 里 127.0.0.53)或普通缓存时无法验证 DNSSEC。按 8.4 节安装 unbound、启用 EDE、把 /etc/resolv.conf 指向 127.0.0.1,用 ./mox dns lookup ns com. 验证输出带 with dnssec。

Q7:修改 mox.conf 后不生效? 静态配置不支持热重载。先 ./mox config test 确认语法(输出 config OK),再 systemctl restart mox。domains.conf 则保存即自动生效,无需重启。

Q8:25 端口连不通、收不到外部来信? 依次检查:云厂商安全组是否放行 25(多数云默认封禁)、域名 MX 记录是否指向本机主机名、防火墙(ufw allow 25,465,993,80,443/tcp)。可用 ss -ltnp | grep ':25 ' 先确认本机在监听。

十四、卸载与清理

完整卸载按「停服务 → 移除 systemd 单元 → 删用户与数据」顺序执行:

systemctl stop mox
systemctl disable /home/mox/mox.service
rm -f /etc/systemd/system/multi-user.target.wants/mox.service /etc/systemd/system/mox.service
systemctl daemon-reload

实测输出:

Removed "/etc/systemd/system/multi-user.target.wants/mox.service".

删除数据与用户(不可逆,确认备份后执行):

rm -rf /home/mox
userdel mox

若按 8.4 节装过 unbound 且不再需要,一并卸载:apt -y purge unbound && apt -y autoremove。

十五、参考链接