Ubuntu 22.04 安装 Haraka 3.3.5 指南
Ubuntu 22.04 从零安装 Haraka 3.3.5:Node.js 20 前置、npm 在线与离线双方案、haraka -i 初始化、systemd 托管、25 端口收信验证与卸载清理,全程实测
Ubuntu 22.04 安装 Haraka 3.3.5 指南
目标:在 Ubuntu 22.04 服务器上从零安装 Haraka 3.3.5 SMTP 服务器,覆盖 Node.js 20 前置、npm 全局安装(在线/离线两种方案)、
haraka -i初始化服务目录、专用用户降权、systemd 托管、25 端口收信验证与卸载清理,全程命令与回显实测自 Ubuntu 22.04.5 LTS 环境。
一、环境信息
本文全部命令与回显在以下环境实测通过:
- 操作系统:Ubuntu 22.04.5 LTS(
PRETTY_NAME="Ubuntu 22.04.5 LTS") - 架构:x86_64
- Node.js:v20.20.2(NodeSource 官方源安装)
- npm:10.8.2
- Haraka:3.3.5(npm 全局安装)
- 服务目录:/opt/haraka(
haraka -i生成) - 运行用户:haraka(系统用户,uid/gid 999)
- 托管方式:systemd(/etc/systemd/system/haraka.service)
- 监听端口:25(
[::0]:25,IPv6 与 IPv4)
二、前置条件
安装 Haraka 前需要确认以下几项,逐条核对可避免后续排障:
- 一台 Ubuntu 22.04 服务器,具备 sudo 权限的账号与可用的软件源(本篇以 root 执行);
- Node.js ≥ 20:Haraka 3.3.5 的 npm 元数据声明
engines = { node: '>=20' },而 Ubuntu 22.04 官方仓库自带的 nodejs 为 12.22.9,必须先安装新版 Node.js(本文用 NodeSource 官方源装 20.x LTS); - 可用的 DNS 解析:默认启用的
mail_from.is_resolvable插件需要对发件域做 MX 查询,服务器需能访问 DNS; - 25 端口未被占用:
ss -ltnp | grep :25应无输出;且公网服务器需确认运营商/云平台未封禁入站 25 端口(多数云平台默认封禁,需申请解封,否则只能收内部邮件); - 网络可达 npm registry(在线方案);离线方案见第五章。
确认系统基线:
cat /etc/os-release | head -2
实测回显:
PRETTY_NAME="Ubuntu 22.04.5 LTS"
NAME="Ubuntu"
ss -ltnp | grep -E ":25 " || echo "port 25 free"
实测回显(25 端口空闲时仅输出提示行):
port 25 free
三、版本选型说明
Haraka 通过 npm 发行,包名大小写敏感为 Haraka。安装前先实查当前最新稳定版与版本线:
npm view Haraka dist-tags
实测回显(查询日期 2026-10-21):
{ alpha: '2.5.0-alpha', alpha2: '2.5.0-alpha2', latest: '3.3.5' }
npm view Haraka engines
实测回显:
engines = { node: '>=20' }
选型结论:
| 版本线 | 说明 | 建议 |
|---|---|---|
| 3.3.x(latest:3.3.5) | 当前稳定线,2026-10-17 发布 | 生产使用此线,锁定小版本安装 |
| 2.5.0-alpha / alpha2 | 下一大版本预发布(alpha dist-tag) | 仅供测试,勿上生产 |
| 3.2.x / 3.1.x | 上一代稳定线,仍可经 Haraka@3.2.1 指定安装 | 仅在插件兼容性受限时回退 |
安装时建议显式锁定版本号:npm install -g Haraka@3.3.5,避免未来 npm update -g 时被动升级。GitHub Releases(releases.atom)与 npm 双源核对,3.3.5 于 2026-10-17 同时发布于两侧,是当前最新稳定版。
四、在线安装方案(A:NodeSource 源 + npm 全局安装)
本章是官方推荐主线:先用 NodeSource 源安装 Node.js 20,再用 npm 全局安装 Haraka,生成服务目录并用 systemd 托管。
4.1 安装 Node.js 20(Haraka 运行时)
Ubuntu 22.04 官方仓库的 nodejs 为 12.22.9,不满足 node>=20,先确认再装:
apt-cache policy nodejs | head -4
实测回显:
nodejs:
Installed: (none)
Candidate: 12.22.9~dfsg-1ubuntu3.6
Version table:
安装基础工具并接入 NodeSource 20.x 源:
apt-get update
apt-get install -y curl ca-certificates
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
实测回显(setup 脚本结尾,输出节选):
2026-10-21 01:48:39 - To install Node.js, run: apt install nodejs -y
2026-10-21 01:48:39 - You can use N|solid Runtime as a node.js alternative
2026-10-21 01:48:39 - To install N|solid Runtime, run: apt install nsolid -y
安装 Node.js 并验证版本:
apt-get install -y nodejs
node -v && npm -v
实测回显:
v20.20.2
10.8.2
4.2 npm 全局安装 Haraka
npm install -g Haraka
实测回显(输出节选,安装约 96 个包):
run `npm fund` for details
npm notice
npm notice New major version of npm available! 10.8.2 -> 12.2.0
npm notice Changelog: https://github.com/npm/cli/releases/tag/v12.2.0
npm notice To update run: npm install -g npm@12.2.0
验证安装结果与版本:
haraka --version
which haraka
npm ls -g --depth=0
实测回显:
Haraka.js — Version: 3.3.5
/usr/bin/haraka
/usr/lib
+-- corepack@0.34.6
+-- Haraka@3.3.5
⚠️ 注意:npm 全局安装需要 root 或具备 /usr/lib/node_modules 写权限的账号。若使用普通账号,请为 npm 配置用户级 prefix,或将全局目录属主改为运维组,切勿日常用 root 跑业务进程(见 4.4 节降权)。
4.3 生成服务目录(haraka -i)
haraka -i 会在指定目录生成服务骨架(config 配置、plugins 目录、docs)。生产目录建议放在 /opt/haraka:
haraka -i /opt/haraka
实测回显:
create: /opt/haraka
create: /opt/haraka/plugins
create: /opt/haraka/docs
create: /opt/haraka/config
create: /opt/haraka/docs/Plugins.md
create: /opt/haraka/config/connection.ini
create: /opt/haraka/config/smtp.ini
create: /opt/haraka/config/log.ini
create: /opt/haraka/config/plugins
查看生成的目录结构与关键文件:
find /opt/haraka -maxdepth 1 | sort
实测回显:
/opt/haraka
/opt/haraka/README
/opt/haraka/config
/opt/haraka/docs
/opt/haraka/package.json
/opt/haraka/plugins
ls /opt/haraka/config/
实测回显:
connection.ini
host_list
internalcmd_key
log.ini
me
plugins
smtp.ini
其中 me 与 host_list 是 haraka -i 自动写入的本机主机名(本机当时主机名为一串随机字符,生产服务器上会自动写入你的主机名),internalcmd_key 是内部命令通道的密钥(--qunstick 等在线队列命令依赖它)。
4.4 配置服务身份与运行用户
Haraka 以 config/me 的内容作为对外宣告的主机名(SMTP 横幅与 Received 头),以 config/host_list 决定本机收哪些域的邮件。将二者改为真实身份:
echo mail01.example.com > /opt/haraka/config/me
echo "example.com" > /opt/haraka/config/host_list
cat /opt/haraka/config/me
实测回显:
mail01.example.com
⚠️ 注意:
mail01.example.com与example.com为示例值,请替换为你自己的服务器主机名与收件域。host_list 里写的是「本机负责收信的域名」(如 example.com),而不是主机名全称。
创建专用系统用户并调整属主,让 Haraka 以最小权限运行:
useradd -r -s /usr/sbin/nologin haraka
id haraka
chown -R haraka:haraka /opt/haraka
实测回显:
uid=999(haraka) gid=999(haraka) groups=999(haraka)
在 config/smtp.ini 中启用 Haraka 自带的降权机制(找到 ;user=smtp 与 ;group=smtp 两行,去掉注释并改为 haraka):
user=haraka
group=haraka
修改后确认(grep 定位行号):
grep -nE "^user=|^group=" /opt/haraka/config/smtp.ini
实测回显:
19:user=haraka
20:group=haraka
这套组合的运行形态是:systemd 以 root 启动主进程(绑定 25 特权端口),Haraka 完成端口绑定后自行 setuid/setgid 降权到 haraka 用户(第五章 5.6 节有实测回显)。
4.5 systemd 托管
Haraka 3.3.5 官方包内不含现成的 unit 文件,按「前台进程 + Type=simple」编写。创建 /etc/systemd/system/haraka.service:
vi /etc/systemd/system/haraka.service
完整内容:
[Unit]
Description=Haraka Mail Transfer Agent
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/haraka -c /opt/haraka
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
💡 说明:
-c指向服务目录是 Haraka 的标准启动参数;不要用haraka /opt/haraka这种位置参数写法(实测会报Error: Undefined or erroneous arguments)。smtp.ini 保持默认(不启用 daemonize),由 systemd 负责进程生命周期;Restart=on-failure保证异常退出自动拉起。
加载并启用服务:
systemctl daemon-reload
systemctl enable haraka
systemctl start haraka
实测回显(enable 时创建符号链接):
Created symlink /etc/systemd/system/multi-user.target.wants/haraka.service → /etc/systemd/system/haraka.service.
4.6 安装验证
依次确认服务状态、监听端口、降权生效与 SMTP 会话。
服务状态:
systemctl is-active haraka
systemctl status haraka --no-pager -l | head -14
实测回显:
active
● haraka.service - Haraka Mail Transfer Agent
Loaded: loaded (/etc/systemd/system/haraka.service; enabled; vendor preset: enabled)
Active: active (running) since Wed 2026-10-21 01:52:51 UTC; 3s ago
Main PID: 3534 (node)
Tasks: 22 (limit: 94371)
Memory: 81.4M
CPU: 502ms
CGroup: /system.slice/haraka.service
├─3534 node /usr/bin/haraka -c /opt/haraka
└─3546 /usr/bin/node /usr/lib/node_modules/Haraka/haraka.js -c /opt/haraka
监听端口:
ss -ltnp | grep -E ":25 "
实测回显:
LISTEN 0 511 *:25 *:* users:(("node",pid=3534,fd=20))
降权生效验证(主进程 root、工作进程 haraka):
ps -eo user,pid,ppid,args | grep -E "[h]araka"
实测回显:
root 3534 1 node /usr/bin/haraka -c /opt/haraka
haraka 3546 3534 /usr/bin/node /usr/lib/node_modules/Haraka/haraka.js -c /opt/haraka
SMTP 会话验证(用 Python 标准库即可,无需安装额外工具):
python3 - <<'EOF'
import socket
s = socket.create_connection(("127.0.0.1", 25), timeout=15)
f = s.makefile("rb")
print("S:", f.readline().decode().rstrip())
s.send(b"EHLO tester.gmail.com\r\n")
while True:
l = f.readline().decode().rstrip()
print("S:", l)
if l[3:4] == " ": break
s.send(b"QUIT\r\n")
print("S:", f.readline().decode().rstrip())
s.close()
EOF
实测回显:
S: 220 mail01.example.com ESMTP Haraka/3.3.5 ready (AE8A41)
S: 250-mail01.example.com Hello ip6-localhost [127.0.0.1], Haraka is at your service.
S: 250-PIPELINING
S: 250-8BITMIME
S: 250-SMTPUTF8
S: 250-SIZE 26214400
S: 250 STARTTLS
S: 221 mail01.example.com closing connection. Have a jolly good day.
横幅中的 mail01.example.com 来自 config/me,Haraka/3.3.5 是版本号,(AE8A41) 是连接 UUID 前 6 位(config/connection.ini 的 [uuid] banner_chars=6 默认值)。STARTTLS 出现在能力列表中但尚未配证书(点击即失败),配置 TLS 见 4.7 节。
首次收信路径验证(向 host_list 中的域投递,默认 queue/smtp_forward 会转发到 localhost:2555——需先配置转发目标,完整收发链路见 4.8 节与《Haraka 3.3.5 配置参数详解》)。
4.7 配置 TLS 证书(消除启动 ERROR,启用 STARTTLS)
服务启动日志中若出现以下 ERROR,说明缺 TLS 证书(虽然 tls 插件默认未启用,内核仍会尝试加载证书):
[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.
生产环境应使用受信 CA 签发的证书(如 Let's Encrypt,证书与私钥拷贝到 config/tls_cert.pem 与 config/tls_key.pem)。内网/测试环境可用自签证书:
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"
chown haraka:haraka tls_key.pem tls_cert.pem
在 config/plugins 中启用 tls 插件(把 # tls 行的注释去掉,改为 tls),然后重启服务:
systemctl restart haraka
再次进行 EHLO + STARTTLS 验证:
python3 - <<'EOF'
import smtplib
s = smtplib.SMTP("127.0.0.1", 25, timeout=30)
code, msg = s.ehlo("tester.gmail.com")
print("STARTTLS advertised:", b"STARTTLS" in msg)
print("starttls:", s.starttls()[0])
s.close()
EOF
实测回显:
STARTTLS advertised: True
starttls: 220
服务端日志同步出现 TLS 握手成功记录:
[INFO] [0E1FD57C-3F38-4A94-9078-DD6FFAB9B68B] [tls] secured: cipher=TLS_AES_256_GCM_SHA384 version=TLSv1.3 verified=false
自签证书 verified=false 属预期(客户端未校验私有 CA);使用受信 CA 证书后为 verified=true。
4.8 端到端收发验证(queue/smtp_forward)
默认插件管线是「mail_from.is_resolvable 校验发件域 MX → rcpt_to.in_host_list 校验收件域 → queue/smtp_forward 转发到下游」。在 config/smtp_forward.ini 中指定下游 SMTP 服务器(生产环境通常指向内部邮箱系统;本例用同一台机上的下游调试端口 2555 演示):
vi /opt/haraka/config/smtp_forward.ini
内容(host 与 port 按实际下游修改):
host=127.0.0.1
port=2555
重启后投递一封测试邮件:
systemctl restart haraka
python3 - <<'EOF'
import smtplib
s = smtplib.SMTP("127.0.0.1", 25, timeout=30)
s.ehlo("tester.gmail.com")
r = s.sendmail("sender@gmail.com", ["postmaster@example.com"],
"Subject: install verify\r\n\r\nHello Haraka.\r\n")
print("refused:", r)
print(s.quit()[0])
EOF
实测回显(refused 为空字典表示全部收件人被接受):
refused: {}
221
在服务日志中确认转发执行(journalctl):
journalctl -u haraka --no-pager -n 6
实测回显(输出节选):
[INFO] [-] [core] [smtp_client] uuid=09CBBD42-EFBB-4AEB-BF96-FA081AD27E20 host=127.0.0.1 port=2555 created
[INFO] [FA00077B-6B12-4BA4-BC4B-B1C4B2DE77FE.1] [queue/smtp_forward] forwarding to 127.0.0.1:2555
[INFO] [FA00077B-6B12-4BA4-BC4B-B1C4B2DE77FE.1] [core] hook=queue plugin=queue/smtp_forward function=queue_forward params="" retval=OK msg="{\"0\":\"OK\"}"
[NOTICE] [FA00077B-6B12-4BA4-BC4B-B1C4B2DE77FE.1] [core] queue code=OK msg="OK (FA00077B-6B12-4BA4-BC4B-B1C4B2DE77FE.1)"
至此安装主线完成:systemd 托管、25 端口收信、转发链路全部打通。配置文件的逐项参数说明见本平台 《Haraka 3.3.5 配置参数详解》,命令行工具全集见 《Haraka 3.3.5 常用 CLI 命令详解》。
五、离线安装方案(B:npm tarball 离线包)
对于无法访问 npm registry 的隔离网络服务器,可在有网的跳板机上用 npm pack 下载离线包,拷贝到目标机安装。注意:npm install -g 安装 tarball 时仍需解析并下载依赖包,严格离线环境还需配合 npm install 的离线缓存(npm cache 目录整体拷贝)或内网 registry mirror;本章给出最常用的「跳板机打包 + 目标机安装」流程。
5.1 跳板机:下载离线包
在有网机器上执行:
npm pack Haraka@3.3.5
实测回显:
Haraka-3.3.5.tgz
记录校验和(拷贝到目标机后核对):
sha256sum Haraka-3.3.5.tgz
实测回显:
cf22799d7426632e6884ae109ca4ea7582f7828bb8366a08105c1e032f46e7a7 Haraka-3.3.5.tgz
也可以直接从 registry 下载官方 tarball(URL 大小写敏感,包名为大写 Haraka):
curl -O https://registry.npmjs.org/Haraka/-/Haraka-3.3.5.tgz
实测回显(HTTP 头节选):
HTTP/2 200
date: Wed, 21 Oct 2026 02:23:43 GMT
content-type: application/octet-stream
5.2 目标机:安装与验证
将 tgz 与前置的 Node.js 20 运行时(含依赖包缓存或内网 registry)准备就绪后:
sha256sum Haraka-3.3.5.tgz
npm install -g ./Haraka-3.3.5.tgz
haraka --version
实测回显(在本机用离线 tgz 重装验证,输出节选):
4 packages are looking for funding
run `npm fund` for details
Haraka.js — Version: 3.3.5
后续的 haraka -i、systemd 托管步骤与 4.3~4.8 节完全一致。
六、多方案对比与选择建议
| 维度 | 方案 A:在线 npm 安装 | 方案 B:离线 tarball 安装 |
|---|---|---|
| 前置网络 | 可达 npm registry | 仅跳板机需联网 |
| 版本控制 | Haraka@3.3.5 显式锁定 | 包文件即版本,天然锁定 |
| 完整性校验 | npm 内置完整性校验(integrity) | 手工 sha256 核对 |
| 依赖解析 | 自动拉取 | 目标机仍需依赖来源(缓存/内网 mirror) |
| 适用场景 | 常规服务器 | 隔离网段、审计要求留包 |
| 服务名/unit | 自建 haraka.service | 同左 |
选择建议:能联网走方案 A(官方推荐 npm install Haraka -g 即此形态);有合规留档或隔离网段走方案 B,并把 tgz 与 sha256 纳入发布物清单。
七、配置说明
安装完成后的最小配置清单(逐项含义与更多参数见《Haraka 3.3.5 配置参数详解》):
| 文件 | 必改项 | 作用 |
|---|---|---|
| config/me | 服务器对外主机名 | SMTP 横幅、Received 头、UUID 生成 |
| config/host_list | 本机收件域列表 | rcpt_to.in_host_list 的准入依据 |
| config/smtp.ini | user/group(降权)、listen(可选) | 运行用户与监听地址 |
| config/smtp_forward.ini | host/port | 入站邮件的转发下游 |
| config/plugins | 按需启用插件 | 管线顺序即处理顺序 |
| config/log.ini | level/format(可选) | 日志级别与格式 |
| config/tls_key.pem / tls_cert.pem | 证书与私钥 | STARTTLS 加密 |
日常运维操作(systemd):
systemctl restart haraka # 修改 config 后重启生效
systemctl status haraka # 状态检查
journalctl -u haraka -f # 实时日志
八、常见问题(FAQ)
以下问题均为实测中真实触发并解决的过程,遇到同类报错可直接对号入座。
Q1:执行 haraka -h <插件名> 报 Error: spawn less ENOENT 进程崩溃。
现象(实测):
Error: spawn less ENOENT
at ChildProcess._handle.onexit (node:internal/child_process:285:19)
原因:-h 会用 less 分页展示插件 README,最小化系统未安装 less。解决:
apt-get install -y less
Q2:MAIL FROM 被拒:550 5.7.27 Null MX: example.org does not send mail。
原因:默认启用的 mail_from.is_resolvable 插件会拒绝发布了 Null MX(RFC 7505)记录的域(example.org 于近年启用了 Null MX),这是设计行为。测试时换用有正常 MX 的发件域(如 gmail.com);业务上确需放行可在插件配置 [reject] 段调整 no_mx 策略(deny/defer/no)。
Q3:DATA 阶段被拒:451 Bare line-feed; see https://haraka.github.io/barelf/。
现象:用自研脚本发信时消息正文以裸 LF(\n)结尾即被拒。原因:Haraka 强制要求 DATA 内容使用 CRLF 行尾(RFC 合规检查)。解决:客户端按 SMTP 规范使用 \r\n,或直接使用成熟 SMTP 客户端库(如 Python smtplib 的 sendmail 方法会自动处理)。
Q4:投递报 550 Queueing failed,日志出现 EACCES 权限错误。
日志实测:
[ERROR] [-] [outbound] Unable to write queue file (...): Error: EACCES: permission denied, open '/opt/haraka/queue/__tmp__....'
原因:/opt/haraka/queue 出站队列目录由主进程(root)在首次启动时创建,属主为 root,而降权后的工作进程用户为 haraka,无法写入。解决:
chown haraka:haraka /opt/haraka/queue
⚠️ 注意:凡是 Haraka 运行中要写入的目录(queue、config 下生成文件、日志目录),属主都应是 config/smtp.ini 中 user= 指定的运行用户。首次启动前统一
chown -R haraka:haraka /opt/haraka最稳妥。
Q5:转发超时:450 plugin timeout (<UUID>.1)。
日志实测:
[CRIT] [...1] [core] Plugin queue/smtp_forward timed out on hook queue - make sure it calls the callback
原因:config/smtp_forward.ini 未配置时,queue/smtp_forward 默认转发到 localhost:2555;目标不通时连接挂起 30 秒后插件超时。解决:按 4.8 节写明 host/port,或换用其他队列插件(queue/discard 等)。
Q6:出站队列里收件域直接被弹回:bouncing mail: Nowhere to deliver mail to for domain: <域名>。
原因:Haraka 出站层按 DNS MX 路由,收件域无任何 MX/A 记录(或域不存在)时立即生成退信(bounce),不进入重试队列。测试受控转发路径可使用 smtp_forward.ini 的 per-domain 路由段([域名] + host=/port=),其 get_mx 钩子实测生效(返回 exchange=192.0.2.1 port=25 形态的固定路由)。
Q7:haraka /opt/haraka(位置参数写法)报 Error: Undefined or erroneous arguments。
解决:启动必须用 -c 选项:haraka -c /opt/haraka;同理 haraka -p <插件名> 生成插件时必须同时给 -c 指向服务目录,否则同样报此错。
Q8:升级与多版本管理。
npm view Haraka version # 查最新版
npm install -g Haraka@3.3.5 # 安装/锁定指定版本
npm ls -g --depth=0 # 查看已装版本
npm update -g Haraka # 升级到 latest(建议先看 Release Notes)
实测回显(npm ls):
/usr/lib
+-- corepack@0.34.6
+-- Haraka@3.3.5
升级后执行 systemctl restart haraka 并核对 haraka --version。
九、卸载与清理
完整卸载按依赖顺序执行:
systemctl disable --now haraka
实测回显:
Removed "/etc/systemd/system/multi-user.target.wants/haraka.service".
rm /etc/systemd/system/haraka.service
systemctl daemon-reload
npm uninstall -g Haraka
实测回显:
removed 96 packages in 234ms
rm -rf /opt/haraka
userdel haraka
验证残留清零:
which haraka || echo "haraka binary removed"
ls /opt/haraka 2>&1
实测回显:
haraka binary removed
ls: cannot access '/opt/haraka': No such file or directory
如需同时卸载 Node.js 运行时:apt-get purge -y nodejs && apt-get autoremove -y(仅当该服务器无其他 Node 业务时执行)。
十、参考链接
- Haraka 官网 Getting Started:https://haraka.github.io/getting_started (查询日期 2026-10-21)
- npm 包页(Haraka):https://www.npmjs.com/package/Haraka (查询日期 2026-10-21)
- NodeSource 安装脚本说明:https://github.com/nodesource/distributions (查询日期 2026-10-21)
- GitHub Releases:https://github.com/haraka/Haraka/releases (查询日期 2026-10-21)
- Haraka 官方 wiki(Performance-Tuning):https://github.com/haraka/Haraka/wiki/Performance-Tuning (查询日期 2026-10-21)
- 站内系列:《Haraka 3.3.5 介绍与选型指南》、《Haraka 3.3.5 配置参数详解》、《Haraka 3.3.5 常用 CLI 命令详解》