Haraka 3.3.5 配置参数详解
Haraka 3.3.5 配置文件逐组详解:smtp.ini、connection.ini、log.ini、plugins 管线、smtp_forward、relay、access、TLS 与 outbound 全参数实测
Haraka 3.3.5 配置参数详解
目标:系统讲解 Haraka 3.3.5 服务目录 config/ 下的全部核心配置文件——smtp.ini、connection.ini、log.ini、plugins 管线、me/host_list、queue/smtp_forward.ini、relay.ini、access 系列、tls.ini 与 outbound.ini——逐组给出参数含义、默认值、实测行为与生产推荐值,配合实测回显说明每项配置的真实效果。
一、环境信息
本文全部配置内容与实测回显来自以下环境:
- 操作系统:Ubuntu 22.04.5 LTS(x86_64)
- Node.js:v20.20.2;npm:10.8.2
- Haraka:3.3.5(npm 全局安装,服务目录 /opt/haraka)
- 运行方式:systemd 托管(/etc/systemd/system/haraka.service),运行用户 haraka
- 监听端口:25;实测服务器对外身份 mail01.example.com,收件域 example.com
安装过程见 《Ubuntu 22.04 安装 Haraka 3.3.5 指南》,组件定位与选型见 《Haraka 3.3.5 介绍与选型指南》。
二、前置条件
- 已按安装指南完成 Haraka 3.3.5 安装并可用
haraka -i /opt/haraka生成服务目录; - 理解 INI 格式:Haraka 配置为 INI 风格,
;或#开头为注释行,[段名]分段,键=值赋值; - 查看插件自带文档的能力:
haraka -h <插件名>(需要系统装有 less 分页器,缺失会崩溃,见安装指南 FAQ Q1)。
三、参数查看与修改方式
3.1 配置文件的加载机制
Haraka 启动时从服务目录的 config/ 子目录读取配置。除少数核心文件(smtp.ini、plugins、me、host_list)外,多数 ini 文件属于具体插件,只有该插件在 config/plugins 中启用后才会生效。配置文件支持热加载:Haraka 对多数配置采用「变更时重新读取」策略,但对 smtp.ini 等启动期参数,修改后仍需 systemctl restart haraka 生效。稳妥起见:任何配置修改后都重启服务并验证。
3.2 修改后的验证闭环
每处配置修改后都应有对应验证手段,本文在每组的「实测」小节给出。通用验证三件套:
systemctl restart haraka
systemctl is-active haraka
journalctl -u haraka --no-pager -n 20
实测回显(重启后正常状态):
active
四、参数总览
Haraka 3.3.5 服务目录 config/ 下的配置文件全景:
| 文件 | 归属 | 作用 |
|---|---|---|
| smtp.ini | 内核 | 监听地址、降权、cluster、守护化、超时 |
| connection.ini | 内核 | spool、RFC 合规、HAProxy PROXY、headers、消息上限、UUID 横幅 |
| log.ini | 内核 | 日志级别、时间戳、格式 |
| plugins | 内核 | 插件管线(处理顺序即文件内顺序) |
| me | 内核 | 本机对外主机名 |
| host_list | rcpt_to.in_host_list | 本机收件域列表 |
| smtp_forward.ini | queue/smtp_forward | 入站转发下游与按域路由 |
| relay.ini + relay_acl_allow | relay | 中继授权(ACL 模式) |
| access.ini + *.access.blacklist/whitelist + access.domains | access | 地址/域黑白名单 |
| tls.ini + tls_key.pem/tls_cert.pem | tls | STARTTLS 证书与协议参数 |
| outbound.ini | 出站层 | 出站投递并发/TLS/重试上限 |
| host_list_regex | rcpt_to 插件族 | 域名正则形式的收件域 |
| aliases | aliases 插件 | 收件地址改写 |
五、核心参数分类详解
5.1 smtp.ini:监听、降权与进程模型
haraka -i 生成的默认文件(全文,; 为注释):
; address to listen on (default: all IPv6 and IPv4 addresses, port 25)
; use "[::0]:25" to listen on IPv6 and IPv4 (not all OSes)
;listen=[::0]:25
; Note you can listen on multiple IPs/ports using commas:
;listen=127.0.0.1:2529,127.0.0.2:2529,127.0.0.3:2530
; public IP address (default: none)
; If your machine is behind a NAT, some plugins (SPF, GeoIP) gain features
; if they know the servers public IP. If the optional '@msimerson/stun'
; module is installed, Haraka discovers it via STUN. Setting it here skips
; that lookup.
;public_ip=N.N.N.N
; Time in seconds to let sockets be idle with no activity
;inactivity_timeout=300
; Drop privileges to this user/group
;user=smtp
;group=smtp
; Don't stop Haraka if plugins fail to compile
;ignore_bad_plugins=0
; Run using cluster to fork multiple backend processes
; Ref: https://github.com/haraka/Haraka/wiki/Performance-Tuning
;nodes=cpus
; Daemonize
;daemonize=true
;daemon_log_file=/var/log/haraka.log
;daemon_pid_file=/var/run/haraka.pid
; Force Shutdown Timeout
; - Haraka tries to close down gracefully, but if everything is shut down
; after this time it will hard close. 30s is usually long enough to
; wait for outbound connections to finish.
;force_shutdown_timeout=30
逐项说明(默认值即注释中的值):
| 参数 | 默认值 | 说明 |
|---|---|---|
| listen | [::0]:25 | 监听地址与端口;可逗号分隔多地址(如仅内网 10.0.0.5:25) |
| public_ip | 无 | NAT 后部署时声明公网 IP,SPF/GeoIP 插件受益;不设则可经 STUN 自动发现 |
| inactivity_timeout | 300 | 空闲 socket 秒数上限,超时断开 |
| user / group | smtp / smtp | 绑定端口后降权到的运行用户;生产建议专用系统用户 |
| ignore_bad_plugins | 0 | 为 1 时插件编译失败不阻断启动(排障期临时用) |
| nodes | 未设置(1) | cluster worker 数;cpus 表示按 CPU 核数 fork |
| daemonize | false | 自守护化;用 systemd 托管时必须保持 false |
| daemon_log_file / daemon_pid_file | /var/log/haraka.log、/var/run/haraka.pid | daemonize 时的日志与 PID 路径 |
| force_shutdown_timeout | 30 | 优雅退出等待出站连接的最长秒数,超时硬退 |
实测一:未显式设置 nodes 时前台启动会给出提示:
[WARN] [-] [server] smtp.ini.nodes unset, using 1, see https://github.com/haraka/Haraka/wiki/Performance-Tuning
Starting up Haraka version 3.3.5
实测二:设置 nodes=cpus(12 核)并重启后,工作进程数恰为 12:
ps -eo user,pid,args | grep -E "[h]araka.js"
实测回显(每行一个 worker,均以 haraka 用户运行):
haraka 4946 /usr/bin/node /usr/lib/node_modules/Haraka/haraka.js -c /opt/haraka
haraka 4947 /usr/bin/node /usr/lib/node_modules/Haraka/haraka.js -c /opt/haraka
haraka 4948 /usr/bin/node /usr/lib/node_modules/Haraka/haraka.js -c /opt/haraka
haraka 4949 /usr/bin/node /usr/lib/node_modules/Haraka/haraka.js -c /opt/haraka
haraka 4950 /usr/bin/node /usr/lib/node_modules/Haraka/haraka.js -c /opt/haraka
haraka 4956 /usr/bin/node /usr/lib/node_modules/Haraka/haraka.js -c /opt/haraka
haraka 4962 /usr/bin/node /usr/lib/node_modules/Haraka/haraka.js -c /opt/haraka
haraka 4971 /usr/bin/node /usr/lib/node_modules/Haraka/haraka.js -c /opt/haraka
haraka 4979 /usr/bin/node /usr/lib/node_modules/Haraka/haraka.js -c /opt/haraka
haraka 4995 /usr/bin/node /usr/lib/node_modules/Haraka/haraka.js -c /opt/haraka
haraka 5001 /usr/bin/node /usr/lib/node_modules/Haraka/haraka.js -c /opt/haraka
haraka 5006 /usr/bin/node /usr/lib/node_modules/Haraka/haraka.js -c /opt/haraka
5.2 connection.ini:会话与消息控制
默认文件全文:
;
[main]
; Spooling
; Save memory by spooling large messages to disk
; directory to create temporary spool files in (default: /tmp - see https://github.com/haraka/message-stream)
; spool_dir=/var/spool/haraka
; Specify -1 to never spool to disk (default)
; Specify 0 to always spool to disk
; Otherwise specify a size in bytes, once reached the
; message will be spooled to disk to save memory.
; spool_after=
[haproxy]
; Bool: enable HAProxy PROXY protocol support and SMTPS pre-parsing.
; enabled=true
; Array: hosts or CIDRs that Haraka should enable the PROXY protocol from. See docs/HAProxy for format
hosts[] =
; hosts[] = 192.0.2.4
; hosts[] = 192.0.2.5
; hosts[] = [2001:db8::1]
[headers]
; add_received=true
; clean_auth_results=true
; show_version=true
max_lines=1000
max_received=100
[max]
; Integer. The maximum SIZE of an email
bytes=26214400
; Integer. Limit a potential denial of service in potentially hostile emails.
mime_parts=1000
; Integer. The maximum length of lines in SMTP session commands (e.g. RCPT, HELO etc). Defaults to 512 (bytes) as mandated by RFC 5321 §4.5.3.1.4. Clients exceeding this limit will be immediately disconnected with a "521 Command line too long" error.
line_length=512
; Integer. The maximum length of lines in the DATA section of emails. Defaults to 992 (bytes), the limit set by Sendmail. When this limit is exceeded the three bytes "\r\n " (0x0d 0x0a 0x20) are inserted into the stream to "fix" it.
data_line_length=992
[message]
; Array. The greeting used when a client connects.
; greeting[]=My Custom
; greeting[]=Greeting Message
helo=Haraka is at your service.
; String. Override the default connection close message.
close=closing connection. Have a jolly good day.
[uuid]
; integer, how many UUID chars to show.
; 0 = none, 6 is enough to be unique per day, 40 will include the
; full connection and transaction UUID
banner_chars=6
; include N characters of the uuid (in brackets) at the start of each line of the deny message
deny_chars=0
💡 说明:默认文件中还有 strict_rfc1869 / postel / smtputf8 等 [main] 键(部分版本样例存在),含义如下表一并给出。
逐项说明:
| 段 | 参数 | 默认值 | 说明 |
|---|---|---|---|
| [main] | spool_dir | /tmp(由 message-stream 决定) | 大消息转盘的临时目录,建议改为独立分区路径 |
| [main] | spool_after | -1(从不转盘) | 消息字节数阈值,超过即写磁盘省内存;0 为总是转盘 |
| [main] | strict_rfc1869 | false | 严格校验 MAIL FROM/RCPT TO 语法(多余空格、缺尖括号即拒) |
| [main] | postel | false | 宽松信封解析(Postel 法则模式),容错不合规信封 |
| [main] | smtputf8 | true | 通告并支持 SMTPUTF8(RFC 6531) |
| [haproxy] | enabled | false | 启用 HAProxy PROXY protocol 与 SMTPS 预解析 |
| [haproxy] | hosts[] | 空 | 信任的 PROXY 协议来源(IP/CIDR) |
| [headers] | add_received | true | 是否追加 Received 头 |
| [headers] | clean_auth_results | true | 规范化 Authentication-Results 头 |
| [headers] | show_version | true | Received 头中展示 Haraka 版本 |
| [headers] | max_lines | 1000 | 邮件头最大行数(超限断连) |
| [headers] | max_received | 100 | Received 头最大数量(防循环反弹) |
| [max] | bytes | 26214400 | 单封邮件大小上限(25 MB),EHLO SIZE 同步通告 |
| [max] | mime_parts | 1000 | MIME 部件数上限(防解析型 DoS) |
| [max] | line_length | 512 | SMTP 命令行长度上限(RFC 5321),超限即断连 521 |
| [max] | data_line_length | 992 | DATA 行长上限(Sendmail 系数值),超限插入 \r\n 修复 |
| [message] | greeting[] | 无(用默认问候) | 自定义 220 横幅附加文案 |
| [message] | helo | Haraka is at your service. | HELO/EHLO 响应中的问候语 |
| [message] | close | closing connection. Have a jolly good day. | 221 退出横幅 |
| [uuid] | banner_chars | 6 | 220 横幅尾部展示的 UUID 字符数 |
| [uuid] | deny_chars | 0 | 拒绝消息前缀展示的 UUID 字符数 |
实测:220 横幅中的 (AE8A41) 正是 banner_chars=6 的效果:
220 mail01.example.com ESMTP Haraka/3.3.5 ready (AE8A41)
EHLO 通告的 SIZE 26214400 正是 [max] bytes 的值,SMTPUTF8 来自 smtputf8=true:
250-PIPELINING
250-8BITMIME
250-SMTPUTF8
250-SIZE 26214400
250 STARTTLS
5.3 log.ini:日志级别与格式
默认文件全文:
[main]
; level=data, protocol, debug, info, notice, warn, error, crit, alert, emerg
level=info
; prepend timestamps to log entries? This setting does NOT affect logs emitted
; by logging plugins (like syslog).
timestamps=false
; format=default, logfmt, json
format=default
| 参数 | 默认值 | 说明 |
|---|---|---|
| level | info | 日志级别,从细到粗:data/protocol/debug/info/notice/warn/error/crit/alert/emerg;排障临时用 debug |
| timestamps | false | systemd/journald 已带时间戳,一般保持 false;直写文件时建议 true |
| format | default | 输出格式:default(人读)/ logfmt / json(采集器友好) |
实测一:level=debug 时可见完整的钩子执行链(输出节选):
[DEBUG] [FB77FDED-...] [core] running mail hooks
[DEBUG] [FB77FDED-...] [core] running mail hook in access plugin
[DEBUG] [FB77FDED-...] [access] checking user@banned.example.com against mail_from.access.whitelist
[DEBUG] [FB77FDED-...] [core] hook=mail plugin=access function=any params=<user@banned.example.com> retval=CONT msg=""
实测二:timestamps=true + format=json 时,journal 中每行变为 JSON 并前置 ISO 时间戳:
2026-10-21T02:19:54.708Z {"level":"DEBUG","uuid":"BDDE0F92-7448-44F0-9DEB-5AA59C1DB510.1","origin":"core","message":"client has disconnected"}
2026-10-21T02:19:54.708Z {"level":"NOTICE","uuid":"BDDE0F92-7448-44F0-9DEB-5AA59C1DB510.1","origin":"core","message":"disconnect","ip":"127.0.0.1","rdns":"ip6-localhost","helo":"t.example.net","relay":"Y","early":"N","esmtp":"Y","tls":"N","pipe":"N","errors":0,"txns":1,"rcpts":"0/0/0","msgs":"0/0/0","bytes":0,"lr":"","time":25.096}
json 格式把连接/事务属性(ip、helo、relay、txns、rcpts、bytes)结构化,适合接入日志采集与告警。
5.4 config/plugins:插件管线
默认文件(未注释行即已启用插件):
# This file lists plugins that Haraka will run
#
# Plugin ordering often matters, run 'haraka -o -c /path/to/haraka/config'
# to see the order plugins (and their hooks) will run.
#
# To see a list of installed plugins, run 'haraka -l'
#
# The plugin registry: https://github.com/haraka/Haraka/blob/master/Plugins.md
#
# To see the docs for a plugin, run 'haraka -h plugin.name'
# status
# process_title
# syslog
# CONNECT
# ----------
# toobusy
# karma
# relay
# access
# geoip
# asn
# fcrdns
# dns-list
# HELO
# ----------
# early_talker
# helo.checks
# see 'haraka -h tls' before enabling!
# tls
#
# AUTH plugins require TLS before AUTH is advertised, see
# https://github.com/haraka/Haraka/wiki/Require-SSL-TLS
# ----------
# auth/flat_file
# auth/auth_proxy
# MAIL FROM
# ----------
mail_from.is_resolvable
# spf
# RCPT TO
# ----------
# At least one rcpt_to plugin is REQUIRED for inbound email.
rcpt_to.in_host_list
# qmail-deliverable
# DATA
# ----------
# attachment
# bounce
# clamd
# dkim
# headers
# limit
# rspamd
# spamassassin
# uribl
# QUEUE
# ----------
# queues: discard qmail-queue quarantine smtp_forward smtp_proxy
# Queue mail via smtp - see config/smtp_forward.ini for where your mail goes
queue/smtp_forward
要点:
- 顺序即执行序:同钩子插件按文件内出现顺序执行,
OK会短路后续插件,把准入类插件放前面可省 CPU; - 语法:每行一个插件;
#开头为注释;子目录插件写queue/smtp_forward形式;npm 插件直接写包名; - 入站至少要有一个 rcpt_to.* 插件(官方注释原文明确 "At least one rcpt_to plugin is REQUIRED for inbound email");
- AUTH 必须先有 TLS:auth/* 插件要求 TLS 先行(官方注释指向 Require-SSL-TLS wiki 页);
- 修改后用
haraka -o -c /opt/haraka核对实际注册的钩子与顺序(完整回显见《Haraka 3.3.5 常用 CLI 命令详解》)。
实测:本文实验中启用 relay、access、tls 三个插件后的 mail 钩子链(haraka -o 输出节选):
Hook: mail ---------------------------------------------------------------------
Plugin Method Prio T/O
--------------------------------------------------------------------------------
access mail_from_access 0 30
access any 0 30
mail_from.is_resolvable hook_mail 0 30
rcpt_to.in_host_list hook_mail 0 30
5.5 me 与 host_list:本机身份与收件域
| 文件 | 内容 | 实测值 |
|---|---|---|
| config/me | 本机对外主机名(单行) | mail01.example.com |
| config/host_list | 本机收件域(每行一个) | example.com |
me 影响 SMTP 横幅、EHLO 响应、Received 头与 UUID 生成:
220 mail01.example.com ESMTP Haraka/3.3.5 ready (AE8A41)
250-mail01.example.com Hello ip6-localhost [127.0.0.1], Haraka is at your service.
host_list 是 rcpt_to.in_host_list 的准入名单:RCPT TO 域名在名单内即接受,否则拒绝。实测(user@stuck.example 不在名单——被 access/路由环节处理;postmaster@example.com 在名单内——接受):
250 recipient <postmaster@example.com> OK
注意名单内写「域」而非主机名;多域场景每行一个。另有 config/host_list_regex 支持正则形式的域匹配(见 haraka -h rcpt_to.in_host_list)。
5.6 smtp_forward.ini:转发下游与按域路由
config/smtp_forward.ini 属于 queue/smtp_forward 插件(未建文件时插件按默认值转发到 localhost:2555——这是「450 plugin timeout」排障的根源)。全部键(来源:插件 README,haraka -h queue/smtp_forward 可查原文):
| 键 | 默认 | 说明 |
|---|---|---|
| enable_outbound | true | 中继连接的消息交由 Haraka Outbound(MX 路由)投递;false 则出站也走 smtp_forward |
| host | 无 | 转发下游主机(不设默认 localhost) |
| port | 25(样例文件为 2555) | 下游端口;实测未配置文件时插件连接 localhost:2555 |
| connect_timeout | 30 | 建立下游连接的最长等待秒数 |
| timeout | — | 连接池空闲存活时间,应小于全局插件超时(默认 30s) |
| max_connections | 1000 | 并发连接上限 |
| enable_tls | false | 对下游做机会主义 STARTTLS |
| auth_type / auth_user / auth_pass | 无 | 对下游做 SMTP AUTH(plain/login) |
| queue | 无 | 复杂路由时指定收连接使用的队列插件 |
| check_sender | false | 要求 smtp_forward.ini 中定义的发件域具备中继权限(防伪造) |
| check_recipient | false | 用 per-domain 段代替 host_list 做收件域准入 |
| [tls] 段 | — | 覆盖 tls.ini [main] 的客户端 TLS 参数(rejectUnauthorized/minVersion/ciphers 等) |
基础转发配置(实测有效):
host=127.0.0.1
port=2555
实测(下游 2555 在跑时投递成功,journal 节选):
[INFO] [FA00077B-...1] [queue/smtp_forward] forwarding to 127.0.0.1:2555
[INFO] [FA00077B-...1] [core] hook=queue plugin=queue/smtp_forward function=queue_forward params="" retval=OK msg="{\"0\":\"OK\"}"
下游不通时的实测表现(30 秒后插件超时,DENYSOFT):
[CRIT] [7E418AAE-...1] [core] Plugin queue/smtp_forward timed out on hook queue - make sure it calls the callback
[INFO] [7E418AAE-...1] [core] hook=queue plugin=queue/smtp_forward function=queue_forward params="" retval=DENYSOFT msg="plugin timeout"
per-domain 路由(本插件的高级能力):在文件末尾加域段,出站层的 get_mx 钩子会对该域返回固定路由,绕过 DNS MX:
[stuck.example]
host=192.0.2.1
port=25
实测 journal(get_mx 返回段内定义的路由,连接失败后临时失败重试):
[INFO] [5084DE4D-...1.1] [outbound] hook=get_mx plugin=queue/smtp_forward function=get_mx params=stuck.example retval=OK msg="{\"priority\":0,\"exchange\":\"192.0.2.1\",\"port\":25}"
[NOTICE] [5084DE4D-...1.1] [outbound] Failed to get socket: connect ECONNREFUSED 192.0.2.1:25
[WARN] [5084DE4D-...1.1] [outbound] Tried all MXs stuck.example: 192.0.2.1:25 connect ECONNREFUSED 192.0.2.1:25
[INFO] [5084DE4D-...1.1] [outbound] Temp failing 1792548278720_..._0_3869_NnZ1vS_8_317dc6228b57 for 64 seconds: Tried all MXs stuck.example
临时失败(Temp fail)后消息滞留出站队列按退避重试,队列文件名中的重试计数会递增(..._0_... → ..._1_...),超过 maxTempFailures(默认 13 次)后弹回。
5.7 relay.ini 与 relay_acl_allow:中继授权
relay 插件负责「允许谁通过本机中继」。ACL 模式两步配置:
config/relay.ini:
[relay]
acl=true
config/relay_acl_allow(每行一个 IP 或 CIDR):
127.0.0.1
实测:来自 127.0.0.1 的连接被授予中继身份(disconnect 行 relay=Y):
[INFO] [FB77FDED-...] [relay] checking 127.0.0.1 in relay_acl_allow
[DEBUG] [FB77FDED-...] [relay] checking if 127.0.0.1 is in 127.0.0.1/32: yes
[NOTICE] [...disconnect...] relay=Y early=N esmtp=Y tls=N pipe=N errors=0 ...
未列出的地址 relay=N(普通入站连接),其发往非 host_list 域的邮件会被 rcpt_to 插件拒绝。中继身份的另一来源是 AUTH 认证成功(auth/* 插件)。 relay.ini 还支持 [relay] 段下更多开关(如 allow_relaying 等),完整清单见 haraka -h relay 显示的插件 README。
5.8 access 系列:黑白名单
access 插件提供四类名单:地址白/黑名单(按收发件人)、rdns 白/黑名单(按反解主机名)、域名单(access.domains)、正则名单(*_regex 后缀文件)。
config/access.ini 控制各检查开关:
[check]
any=true
| 键 | 默认 | 说明 |
|---|---|---|
| any | false | 全阶段通用检查:对 connect/helo/ehlo/mail/rcpt 注册 any 钩子,配合 access.domains 域名单 |
| conn | false | 启用 connect.rdns_access.whitelist/blacklist(按反解主机名) |
| false | 启用 mail_from.access.whitelist/blacklist(按发件地址) | |
| rcpt | false | 启用 rcpt_to.access.whitelist/blacklist(按收件地址) |
| [rcpt] accept | false | (可选)仅允许名单内收件人的白名单模式 |
名单文件(每行一条,! 前缀为白名单豁免):
| 文件 | 条目形态 | 实测条目 |
|---|---|---|
| config/mail_from.access.blacklist | 完整地址 | spammer@spam.example |
| config/rcpt_to.access.blacklist | 完整地址 | baduser@spam.example |
| config/access.domains | 裸域名 | banned.example.com |
实测一:发件地址黑名单命中(550):
MAIL spammer: (550, b'spammer@spam.example That sender cannot send mail here')
实测二:收件地址黑名单命中(550):
RCPT baduser: (550, b'baduser@spam.example That recipient is not allowed')
默认拒绝文案可在 access.ini 的 [deny_msg] 段自定义(mail/rcpt/conn 三档),默认值即上两行的括号内文字。
实测三(⚠️ 已知行为):access.domains 域名单在 3.3.5 实测未生效——发往 banned.example.com 的 RCPT TO 返回 250 而非 550。定位过程:debug 日志显示 any 钩子返回 CONT;用 node 直接调用插件依赖的 haraka-tld 库验证,get_organizational_domain() 对 gmail.com、example.com、banned.example.com、banned.example 全部返回 null(域规约依赖的公共后缀数据未参与判定),导致域黑名单匹配落空。日志侧证:
[INFO] [6276304D-...1] [access] no org domain from banned.example
结论:生产使用 access 插件时,依赖「地址级黑白名单」(实测有效)+ 正则名单;域名单在当前版本不可靠,升级版本时应重新验证该行为。
5.9 TLS 证书与 tls.ini
TLS 文件按固定文件名放置于 config/:tls_key.pem(私钥)、tls_cert.pem(证书)。缺失时启动日志报错(但服务不中断):
[ERROR] [-] [core] tls key /opt/haraka/config/tls_key.pem could not be loaded.
[ERROR] [-] [core] tls cert /opt/haraka/config/tls_cert.pem could not be loaded.
自签生成(内网/测试):
cd /opt/haraka/config
openssl req -x509 -nodes -newkey rsa:2048 -keyout tls_key.pem -out tls_cert.pem -days 3650 -subj "/CN=mail01.example.com"
在 config/plugins 启用 tls 插件并重启后,STARTTLS 生效。实测:EHLO 能力列表出现 STARTTLS,STARTTLS 握手后日志记录加密套件:
250 STARTTLS
...
[INFO] [0E1FD57C-3F38-4A94-9078-DD6FFAB9B68B] [tls] secured: cipher=TLS_AES_256_GCM_SHA384 version=TLSv1.3 verified=false
tls.ini(可从包内 config 样例拷贝)控制协议细节,常用键:minVersion(默认 TLSv1.2 样例)、ciphers、rejectUnauthorized、key/cert(改文件名时)、no_tls_hosts[](对指定地址不强制 TLS)、force_tls_hosts[](指定域强制 TLS)。包内默认样例全文(来源:Haraka 3.3.5 包内 config/tls.ini):
[tls]
; rejectUnauthorized=true
; minVersion=TLSv1.2
; ciphers=ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256
; key=outbound_tls_key.pem
; cert=outbound_tls_cert.pem
; no_tls_hosts[]=10.0.0.5
; force_tls_hosts[]=mx.example.com
⚠️ 注意:官方注释明确提示「启用 auth/* 前先阅读 tls 相关文档」——AUTH 能力只有在 TLS 会话内才通告,明文口令认证必须以加密为前提。
5.10 outbound.ini:出站投递层
出站(Outbound)层负责非转发类的 MX 路由投递。包内默认样例(来源:Haraka 3.3.5 包内 config/outbound.ini,查询日期 2026-10-21):
; disabled (default: false)
; disabled=true
; concurrency_max (default: 100)
; concurrency_max=100
; uncomment to disable tls for outbound mail
; enable_tls=false
; maxTempFailures (default: 13)
; always_split: default: false
; always_split=true
; received_header (default: "Haraka outbound")
; received_header=Haraka outbound
; inet_prefer (default: default)
; inet_prefer=v4
| 参数 | 默认 | 说明 |
|---|---|---|
| disabled | false | 整体停用出站层(纯入站转发场景可开) |
| concurrency_max | 100 | 全局出站并发上限 |
| enable_tls | true | 出站机会主义 TLS(对端支持 STARTTLS 即升级);实测对 mx.google.com 完成 TLSv1.3 握手 |
| maxTempFailures | 13 | 临时失败重试次数上限,超过弹回 |
| always_split | false | 按收件人拆分消息(多收件人各投一份) |
| received_header | Haraka outbound | 出站消息追加的 Received 头标识 |
| inet_prefer | default | 出站地址族偏好(v4/v6) |
实测(机会主义 TLS 出站投递的握手记录,输出节选):
[INFO] [95204971-...1] [outbound] secured verified=true cipher=TLS_AES_256_GCM_SHA384 version=TLSv1.3 cn=mx.google.com organization="" issuer="Google Trust Services" expires="Dec 21 18:40:12 2026 GMT" fingerprint=AC:A7:D8:24:48:3C:25:3A:AD:4E:0B:5E:59:3D:10:E5:12:74:7A:96
队列管理命令(--qlist/--qstat/--qunstick)的实测回显见《Haraka 3.3.5 常用 CLI 命令详解》。
5.11 mail_from.is_resolvable:发件域 MX 校验
默认启用的准入插件,校验 MAIL FROM 域名在 DNS 上可达。配置键(来源:插件 README,haraka -h mail_from.is_resolvable):
| 键 | 默认 | 说明 |
|---|---|---|
| [main] allow_mx_ip | false | 是否接受 MX 直接指向 IP(RFC 不允许,部分 MTA 放行) |
| [main] re_bogus_ip | `^(?:0.0.0.0 | 255.255.255.255 |
| [main] timeout_ms | 5000 | 每次 DNS 查询超时,超时返回 DENYSOFT |
| [reject] no_mx | deny | 无 MX/仅无效 IP 时的动作:deny(550 拒绝)/ defer(DENYSOFT 4xx)/ no(放行) |
实测:对发布 Null MX(RFC 7505)的域直接拒绝:
(550, b'5.7.27 Null MX: example.org does not send mail')
对有正常 MX 的域放行并给出判定依据(日志):
[INFO] [E83229A2-...1] [mail_from.is_resolvable] pass:has_fwd_dns
[NOTICE] [E83229A2-...1] [core] sender <sender@gmail.com> code=CONT msg=""
六、生产配置模板
适用场景:2 核 4GB 内存、NVMe 磁盘的边缘收信/中继节点,日收信量 50 万封以内,后端为内部邮箱系统。核心文件完整配置如下。
/opt/haraka/config/smtp.ini:
; 监听全部地址的 25 端口(IPv6+IPv4)
listen=[::0]:25
; NAT 后部署时填写公网 IP(SPF/GeoIP 插件受益)
;public_ip=203.0.113.10
; 空闲连接 300 秒断开
inactivity_timeout=300
; 运行用户(绑定 25 端口后自动降权)
user=haraka
group=haraka
; cluster worker 数 = CPU 核数
nodes=cpus
; systemd 托管,禁用自守护化
;daemonize=true
; 优雅退出等待 30 秒
force_shutdown_timeout=30
/opt/haraka/config/connection.ini(仅列建议调整项):
[max]
bytes=26214400
mime_parts=1000
[main]
; 大消息超过 5MB 转磁盘 spool,保护内存
spool_after=5242880
spool_dir=/var/spool/haraka
/opt/haraka/config/log.ini:
[main]
level=info
timestamps=false
format=json
/opt/haraka/config/smtp_forward.ini:
host=10.0.0.20
port=25
enable_tls=true
connect_timeout=30
max_connections=200
/opt/haraka/config/plugins(边缘中继推荐起点):
# CONNECT
relay
access
fcrdns
dns-list
# HELO
tls
helo.checks
# MAIL FROM
mail_from.is_resolvable
spf
# RCPT TO
rcpt_to.in_host_list
# DATA
headers
rspamd
# QUEUE
queue/smtp_forward
配套动作清单:创建 haraka 系统用户并对 /opt/haraka 整目录 chown;fcrdns/dns-list/spf/helo.checks/rspamd 等插件各自的 ini 按插件文档微调(haraka -h <插件名> 可查);修改任何配置后 systemctl restart haraka 并用 haraka -o -c /opt/haraka 核对钩子链。
七、常见问题(FAQ)
Q1:改了配置不生效。
分两类:启动期参数(smtp.ini 的 listen/user/nodes 等)与插件清单(config/plugins)必须重启;多数插件 ini(smtp_forward.ini、access.ini 等)支持变更重读,但为确定性起见统一重启。验证:systemctl restart haraka && journalctl -u haraka --no-pager -n 20 观察启动行 Starting up Haraka version 3.3.5 与监听通告 Listening on [::0]:25。
Q2:邮件被 550/451 拒,如何快速定位是哪个插件?
把 log.ini 的 level 临时改为 debug 并重启,日志中每条钩子的判定都以 [core] hook=<钩子> plugin=<插件> ... retval=<OK|CONT|DENY|DENYSOFT> msg="..." 形式输出,一眼可见拒绝者与文案。定位完改回 info。
Q3:为什么默认配置连内部邮箱都没法收信?
默认 queue/smtp_forward 无 smtp_forward.ini 时转发到 localhost:2555;host_list 未含收件域时 rcpt_to.in_host_list 全拒。按 5.5/5.6 两节分别写好 host_list 与 smtp_forward.ini 即可。
Q4:横幅里的 (AE8A41) 是什么?
config/connection.ini [uuid] banner_chars=6 的默认行为:220 横幅尾部附连接 UUID 前 6 位,便于日志对账;设为 0 可隐藏。
Q5:出站队列积压如何处理?
先看成因(haraka --qstat 计数 + journal 中 Temp failing 行的退避时长),若为下游临时故障可等待自动重试,或对指定域用 haraka --qunstick <域名> 强制立即重投(实测返回 250 Command sent for execution. Check Haraka logs for results.);确认无法投递的死信可直接删除 /opt/haraka/queue/ 下对应文件后重启。
八、参考链接
- Haraka 官方文档站:https://haraka.github.io (查询日期 2026-10-21)
- 配置参考(Config 页):https://haraka.github.io/config (查询日期 2026-10-21)
- 插件文档获取方式:
haraka -h <插件名>(服务端内置 README,随版本同步) - 默认配置样例:Haraka 3.3.5 包内 config/ 目录(outbound.ini、tls.ini、smtp_forward.ini 等)
- 官方 wiki Performance-Tuning:https://github.com/haraka/Haraka/wiki/Performance-Tuning (查询日期 2026-10-21)
- 站内系列:《Ubuntu 22.04 安装 Haraka 3.3.5 指南》、《Haraka 3.3.5 常用 CLI 命令详解》、《Haraka 3.3.5 介绍与选型指南》