Listmonk 自托管新闻通讯:Ubuntu 24.04 VPS 部署
在 Ubuntu 24.04 上部署 Listmonk v6.2.0,配置 PostgreSQL、config.toml、systemd 和 TLS,接入 SMTP,并说明软件不负责投递,邮件信誉建立通常需要数周。
Listmonk 自托管新闻通讯所需条件
Listmonk 是一个自托管的新闻通讯和邮件列表管理器:一个 Go 二进制文件、一个 PostgreSQL 数据库、一个配置文件和一个 systemd 单元。小型 VPS 即可轻松运行它,因为 Listmonk 只负责存储订阅者和排列营销活动队列,不负责实际投递邮件。它会将每封邮件交给 SMTP(简单邮件传输协议)服务器,因此邮件投递率取决于该服务器的信誉,而不是这个软件。
本指南将在 Ubuntu 24.04 上安装 Listmonk v6.2.0,即截至 July 2026 的当前版本。您需要一台具有公网 IP 地址的 VPS、一个由您控制的域名,以及 PostgreSQL 12 或更高版本。安装大约需要 1 小时。建立发信信誉需要数周,本文将在接近结尾处介绍相关内容。
安装 PostgreSQL 并创建数据库
Ubuntu 24.04 自带的软件仓库提供 PostgreSQL 16,版本远高于 Listmonk 的要求。
sudo apt update
sudo apt install -y postgresql curl
sudo systemctl enable --now postgresql在同一个 psql 会话中创建角色和数据库。-v ON_ERROR_STOP=1 会让 psql 在第一条失败的语句处退出,因此即使存在拼写错误,也不会留下看似完成但实际只完成一半的配置。
sudo -u postgres psql -v ON_ERROR_STOP=1 <<'SQL'
CREATE USER listmonk WITH PASSWORD 'pick-a-long-random-password';
CREATE DATABASE listmonk OWNER listmonk;
SQLOWNER listmonk 不是可有可无的设置。安装架构时会创建表、类型、索引和函数,因此该角色必须拥有数据库。让 Listmonk 使用其他角色拥有的数据库时,即使已经运行 GRANT CONNECT,安装仍会因 permission denied 而停止。
继续之前,确认数据库已存在。
sudo -u postgres psql -tAc "SELECT datname FROM pg_database WHERE datname='listmonk';"该命令会输出 listmonk。如果输出为空行,说明 CREATE 语句从未执行,请重新查看 psql 输出。
安装 Listmonk 二进制文件
Listmonk 会为每种架构发布一个静态二进制文件。请先确认服务器架构,因为在 ARM VPS 上运行 amd64 二进制文件时,内核会拒绝执行该文件。
dpkg --print-architecture
cd /tmp
curl -fsSLO https://github.com/knadh/listmonk/releases/download/v6.2.0/listmonk_6.2.0_linux_amd64.tar.gz
tar -xzf listmonk_6.2.0_linux_amd64.tar.gz
sudo install -m 755 listmonk /usr/bin/listmonk
listmonk --version在 ARM VPS 上,将文件名中的 amd64 替换为 arm64。listmonk --version 输出版本字符串,首先证明该二进制文件与当前机器匹配。
生成 config.toml 并限制其访问权限
--new-config 会在当前工作目录中写入 config.toml。因此,cd 位于 sh -c 内,而不是位于 sudo 之前。
sudo install -d -m 750 /etc/listmonk
sudo sh -c 'cd /etc/listmonk && listmonk --new-config'生成的文件很短。在 [app] 下,address = "localhost:9000" 仅将 HTTP 服务器绑定到 loopback,因此在前面配置反向代理之前,管理面板无法从互联网访问。不要修改这一行。在 [db] 下,你会看到 host = "localhost"、port = 5432、user = "listmonk"、database = "listmonk" 和 ssl_mode = "disable"。这些默认值已经与刚创建的数据库匹配,因此只需修改密码这一行。
当 Postgres 在同一台计算机上监听 loopback 时,ssl_mode = "disable" 配置正确,因为该流量不会离开计算机。如果将数据库移到另一台主机,请将其设置为 require,否则密码会以明文形式通过网络传输。
编辑 [db] 下的密码行,使其与该角色匹配,然后创建服务账户,并禁止其他登录账户访问该文件。
sudo useradd --system --home-dir /var/lib/listmonk --create-home --shell /usr/sbin/nologin listmonk
sudo chown -R root:listmonk /etc/listmonk
sudo chmod 640 /etc/listmonk/config.toml现在,服务账户可以读取该文件,其他账户都无法读取。
sudo -u listmonk cat /etc/listmonk/config.toml > /dev/null && echo readable
stat -c '%U:%G %a' /etc/listmonk/config.toml第一个命令会输出 readable。第二个命令会输出 root:listmonk 640。任何其他非特权账户尝试执行相同的 cat 时,都会得到 Permission denied。这正是目的所在:该文件以明文保存数据库密码,而服务器通常不止一个登录账户。同样的原则适用于运行的每项服务,因此请阅读一次最小权限服务用户,并在所有服务中应用这一原则。
使用 --install 创建架构
--install 会创建表并写入默认设置。通过环境变量设置第一个管理员登录信息,确保面板可访问前,管理员账户已经存在。
sudo -u listmonk env LISTMONK_ADMIN_USER=admin \
LISTMONK_ADMIN_PASSWORD='another-long-random-password' \
listmonk --config /etc/listmonk/config.toml --install --yes--yes 会确认提示。自动执行前,先手动阅读一次该提示,因为 --install 是首次安装程序,会删除现有的 Listmonk 架构。在正在使用的数据库上再次运行它会销毁订阅者数据。在任何可能执行两次的脚本中,应使用 --install --idempotent --yes;如果表已经存在,该命令不会执行任何操作。新版本中发布的架构变更应使用 --upgrade 应用,不能使用 --install。
从数据库端检查结果,不要通过浏览器检查。
sudo -u postgres psql -d listmonk -c '\dt'
sudo -u postgres psql -d listmonk -tAc "SELECT username FROM users;"第一个命令会列出 Listmonk 的表,其中包括 subscribers、lists、campaigns、templates 和 bounces。第二个命令会输出 admin。如果第二个命令返回空结果,说明环境变量没有传递到进程,因此面板会改为要求您在浏览器中创建第一个用户。
在 systemd 下运行 Listmonk
编写 /etc/systemd/system/listmonk.service。
[Unit]
Description=Listmonk newsletter and mailing list manager
After=network-online.target postgresql.service
Wants=network-online.target
[Service]
Type=simple
User=listmonk
Group=listmonk
WorkingDirectory=/var/lib/listmonk
ExecStart=/usr/bin/listmonk --config /etc/listmonk/config.toml
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
[Install]
WantedBy=multi-user.targetWorkingDirectory 很重要,因为 Listmonk 会以它为基准解析相对路径,包括文件系统中的媒体上传路径。After=postgresql.service 只负责安排启动顺序,不会等待 Postgres 接受连接,因此 Restart=on-failure 可以处理 Listmonk 启动过早、无法连接的情况。
sudo systemctl daemon-reload
sudo systemctl enable --now listmonk
ss -ltnp | grep 9000
curl -sI http://127.0.0.1:9000/ss 应显示 127.0.0.1:9000 处于 LISTEN 状态。curl 返回任意 HTTP 状态行,都表示服务器正在响应。curl 如果失败并显示 Connection refused,说明进程在启动期间退出,journalctl -u listmonk -n 50 --no-pager 会说明原因。请注意,enable --now 才能在重启后继续生效:手动启动的进程会在下一次内核升级后退出。
在前端配置 nginx 和 TLS
Listmonk 在 loopback 上使用明文 HTTP,因此由 nginx 终止 TLS(传输层安全性)并转发请求。
server {
listen 443 ssl;
server_name lists.example.com;
client_max_body_size 25m;
location / {
proxy_pass http://127.0.0.1:9000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}由于订阅者导入和媒体上传都属于文件上传请求,因此必须提高 client_max_body_size;nginx 默认会通过 413 Request Entity Too Large 拒绝超过 1 MB 的请求。使用 certbot 签发证书。certbot 还会为您写入 listen 443 ssl 配置行,并配置从端口 80 重定向到 HTTPS:具体步骤请参阅nginx 的 Let's Encrypt 证书指南。开放端口 80 和 443,并保持端口 9000 关闭,因为代理通过 loopback 访问该端口。如果防火墙尚未配置,请先阅读ufw 防火墙基础。
然后打开管理面板,在 Settings 中将根 URL 设置为 https://lists.example.com。全新安装默认使用 http://localhost:9000,Listmonk 会将该值写入邮件中的每个退订链接和媒体 URL。修改该值之前先发送一次活动邮件,每位收件人收到的链接都会指向发送邮件的那台机器。收件人无法访问这些链接;对垃圾邮件过滤器而言,这些链接还表明发件人无法正确配置自己的域名。
连接 SMTP(该配置不在 config.toml 中)
搜索 config.toml,你不会找到 SMTP 配置段。邮件设置存储在数据库的 settings 表中,可在管理面板的 Settings 和 SMTP 下编辑。因此,生成的文件会保持很短;SMTP 设置变更也不需要重启。
SMTP 服务器本身有两种可行方案。自行运行 SMTP 服务器。这样,发信信誉完全由您负责,而且这本身就是一个完整项目:使用 Mailcow 运行自己的邮件服务器介绍了相关工作。或者,将 Listmonk 指向事务性邮件中继,由其他服务商负责 IP 信誉。
无论选择哪种方案,都应使用带 STARTTLS 的 587 端口,或使用隐式 TLS 的 465 端口。不要规划使用出站 25 端口。大多数 VPS 服务商会在新账户上默认封锁该端口。被封锁的 25 端口看起来与连接挂起完全相同,因为数据包会被丢弃,而不是被拒绝。因此,客户端会等待超时,而不会立即失败。
在信任它之前先进行测试。创建一个列表,将您自己的地址添加为订阅者,然后发送一封只有一个收件人的邮件活动。打开收到的邮件并查看完整邮件头。接收方添加的 Authentication-Results 邮件头会告诉您 SPF 和 DKIM 是否通过。
投递能力是完整系统的基础
Listmonk 负责构建邮件、跟踪列表并移交邮件。邮件能否进入收件箱,完全由收件方服务商根据发送 IP 地址和发送域名决定。新 VPS IP 没有任何历史记录,而所有大型邮箱服务商都会将没有历史记录视为轻度可疑。
以下四项不可省略:
- SPF(sender policy framework,发件人策略框架)TXT 记录,用于声明允许代表您的域名发送邮件的主机。
- DKIM(domainkeys identified mail,域名密钥识别邮件)密钥,并将其发布为 TXT 记录。签名应由邮件服务器完成,而不是由 Listmonk 完成。
- DMARC(domain based message authentication, reporting and conformance,基于域名的消息认证、报告和一致性)记录,用于告知收件方前两项检查失败时应采取的措施。
- 供 Listmonk 读取的退信邮箱。这样拒收邮件的地址会从列表中移除,而不会被无限重试。
然后从低速率开始发送。一个从未发送过邮件的域名如果突然在一小时内投递一万封邮件,其行为特征与账户遭入侵完全一致,因此会被按此类行为过滤。先向互动最积极的订阅者发送,再在数天内逐步提高发送量。
每个模板还必须包含可用的退订链接。在 Listmonk 模板中,该链接是 {{ UnsubscribeURL }};活动正文会插入到 {{ template "content" . }} 所在的位置,并且每个模板中必须恰好出现一次。没有退订链接的活动会引发垃圾邮件投诉,而不是退订。投诉是损失发送信誉的最快方式,而建立这种信誉可能需要数周。
备份,以及实际恢复所需的内容
必须将两项内容移出此服务器:数据库转储和 config.toml。如果您会将图片上传到营销活动中,还应加入媒体目录。
sudo -u postgres pg_dump -Fc listmonk > listmonk-$(date +%F).dump该转储包含订阅者、营销活动、模板和所有设置,包括 SMTP 凭据。因此,请对其加密,并将其保存在此服务器之外。备份调度已经有成熟的解决方案:请参阅将加密的 restic 备份存储到远程存储。config.toml只有几行内容,但包含数据库密码,因此应按相同方式处理。
升级必须按固定顺序执行。停止服务,创建转储,将 /usr/bin 中的二进制文件替换为新版本,运行 listmonk --config /etc/listmonk/config.toml --upgrade,然后启动服务。架构迁移只能向前执行,因此该转储是唯一的回退方式。
Listmonk 为什么无法启动?
首先使用 journalctl -u listmonk -n 50 --no-pager 查看 journal。几乎所有启动失败都能在 [db] 块中找到一行对应信息。
pq: password authentication failed for user "listmonk" 表示 [db] 中的密码与 Postgres 角色不匹配。pq 前缀表示 Postgres 驱动报告了服务器的拒绝信息,因此配置已正确读取,错误出在凭据上。使用 sudo -u postgres psql -c "ALTER USER listmonk WITH PASSWORD 'new-password';" 重置角色,并将完全相同的字符串写入文件。
pq: database "listmonk" does not exist 表示 [db] 中的 database 值不是实际存在的数据库名称。sudo -u postgres psql -l 会列出服务器上实际存在的数据库,其中也包括你误写的名称。
在运行 --install 期间出现 permission denied,表示该角色可以连接,但不拥有此数据库,因此无法在其中创建表。使用 sudo -u postgres psql -c "ALTER DATABASE listmonk OWNER TO listmonk;" 修复,然后再次运行安装。
服务始终无法启动,并且 journal 指向配置文件。 以 listmonk 身份运行的进程无法打开权限设置为 root:root、模式为 600 的 config.toml。stat -c '%U:%G %a' /etc/listmonk/config.toml 应输出 root:listmonk 640,其上级目录应为 root:listmonk 750。
管理面板可以使用,但没有邮件到达。 这不是启动问题。首先检查 Settings 和 SMTP,然后在管理面板中查看该 campaign 自身的日志。该日志会记录邮件服务器针对每次尝试返回的错误。
FAQ
使用 Listmonk 是否需要自己的邮件服务器?
不需要。Listmonk 不是邮件服务器。它需要一个能够接收并投递邮件的服务器的 SMTP 凭据。该服务器可以是事务性中继,也可以是您自行运行的邮件服务器。在管理面板的 Settings 和 SMTP 中设置这些凭据,不要在 config.toml 中设置,因为邮件设置存储在数据库中。使用端口 587 和 STARTTLS,或使用端口 465 和隐式 TLS,因为大多数 VPS 提供商会封锁新账户的出站端口 25。
为什么我的营销邮件中的链接指向 localhost:9000?
根 URL 设置仍然是安装时的默认值 http://localhost:9000。Listmonk 会在发送营销邮件时,将该值写入退订链接和媒体 URL。打开管理面板中的 Settings,将根 URL 设置为真实的 HTTPS 地址,然后保存。已经投递的邮件无法修正,因此请先向自己发送测试营销邮件,并在发送给真实列表前点击其中的退订链接。
再次运行 --install 会清除订阅者吗?
会。--install 是首次安装程序,会删除现有的 Listmonk 架构;--yes 会移除原本用于警告您的确认提示。在可能运行两次的脚本中,请使用 --install --idempotent --yes。如果表已存在,该命令不会执行任何操作。要在新版本中应用架构变更,请停止服务,创建 pg_dump,然后运行 --upgrade。
为什么 Listmonk 提示用户 listmonk 的密码身份验证失败?
/etc/listmonk/config.toml 的 [db] 块中的密码与同名 Postgres 角色的密码不匹配。日志中的相关行是 pq: password authentication failed for user "listmonk"。pq 表示 Postgres 驱动正在转发服务器的拒绝信息,这说明配置文件已找到并成功读取。使用 sudo -u postgres psql -c "ALTER USER listmonk WITH PASSWORD 'new-password';" 重置该角色的密码,将完全相同的字符串写入配置文件,然后运行 sudo systemctl restart listmonk。