Stalwart v0.16.26 配置参数详解

Stalwart v0.16 配置体系三层模型实测详解:config.json 引导配置、环境变量与 JMAP 管理对象(监听器/认证策略/账号/域名/DKIM)逐字段解读与生效规则汇总

StalwartStalwart邮件服务器2026/10/18

Stalwart v0.16.26 配置参数详解

目标:以 v0.16.26 实测为基础,讲透 Stalwart 的配置体系——从只存"引导信息"的 config.json,到存于数据存储中的系统设置、监听器、认证策略、账号与域名、DKIM 签名等配置对象,逐段给出实测数据、字段逐项解说与生效规则。所有回显均在 Ubuntu 22.04 + Stalwart v0.16.26 实测通过,可直接对照自己的实例验证。

一、环境信息

本文沿用 《Ubuntu 22.04 安装 Stalwart v0.16.26 指南》完成初始化的同套实测环境,配置对象均已实际创建并验证。

1.1 基础环境

  • 操作系统:Ubuntu 22.04
  • Stalwart 版本:v0.16.26
  • 部署方式:官方目录布局(配置 /etc/stalwart、数据 /var/lib/stalwart、日志 /var/log/stalwart),systemd 托管
  • 服务器主机名:mail.lab.example.com,默认域 lab.example.com
  • 管理员:admin@lab.example.com(密码 q1w2e3r4);测试账号:demo@lab.example.com
  • 管理 API 入口:http://127.0.0.1:8080/jmap(下文所有配置读写都经此端点)

1.2 配置体系总览

动任何参数前先建立整体认知。v0.16 的配置分为三层,各层的存放位置、变更工具、生效方式完全不同:

层存放位置内容变更方式
引导层/etc/stalwart/config.json数据存储后端连接 + 向导写入的系统设置手工编辑 / 向导写回,重启生效
环境层/etc/stalwart/stalwart.env进程环境变量(如恢复管理员、代理)手工编辑,重启生效
对象层数据存储内(RocksDB 等)监听器、账号、域名、DKIM、密码策略、队列……JMAP 管理 API / WebAdmin / stalwart-cli,部分需重启

三层之外的"运行态"(队列消息、日志、遥测)也在数据存储内,属运维对象而非配置对象。下文按层展开。

二、引导层:config.json 逐项详解

config.json 是 Stalwart 启动时读的第一个文件,决定"去哪里取其余全部配置"。看一份实测文件:

cat /etc/stalwart/config.json

实测输出(初始化完成后的完整内容,为便于阅读做了格式化):

{
  "@type": "RocksDb",
  "path": "/var/lib/stalwart/",
  "blobSize": 16834,
  "bufferSize": 134217728,
  "poolWorkers": null,
  "cacheSize": 134217728,
  "server": {
    "hostname": "mail.lab.example.com"
  },
  "session": {
    "defaultDomain": "lab.example.com"
  },
  "settings": {
    "mta": {
      "directory": { "defaultDomain": "lab.example.com" },
      "rcpt": { "domain": "lab.example.com" }
    }
  },
  "tls": {
    "acme": { "request": { "domains": [] } }
  }
}

2.1 数据存储段(@type/path/blobSize/bufferSize/poolWorkers/cacheSize)

这部分决定数据的物理落点,是 config.json 的本职:

字段实测值解说
@typeRocksDb存储后端类型。可选还有 PostgreSql、MySql、Sqlite、FoundationDb、S3(对象存储,常作 Blob 层)
path/var/lib/stalwart/RocksDB 数据目录(含索引与 Blob 文件),结尾斜杠是官方生成风格
blobSize16834(约 16KB)内联阈值:小于该值的 Blob 直接写入键值对,大于的走独立文件对象,小附件读放大更少
bufferSize134217728(128MB)RocksDB 写缓冲总大小,影响写吞吐与内存占用;小内存机器可降到 64MB
poolWorkersnull存储线程池 worker 数,null 为按 CPU 核数自动
cacheSize134217728(128MB)存储缓存大小,命中缓存可减少磁盘读

若要切换到外部数据库(例如高可用场景用 PostgreSQL),把对象替换为对应后端字段即可,形如 {"@type":"PostgreSql","host":"db.internal","port":5432,"database":"stalwart","user":"stalwart","password":"...","poolMax":20}——字段名以官方文档为准,切换前务必用 --export 做全量备份(见 《Stalwart 日常运维手册》)。

2.2 向导写入的系统设置段(server/session/settings/tls)

server、session、settings、tls 四段是初始化向导(x:Bootstrap/set)写回的结果:

  • server.hostname:服务器对外主机名,影响 SMTP 的 Banner、TLS 证书匹配与队列日志标识;
  • session.defaultDomain / settings.mta.directory.defaultDomain / settings.mta.rcpt.domain:三处共同把 lab.example.com 设为默认域——本地收件人解析的兜底域、登录名省略域名的补全域;
  • tls.acme.request.domains:ACME 自动证书要申请的域名清单,数组为空即未启用(实测环境没有公网 DNS,故向导时显式关闭了证书申请)。

2.3 生效规则与编辑注意

config.json 是启动引导文件:手工修改后必须重启 stalwart 服务才生效(systemctl restart stalwart)。另外三点实操注意:

  1. 该文件由向导/系统写回,手工编辑前后都要备份(cp config.json config.json.bak);
  2. 文件权限应为 0640 root:stalwart——服务要以组权限读它,且向导流程需要向 /etc/stalwart 目录写回文件,目录权限给 0750 root:stalwart(《Ubuntu 22.04 安装 Stalwart v0.16.26 指南》 5.4 节有权限不足的反面实测);
  3. JSON 语法错误会让服务启动失败,改完先用 python3 -m json.tool /etc/stalwart/config.json 校验。

三、环境层:stalwart.env

/etc/stalwart.env 由 systemd unit 的 EnvironmentFile=-/etc/stalwart/stalwart.env 加载,存放进程级环境变量。实测内容(初始化完成后清理了固定凭据):

cat /etc/stalwart/stalwart.env

实测输出:

# This file contains the environment variables for the Stalwart Mail Server.

常用变量一览(均为实测可用项):

变量用途备注
STALWART_RECOVERY_ADMIN引导模式/恢复模式的临时管理员凭据(user:pass)官方脚本默认生成随机值;初始化完成后建议删除
https_proxy / http_proxy出站代理(如 WebUI 资源包下载、外部 DNSBL 查询经代理)实测配置后 WebUI bundle 下载成功

改完重启生效:systemctl restart stalwart。

四、对象层:系统配置对象逐类详解

对象层是 v0.16 配置的主战场:一切以"类型化对象"存在数据存储中,用 JMAP 的 query/get/set 方法管理。方法是统一的:

x:<对象类型>/query  —— 列出对象 ID
x:<对象类型>/get    —— 读取属性
x:<对象类型>/set    —— 创建(create)/更新(update)/删除(destroy)

(x: 前缀表示 Stalwart 私有扩展对象,标准 JMAP 对象如 Email/Mailbox 无前缀。)下面按配置面逐类讲解,全部示例都在实测环境验证过。

4.1 网络监听器(NetworkListener)

监听器决定"在哪些地址端口提供哪些协议"。先列出全部监听器:

curl -s http://127.0.0.1:8080/jmap -u "admin@lab.example.com:q1w2e3r4" \
  -H "Content-Type: application/json" \
  -d '{"using":["urn:ietf:params:jmap:core","urn:stalwart:jmap"],"methodCalls":[["x:NetworkListener/query",{"filter":null},"c0"],["x:NetworkListener/get",{"#ids":{"resultOf":"c0","name":"x:NetworkListener/query","path":"/ids"},"properties":null},"c1"]]}' \
  | python3 -c "import json,sys;d=json.load(sys.stdin);[print(it['id'],'|',it['name'],'|',it['protocol'],'|',list(it['bind'].keys()),'| implicit:',it['tlsImplicit']) for it in d['methodResponses'][1][1]['list']]"

实测输出(9 个监听器,前 7 个为 v0.16 默认,后 2 个为手动补建):

jkc7rdqaaeqb | smtp        | smtp       | ['[::]:25']    | implicit: False
jkc7rdqaafab | submissions | smtp       | ['[::]:465']   | implicit: True
jkc79uvmaaaa | submission  | smtp       | ['[::]:587']   | implicit: False
jkc7rdqaafqb | imaps       | imap       | ['[::]:993']   | implicit: True
jkc79uvmaaqa | imap        | imap       | ['[::]:143']   | implicit: False
jkc7rdqaagab | pop3s       | pop3       | ['[::]:995']   | implicit: True
jkc7rdqaagqb | sieve       | manageSieve| ['[::]:4190']  | implicit: False
jkc7rdqcahab | https       | http       | ['[::]:443']   | implicit: True
jkc7rdqcahqb | http        | http       | ['[::]:8080']  | implicit: False

逐字段解读一个实测对象(https 监听器,属性最多的一类):

字段实测值解说
namehttps监听器名,日志与 WebAdmin 中显示
protocolhttp协议处理器:smtp/imap/pop3/manageSieve/http
bind{"[::]:443": true}键为"地址:端口"(IPv6 通配同时覆盖 IPv4),值 true 启用
useTlstrue该监听器启用 TLS 能力
tlsImplicittrue关键开关:true=隐式 TLS(连上即握手,465/993/995/443);false=明文起步、客户端 STARTTLS 升级(25/587/143)
socketBacklog1024内核 accept 队列长度
socketNoDelaytrue禁用 Nagle,降低交互延迟(邮件服务合理默认)
socketReuseAddress / socketReusePorttrue地址/端口复用,重启不等待 TIME_WAIT
maxConnections8192该监听器最大并发连接
tlsTimeout60000TLS 握手超时(毫秒)
tlsDisableProtocols / tlsDisableCipherSuites{}要禁用的 TLS 协议版本/套件黑名单,默认全启用

创建/修改监听器的实测例子见 《Ubuntu 22.04 安装 Stalwart v0.16.26 指南》 3.7 节(587/143 补建)。生效规则:监听器变更必须重启服务(实测创建后 587/143 端口在重启后才出现)。

4.2 认证策略(Authentication)

认证策略是 singleton 对象(全局唯一),控制密码强度、失败锁定等。读取:

curl -s http://127.0.0.1:8080/jmap -u "admin@lab.example.com:q1w2e3r4" \
  -H "Content-Type: application/json" \
  -d '{"using":["urn:ietf:params:jmap:core","urn:stalwart:jmap"],"methodCalls":[["x:Authentication/get",{"ids":["singleton"]},"c0"]]}' \
  | python3 -c "import json,sys;print(json.dumps(json.load(sys.stdin)['methodResponses'][0][1]['list'][0],indent=1))"

实测输出(节选核心字段):

{
  "id": "singleton",
  "passwordMinStrength": "zero",
  "enableRateLimiting": true,
  "rate": { "requests": 30, "period": "1m" },
  "authAttempt": { "limit": 6, "delay": 3000, "expiresIn": 30000 },
  ...
}

关键字段解说:

  • passwordMinStrength:密码强度下限(实测已调为 zero)。默认 low,会拒绝 q1w2e3r4 这类弱口令(实测报 secret is too weak);可选档位从松到严为 zero/low/medium/high/extreme,生产环境建议至少 medium;
  • enableRateLimiting + rate:认证接口限流(实测默认每分钟 30 次);
  • authAttempt:失败尝试惩罚(默认 6 次失败后逐步延迟、窗口 30 秒)。

生效规则实测:修改该对象返回成功,但运行中的服务策略不变,需重启才真正生效(《Ubuntu 22.04 安装 Stalwart v0.16.26 指南》 2.6 节的两段实测:改完不重启改密仍被拒,重启后成功)。同时它是受保护配置——初次修改时曾返回 passwordMinStrength was not honored due to insufficient privileges,重启服务后同一调用成功,遇到时按"改完重启"处理。

4.3 账号与凭据(Account / User)

账号体系核心是 User 对象(挂在主账号 Account 下),实测创建 demo@lab.example.com 的完整调用:

curl -s http://127.0.0.1:8080/jmap -u "admin@lab.example.com:q1w2e3r4" \
  -H "Content-Type: application/json" \
  -d '{"using":["urn:ietf:params:jmap:core","urn:stalwart:jmap"],"methodCalls":[["x:Account/set",{"create":{"demo":{"@type":"User","name":"demo","domainId":"b","credentials":{"0":{"@type":"Password","secret":"q1w2e3r4"}},"description":"Demo test account"}}},"c0"]]}' \
  | python3 -c "import json,sys;print(json.dumps(json.load(sys.stdin)['methodResponses'][0][1],indent=1))"

实测输出:

{
  "accountId": "b",
  "newState": "48392017",
  "created": {
    "demo": { "id": "c" }
  }
}

字段与结构解说:

  • @type: "User":显式声明对象类型(管理面对象的固定写法,缺省会报 unknownType);
  • name:本地部分,与 domainId 指向的域组合成完整地址 demo@lab.example.com;
  • credentials:凭据字典,键为序号(0、1…),每项 @type 决定凭据类型——Password(口令)、Totp(两步验证)、AppPassword、OauthBearerToken 等,一个用户可并存多把凭据;
  • 返回的 id(c)是后续 IMAP/JMAP 用的 accountId。

常用运维写法:

# 改密码(路径式更新:账号ID/credentials/序号/secret)
'{"update":{"b":{"credentials/0/secret":"新密码"}}}'

# 停用账号(注意 isEnabled 在域对象上,用户账号用 description 或直接删除)
'{"update":{"c":{"description":"disabled 2026-10-18"}}}'

4.4 域名对象(Domain)

域名是收发策略的载体。实测读取默认域(ID b):

curl -s http://127.0.0.1:8080/jmap -u "admin@lab.example.com:q1w2e3r4" \
  -H "Content-Type: application/json" \
  -d '{"using":["urn:ietf:params:jmap:core","urn:stalwart:jmap"],"methodCalls":[["x:Domain/get",{"ids":["b"],"properties":null},"c0"]]}' \
  | python3 -c "import json,sys;print(json.dumps(json.load(sys.stdin)['methodResponses'][0][1]['list'][0],indent=1))"

实测输出(节选):

{
  "name": "lab.example.com",
  "isEnabled": true,
  "catchAllAddress": null,
  "subAddressing": { "@type": "Enabled" },
  "allowRelaying": false,
  "reportAddressUri": "mailto:postmaster",
  "certificateManagement": { "@type": "Manual" },
  "dkimManagement": {
    "@type": "Automatic",
    "algorithms": { "Dkim1Ed25519Sha256": true, "Dkim1RsaSha256": true },
    "selectorTemplate": "v{version}-{algorithm}-{date-%Y%m%d}",
    "rotateAfter": 7776000000,
    "retireAfter": 604800000,
    "deleteAfter": 2592000000
  },
  "dnsZoneFile": "..."
}

逐项解说:

  • isEnabled:域启用开关,关闭后该域收件直接拒绝;
  • catchAllAddress:catch-all 目标(null=未启用),设为某账号 ID 后所有未知收件人都进它;
  • subAddressing:子地址(user+tag@)扩展支持,Enabled 时 user+tag 投给 user;
  • allowRelaying:对内域必须保持 false——该域的邮件由本服务器收发,设 true 反而变开放转发风险;
  • dkimManagement:DKIM 自动化管理,Automatic 模式下按 selectorTemplate(实测模板 v{version}-{algorithm}-{date-%Y%m%d} 生成 v1-rsa-20261018、v1-ed25519-20261018 两个选择器)自动建签名,rotateAfter/retireAfter/deleteAfter 控制轮换/退役/删除周期(毫秒,实测 90 天轮换、7 天退役、30 天删除);
  • dnsZoneFile:一键生成的完整 DNS zone 文本(MX/SPF/DKIM/DMARC/MTA-STS/TLSRPT/自动发现全包含),加域后从对象直接取走发布即可。

4.5 DKIM 签名(DkimSignature)

域名创建时自动生成两条签名(RSA + Ed25519 双算法,收件方按支持情况选择验证)。实测读取:

curl -s http://127.0.0.1:8080/jmap -u "admin@lab.example.com:q1w2e3r4" \
  -H "Content-Type: application/json" \
  -d '{"using":["urn:ietf:params:jmap:core","urn:stalwart:jmap"],"methodCalls":[["x:DkimSignature/query",{"filter":null},"c0"],["x:DkimSignature/get",{"#ids":{"resultOf":"c0","name":"x:DkimSignature/query","path":"/ids"},"properties":null},"c1"]]}' \
  | python3 -c "import json,sys;d=json.load(sys.stdin);[print(json.dumps({k:(v if k!='publicKey' else v[:24]+'...') for k,v in it.items()},indent=1)) for it in d['methodResponses'][1][1]['list']]"

实测输出(两条签名的公共字段一致,仅算法与选择器不同;privateKey 服务端已脱敏为 **):

{
  "@type": "Dkim1RsaSha256",
  "selector": "v1-rsa-20261018",
  "domainId": "b",
  "stage": "active",
  "canonicalization": "relaxed/relaxed",
  "headers": { "From": true, "To": true, "Date": true, "Subject": true, "Message-ID": true },
  "report": true,
  "expire": null,
  "auid": null,
  "createdAt": "2026-10-18T09:16:57Z",
  "nextTransitionAt": null,
  "publicKey": "MIIBIjANBgkqhkiG9w0BAQ..."
}
{
  "@type": "Dkim1Ed25519Sha256",
  "selector": "v1-ed25519-20261018",
  "domainId": "b",
  "stage": "active",
  ...
}

字段解说:

  • @type:算法类型(Dkim1RsaSha256/Dkim1Ed25519Sha256),决定 DNS TXT 里 k= 与邮件头 a=;
  • selector:DNS 查询的 <selector>._domainkey.<domain> 前缀,发布 DNS 记录时要用;
  • stage:生命周期阶段(active 使用中;配合域名上的轮换策略还会出现 new/retire 等);
  • canonicalization:头/正文的规范化算法,relaxed/relaxed 为业界通用;
  • headers:参与签名的信头清单(From/To/Date/Subject/Message-ID 覆盖防篡改关键面);
  • report:请求收件方回传 DKIM 失败报告(对 DNS 记录追加 r=y 标记的声明)。

签名验证实测:向 demo 邮箱投递一封信后从 IMAP 取原始信头(实测见 《Ubuntu 22.04 安装 Stalwart v0.16.26 指南》 4.3 节):

DKIM-Signature: v=1; a=ed25519-sha256; d=lab.example.com; s=v1-ed25519-20261018; ...
DKIM-Signature: v=1; a=rsa-sha256; d=lab.example.com; s=v1-rsa-20261018; ...

两条签名同时存在且 d=/s= 与对象配置严格对应。

4.6 系统设置对象(SystemSettings 与向导对象)

除上述对象外,还有几类 singleton/列表对象承担全局设置,给出实测存在的对象类型与用途速查:

对象(x: 前缀)单例/列表用途实测确认
Bootstrap单例初始化向导状态(完成后不可再用)3.x 节实测
SystemSettings单例服务器级杂项设置登录读取正常
Authentication单例认证策略4.2 节详解
NetworkListener列表监听器4.1 节详解
Account(User/Domain/Group…)列表账号目录4.3/4.4 节详解
DkimSignature列表DKIM 签名4.5 节详解
QueuedMessage列表外发队列消息《Stalwart 日常运维手册》详解
Task列表后台任务《Stalwart 日常运维手册》详解

不确定某类对象是否存在/字段含义时,用 CLI 快速自省(stalwart-cli 1.0.13 实测):

export STALWART_URL=http://127.0.0.1:8080 STALWART_USER=admin@lab.example.com STALWART_PASSWORD=q1w2e3r4
stalwart-cli describe

实测输出(节选,对象类型清单):

Account, Alias, Authentication, Domain, DkimSignature, NetworkListener, QueuedMessage,
ReloadSettings, SpamdTrain, SystemSettings, Task, Tenant, ...

describe <类型> 可进一步看单类型的字段定义。

五、反垃圾与病毒过滤配置概览

Stalwart 内置垃圾过滤栈开箱即用(无需安装 Rspamd 也有基础能力),实测一封普通测试邮件到达收件箱时自动追加的评分头:

X-Spam-Status: No, score=0.40
X-Spam-Bar: +
X-Spam-Report: Spam detection software, running on the server, has identified this
               incoming email as possible spam...
  • score/Bar:综合评分与可视化条,X-Spam-Status: No 表示未达拦截阈值;
  • 评分来源包括启发式规则、DNSBL 实时黑名单查询、SPF/DKIM/DMARC 校验结果、发件方信誉等。

相关配置对象与扩展点:

  • 外挂 Rspamd:如需贝叶斯学习、模糊哈希等更强检测,可把 Stalwart 的垃圾过滤处理器指向外部 Rspamd 服务(HTTP 接口对接),Stalwart 只负责调用与结果执行;
  • 病毒扫描:同样走外置服务对接(如 ClamAV),命中后可配置拒绝/隔离/打标动作;
  • 阈值与动作:评分阈值、命中后动作(打标/投递/拒绝)在管理面相应对象中调整,本篇不展开逐项实测。

一句话结论:默认行为已可用(实测评分头正常生成、正常邮件进收件箱),强化场景建议外挂 Rspamd,属"接入即得"的配置而非另起炉灶。

六、配置变更的生效规则总结

把全文实测的生效行为汇总成表,改配置前先查这张表:

配置项变更工具生效方式实测依据
config.json(存储/系统设置)手工编辑重启服务2.3 节
stalwart.env(环境变量)手工编辑重启服务三章
NetworkListener 监听器API/WebAdmin重启服务(端口实测重启后出现)4.1 节
Authentication 密码策略API/WebAdmin重启服务(受保护配置,不重启不生效)4.2 节
Account/User 凭据API/WebAdmin/CLI即时生效(实测改密后新密码直接可登录)安装指南 2.6 节
Domain/DkimSignatureAPI/WebAdmin即时生效(新邮件按新签名配置处理)4.5 节
垃圾过滤/病毒对接API/WebAdmin即时生效五章

通用的"温和重载"入口也存在(ReloadSettings 动作、stalwart-cli 对应命令),但对监听器/认证策略这类核心对象,实测结论是重启最可靠。

七、常见问题

7.1 网上教程里的 config.toml 在 v0.16 不生效

现象:按旧教程创建/编辑 /etc/stalwart/config.toml 后重启,配置毫无反应。

原因与处理:v0.16 起配置模型重构,TOML 主配置已被废除,配置在数据存储中通过管理 API 维护;文件系统的 config.json 只承载引导信息。处理方式:忘掉 config.toml,改用本篇四章的 API/WebAdmin/CLI 三件套;跨版本迁移参考官方 UPGRADING/v0_16 文档。

7.2 修改密码策略/监听器后"不生效"

现象:API 返回 updated 成功,但行为没变(弱口令仍被拒/新端口没监听)。

原因与处理:这两类对象运行时不会热加载(实测 Authentication 修改后必须重启才拦截弱口令;NetworkListener 重启后才监听新端口)。执行 systemctl restart stalwart 后验证:

systemctl restart stalwart && sleep 5 && ss -ltn | grep <端口>

7.3 配置 API 返回 insufficient privileges

现象:x:Authentication/set 等调用返回 ... was not honored due to insufficient privileges(实测回显见 4.2 节)。

原因与处理:受保护配置的变更被运行时拒收,多数情况是实例刚改过配置未重启、权限上下文未刷新。重启服务后用管理员凭据重试即可。

7.4 config.json 手工编辑后服务起不来

现象:systemctl restart stalwart 后服务 failed,日志报 JSON 解析错误。

处理:先用 python3 -m json.tool /etc/stalwart/config.json 校验语法;从备份恢复(cp /etc/stalwart/config.json.bak /etc/stalwart/config.json)。养成"改前备份、改后校验"的习惯即可规避。

7.5 对象 ID 与 accountId 混淆

现象:JMAP 调用里 accountId 传错导致 404/权限错误。

澄清(实测值):管理 API 的 accountId 是主账号 ID(本实测环境恒为 b,管理员所在账号);而 Email/query 等邮件对象用的 accountId 是用户自己的账号 ID(demo 用户为 c)。二者同名不同义,排错时先 x:Account/query 确认目标对象的 ID,再区分调用的是管理方法还是邮件方法。

八、参考链接