Ubuntu 22.04 安装 Haraka 3.3.5 指南

Ubuntu 22.04 从零安装 Haraka 3.3.5:Node.js 20 前置、npm 在线与离线双方案、haraka -i 初始化、systemd 托管、25 端口收信验证与卸载清理,全程实测

Haraka邮件服务器2026/10/21

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 前需要确认以下几项,逐条核对可避免后续排障:

  1. 一台 Ubuntu 22.04 服务器,具备 sudo 权限的账号与可用的软件源(本篇以 root 执行);
  2. Node.js ≥ 20:Haraka 3.3.5 的 npm 元数据声明 engines = { node: '>=20' },而 Ubuntu 22.04 官方仓库自带的 nodejs 为 12.22.9,必须先安装新版 Node.js(本文用 NodeSource 官方源装 20.x LTS);
  3. 可用的 DNS 解析:默认启用的 mail_from.is_resolvable 插件需要对发件域做 MX 查询,服务器需能访问 DNS;
  4. 25 端口未被占用:ss -ltnp | grep :25 应无输出;且公网服务器需确认运营商/云平台未封禁入站 25 端口(多数云平台默认封禁,需申请解封,否则只能收内部邮件);
  5. 网络可达 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.iniuser/group(降权)、listen(可选)运行用户与监听地址
config/smtp_forward.inihost/port入站邮件的转发下游
config/plugins按需启用插件管线顺序即处理顺序
config/log.inilevel/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 业务时执行)。

十、参考链接

  1. Haraka 官网 Getting Started:https://haraka.github.io/getting_started (查询日期 2026-10-21)
  2. npm 包页(Haraka):https://www.npmjs.com/package/Haraka (查询日期 2026-10-21)
  3. NodeSource 安装脚本说明:https://github.com/nodesource/distributions (查询日期 2026-10-21)
  4. GitHub Releases:https://github.com/haraka/Haraka/releases (查询日期 2026-10-21)
  5. Haraka 官方 wiki(Performance-Tuning):https://github.com/haraka/Haraka/wiki/Performance-Tuning (查询日期 2026-10-21)
  6. 站内系列:《Haraka 3.3.5 介绍与选型指南》、《Haraka 3.3.5 配置参数详解》、《Haraka 3.3.5 常用 CLI 命令详解》