SSD Nodes Learn 8GB RAM — $66/năm
Hướng dẫn Matt ConnorBởi Matt Connor · Cập nhật ngày 2026-08-01

Hướng dẫn tự cài đặt Listmonk trên VPS Ubuntu 24.04

Cài đặt Listmonk v6.2.0 trên Ubuntu 24.04 với PostgreSQL, systemd và TLS. Bài viết hướng dẫn cấu hình SMTP để tối ưu deliverability cho newsletter tự vận hành của bạn.

Verified Every command ran end-to-end on a fresh Ubuntu 24.04 server, July 30, 2026.

Những yêu cầu để tự vận hành newsletter với Listmonk

Listmonk là một trình quản lý newsletter và danh sách gửi thư tự vận hành: một file binary Go, một cơ sở dữ liệu PostgreSQL, một file cấu hình, và một unit systemd. Một VPS nhỏ có thể chạy nó mà không tốn nhiều tài nguyên, vì Listmonk chỉ lưu trữ người đăng ký và xếp hàng các chiến dịch chứ không trực tiếp gửi thư. Nó chuyển từng tin nhắn cho một máy chủ SMTP (simple mail transfer protocol), vì vậy tỷ lệ gửi thành công của bạn phụ thuộc vào uy tín của máy chủ đó chứ không phải phần mềm này.

Hướng dẫn này cài đặt Listmonk v6.2.0, phiên bản phát hành hiện tại tính đến tháng 7 năm 2026, trên Ubuntu 24.04. Bạn cần một VPS có địa chỉ IP công cộng, một tên miền mà bạn kiểm soát, và PostgreSQL 12 hoặc mới hơn. Việc cài đặt mất khoảng một giờ. Việc xây dựng uy tín gửi thư mất nhiều tuần, và phần đó sẽ được đề cập ở gần cuối.

Cài đặt PostgreSQL và tạo cơ sở dữ liệu

Ubuntu 24.04 phát hành PostgreSQL 16 trong kho lưu trữ của chính nó, phiên bản này vượt xa yêu cầu của Listmonk.

sudo apt update
sudo apt install -y postgresql curl
sudo systemctl enable --now postgresql

Tạo role và cơ sở dữ liệu trong một phiên psql duy nhất. -v ON_ERROR_STOP=1 khiến psql thoát ngay khi gặp câu lệnh lỗi đầu tiên, nhờ đó lỗi đánh máy sẽ không để lại một cấu hình dở dang trông như đã hoàn tất.

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;
SQL

OWNER listmonk không phải là trang trí. Việc cài đặt schema tạo ra các bảng, kiểu dữ liệu, chỉ mục và hàm, vì vậy role phải là chủ sở hữu của cơ sở dữ liệu. Nếu bạn trỏ Listmonk vào một cơ sở dữ liệu do role khác sở hữu, quá trình cài đặt sẽ dừng lại với permission denied, ngay cả khi bạn đã chạy GRANT CONNECT.

Xác nhận cơ sở dữ liệu đã tồn tại trước khi tiếp tục.

sudo -u postgres psql -tAc "SELECT datname FROM pg_database WHERE datname='listmonk';"

Lệnh đó sẽ in ra listmonk. Một dòng trống nghĩa là câu lệnh CREATE chưa bao giờ chạy, vì vậy hãy đọc lại đầu ra của psql.

Cài đặt binary Listmonk

Listmonk phát hành một binary tĩnh cho mỗi kiến trúc. Hãy kiểm tra kiến trúc của bạn trước, vì binary amd64 trên VPS ARM là tệp mà kernel sẽ từ chối thực thi.

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

Trên VPS ARM, hãy thay amd64 bằng arm64 trong tên tệp. listmonk --version việc in ra chuỗi phiên bản là bằng chứng đầu tiên cho thấy binary khớp với máy chủ của bạn.

Tạo config.toml và bảo mật tệp tin

--new-config ghi config.toml vào thư mục làm việc hiện tại. Đó là lý do tại sao cd nằm bên trong sh -c, chứ không phải trước sudo.

sudo install -d -m 750 /etc/listmonk
sudo sh -c 'cd /etc/listmonk && listmonk --new-config'

Tệp được tạo rất ngắn. Dưới mục [app], address = "localhost:9000" ràng buộc HTTP server chỉ với loopback, vì vậy bảng điều khiển quản trị không thể truy cập được từ internet cho đến khi bạn đặt một reverse proxy phía trước nó. Hãy giữ nguyên dòng đó. Dưới mục [db], bạn có host = "localhost", port = 5432, user = "listmonk", database = "listmonk"ssl_mode = "disable". Các giá trị mặc định đó đã khớp với cơ sở dữ liệu bạn đã tạo, vì vậy dòng duy nhất bạn phải thay đổi là mật khẩu.

ssl_mode = "disable" là chính xác khi Postgres lắng nghe trên loopback tại cùng một máy, vì lưu lượng đó không bao giờ rời khỏi máy chủ. Hãy chuyển cơ sở dữ liệu sang một host khác và thiết lập thành require, nếu không mật khẩu sẽ truyền qua mạng dưới dạng văn bản thuần (cleartext).

Chỉnh sửa dòng mật khẩu dưới mục [db] để khớp với role, sau đó tạo service account và tước quyền truy cập tệp tin khỏi mọi tài khoản đăng nhập khác.

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

Bây giờ service account có thể đọc tệp tin và không ai khác có thể làm điều đó.

sudo -u listmonk cat /etc/listmonk/config.toml > /dev/null && echo readable
stat -c '%U:%G %a' /etc/listmonk/config.toml

Lệnh đầu tiên in ra readable. Lệnh thứ hai in ra root:listmonk 640. Bất kỳ tài khoản không có đặc quyền nào khác cố gắng thực hiện cat đều nhận được Permission denied, đó chính là mục đích: tệp này chứa mật khẩu cơ sở dữ liệu của bạn dưới dạng văn bản thuần, và một máy chủ thường có nhiều hơn một tài khoản đăng nhập. Lý do tương tự áp dụng cho mọi dịch vụ bạn chạy, vì vậy hãy đọc nguyên tắc đặc quyền tối thiểu cho service user một lần và áp dụng nó ở mọi nơi.

Tạo schema với --install

--install xây dựng các bảng và nạp các thiết lập mặc định. Hãy thiết lập thông tin đăng nhập quản trị viên đầu tiên bằng các biến môi trường, để tài khoản tồn tại trước khi bảng điều khiển có thể truy cập được.

sudo -u listmonk env LISTMONK_ADMIN_USER=admin \
  LISTMONK_ADMIN_PASSWORD='another-long-random-password' \
  listmonk --config /etc/listmonk/config.toml --install --yes

--yes trả lời cho prompt xác nhận. Hãy đọc prompt đó một lần trước khi bạn tự động hóa nó, vì --install là trình cài đặt lần đầu và nó sẽ xóa schema Listmonk hiện có. Chạy lệnh này lần thứ hai trên một cơ sở dữ liệu đang hoạt động sẽ làm mất dữ liệu người đăng ký của bạn. Trong bất kỳ script nào có khả năng chạy hai lần, hãy sử dụng --install --idempotent --yes, lệnh này sẽ không thực hiện gì nếu các bảng đã tồn tại. Các thay đổi schema được phát hành trong một bản cập nhật mới được áp dụng bằng --upgrade, không bao giờ dùng --install.

Hãy kiểm tra kết quả từ phía cơ sở dữ liệu thay vì từ trình duyệt.

sudo -u postgres psql -d listmonk -c '\dt'
sudo -u postgres psql -d listmonk -tAc "SELECT username FROM users;"

Lệnh đầu tiên liệt kê các bảng của Listmonk, trong đó có subscribers, lists, campaigns, templatesbounces. Lệnh thứ hai in ra admin. Kết quả trống từ lệnh thứ hai nghĩa là các biến môi trường chưa được truyền đến tiến trình, vì vậy bảng điều khiển sẽ yêu cầu bạn tạo người dùng đầu tiên trên trình duyệt.

Chạy Listmonk dưới systemd

Viết /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.target

WorkingDirectory rất quan trọng vì Listmonk phân giải các đường dẫn tương đối, bao gồm cả đường dẫn tải lên media trên hệ thống tệp, dựa trên nó. After=postgresql.service chỉ ra lệnh khởi động, nó không chờ Postgres chấp nhận kết nối, vì vậy Restart=on-failure xử lý trường hợp Listmonk khởi động quá sớm và không thể kết nối.

sudo systemctl daemon-reload
sudo systemctl enable --now listmonk
ss -ltnp | grep 9000
curl -sI http://127.0.0.1:9000/

ss sẽ hiển thị 127.0.0.1:9000 ở trạng thái LISTEN. curl trả về bất kỳ dòng trạng thái HTTP nào nghĩa là server đang phản hồi. curl thất bại với Connection refused nghĩa là tiến trình đã chết trong quá trình khởi động, và journalctl -u listmonk -n 50 --no-pager sẽ cho biết lý do. Lưu ý rằng enable --now là phần duy trì sau khi khởi động lại: một tiến trình khởi động thủ công sẽ mất sau lần nâng cấp kernel tiếp theo.

Đặt nginx và TLS phía trước

Listmonk sử dụng HTTP thuần trên loopback, vì vậy nginx sẽ đảm nhận việc kết thúc TLS (transport layer security) và chuyển tiếp yêu cầu.

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 cần được tăng lên vì việc nhập danh sách người đăng ký và tải lên tệp tin là các yêu cầu file post, và nginx mặc định từ chối mọi yêu cầu trên 1 MB với 413 Request Entity Too Large. Hãy cấp chứng chỉ bằng certbot, công cụ này cũng sẽ tự động ghi các dòng listen 443 ssl và cấu hình chuyển hướng từ cổng 80 cho bạn: các bước thực hiện nằm trong hướng dẫn chứng chỉ Let's Encrypt cho nginx. Mở cổng 80 và 443, và giữ cổng 9000 đóng, vì proxy truy cập nó thông qua loopback. Nếu firewall chưa được cấu hình, hãy bắt đầu với các kiến thức cơ bản về ufw firewall.

Sau đó, mở bảng điều khiển quản trị và thiết lập root URL trong phần Settings thành https://lists.example.com. Một bản cài đặt mới sẽ mang giá trị http://localhost:9000, và Listmonk ghi giá trị đó vào mọi liên kết hủy đăng ký và URL phương tiện mà nó chèn vào email. Nếu bạn gửi chiến dịch trước khi thay đổi giá trị này, mỗi người nhận sẽ nhận được các liên kết trỏ về máy tính của chính họ. Các liên kết này sẽ lỗi đối với người đọc, và đối với bộ lọc thư rác, chúng trông giống như người gửi không biết cách cấu hình tên miền của riêng mình.

Kết nối SMTP, không nằm trong config.toml

Tìm kiếm config.toml cho mục SMTP và bạn sẽ không thấy mục nào cả. Các thiết lập mail nằm trong cơ sở dữ liệu, tại bảng settings, và bạn chỉnh sửa chúng trong bảng điều khiển quản trị tại mục Settings và SMTP. Đó là lý do tệp được tạo ra vẫn rất ngắn gọn, và cũng là lý do việc thay đổi SMTP không cần khởi động lại hệ thống.

Có hai lựa chọn thực tế cho chính máy chủ SMTP. Tự vận hành máy chủ của riêng bạn, điều này đặt toàn bộ uy tín vào tay bạn và là một dự án thực sự: tự vận hành máy chủ mail với Mailcow bao gồm những gì liên quan đến việc này. Hoặc trỏ Listmonk tới một dịch vụ chuyển tiếp giao dịch (transactional relay) và để bên khác chịu trách nhiệm về uy tín IP.

Dù chọn cách nào, hãy sử dụng cổng 587 với STARTTLS, hoặc cổng 465 với TLS ngầm định. Đừng dự định sử dụng cổng 25 cho lưu lượng gửi đi. Hầu hết các nhà cung cấp VPS đều chặn cổng này theo mặc định trên các tài khoản mới, và cổng 25 bị chặn trông giống hệt như một kết nối bị treo, vì các gói tin bị hủy thay vì bị từ chối, do đó client sẽ đợi cho đến khi hết thời gian chờ thay vì báo lỗi ngay lập tức.

Hãy kiểm tra trước khi tin tưởng. Tạo một danh sách, thêm địa chỉ của chính bạn làm người đăng ký, và gửi một chiến dịch tới một người nhận. Mở tin nhắn đã nhận và đọc toàn bộ header. Header Authentication-Results được thêm vào bởi phía nhận sẽ cho bạn biết liệu SPF và DKIM có vượt qua kiểm tra hay không.

Khả năng gửi email là toàn bộ công việc

Listmonk tạo nội dung, quản lý danh sách và chuyển email đi. Mọi quyết định về việc email có đến được hộp thư đến hay không đều do nhà cung cấp dịch vụ nhận quyết định, dựa trên địa chỉ IP gửi và domain gửi. Một IP VPS mới hoàn toàn không có lịch sử, và việc không có lịch sử bị mọi nhà cung cấp hộp thư lớn coi là hơi đáng ngờ.

Bốn yếu tố sau đây là bắt buộc:

  • Một bản ghi TXT SPF (sender policy framework) chỉ định host được phép gửi email thay cho domain của bạn.
  • Một khóa DKIM (domainkeys identified mail) được công bố dưới dạng bản ghi TXT, với việc ký email do mail server thực hiện thay vì Listmonk.
  • Một bản ghi DMARC (domain based message authentication, reporting and conformance), cho biết bên nhận cần làm gì khi hai yếu tố đầu tiên thất bại.
  • Một hộp thư bounce mà Listmonk đọc được, để các địa chỉ từ chối nhận email sẽ bị loại khỏi danh sách thay vì bị gửi lại mãi mãi.

Sau đó, hãy gửi chậm lúc ban đầu. Một domain chưa từng gửi email mà đột ngột gửi mười nghìn tin nhắn trong một giờ có hình thái giống hệt một tài khoản bị chiếm quyền, vì vậy nó sẽ bị lọc như một tài khoản spam. Hãy bắt đầu với những người đăng ký tương tác nhiều nhất và tăng dần lưu lượng trong vài ngày.

Mỗi template cũng cần một liên kết hủy đăng ký hoạt động. Trong template của Listmonk, đó là {{ UnsubscribeURL }}, và nội dung chiến dịch sẽ nằm ở vị trí của {{ template "content" . }}, thành phần này phải xuất hiện chính xác một lần trong mỗi template. Một chiến dịch không có liên kết hủy đăng ký sẽ nhận về các khiếu nại spam thay vì yêu cầu hủy đăng ký, và khiếu nại là cách nhanh nhất để làm mất uy tín gửi email mà bạn đã mất nhiều tuần để xây dựng.

Sao lưu và những yêu cầu thực tế khi khôi phục

Có hai thứ phải được đưa ra khỏi máy chủ: bản dump cơ sở dữ liệu và config.toml. Hãy thêm thư mục media nếu bạn tải ảnh lên các chiến dịch.

sudo -u postgres pg_dump -Fc listmonk > listmonk-$(date +%F).dump

Bản dump đó chứa danh sách người đăng ký, chiến dịch, mẫu email và mọi thiết lập, bao gồm cả thông tin xác thực SMTP, vì vậy hãy mã hóa nó và lưu trữ bên ngoài máy chủ này. Việc lập lịch cho tác vụ này đã có giải pháp: xem sao lưu restic đã mã hóa tới bộ lưu trữ từ xa. config.toml chỉ gồm vài dòng nhưng chứa mật khẩu cơ sở dữ liệu, vì vậy hãy xử lý nó theo cách tương tự.

Việc nâng cấp phải tuân theo một trình tự. Dừng dịch vụ, tạo bản dump, thay thế tệp nhị phân trong /usr/bin, chạy listmonk --config /etc/listmonk/config.toml --upgrade, sau đó khởi động lại dịch vụ. Các thay đổi lược đồ (schema migration) chỉ chạy theo một chiều, vì vậy bản dump đó là con đường duy nhất để bạn quay lại trạng thái cũ.

Tại sao Listmonk không khởi động được?

Trước tiên hãy đọc journal bằng journalctl -u listmonk -n 50 --no-pager. Hầu như mọi lỗi khởi động đều nằm ở một dòng trong khối [db].

pq: password authentication failed for user "listmonk" nghĩa là mật khẩu trong [db] không khớp với Postgres role. Tiền tố pq là do Postgres driver báo cáo việc server từ chối kết nối, nghĩa là file cấu hình đã được đọc đúng nhưng thông tin xác thực bị sai. Hãy reset role bằng sudo -u postgres psql -c "ALTER USER listmonk WITH PASSWORD 'new-password';" và điền chuỗi ký tự giống hệt vào file.

pq: database "listmonk" does not exist nghĩa là giá trị database trong [db] không phải là tên của một database thực tế. sudo -u postgres psql -l sẽ liệt kê những gì đang có trên server, bao gồm cả cách viết sai mà bạn đã dùng.

permission denied trong quá trình --install nghĩa là role có thể kết nối nhưng không sở hữu database đó, nên nó không thể tạo bảng bên trong. Hãy sửa bằng sudo -u postgres psql -c "ALTER DATABASE listmonk OWNER TO listmonk;" và chạy lại lệnh cài đặt.

Service không bao giờ khởi động và journal báo lỗi file cấu hình. Một tiến trình chạy dưới quyền listmonk không thể mở file config.toml nếu nó đang được để ở quyền root:root với mode 600. stat -c '%U:%G %a' /etc/listmonk/config.toml phải in ra root:listmonk 640, và thư mục chứa nó phải là root:listmonk 750.

Bảng điều khiển hoạt động nhưng không có email nào được gửi đến. Đây không phải là lỗi khởi động. Trước tiên hãy kiểm tra Settings và SMTP, sau đó kiểm tra log của chiến dịch trong bảng điều khiển admin, nơi ghi lại lỗi mà mail server trả về cho mỗi lần thử gửi.

FAQ

Tôi có cần máy chủ mail riêng để dùng Listmonk không?

Không. Listmonk không phải là một máy chủ mail. Nó cần thông tin xác thực SMTP của một máy chủ tiếp nhận và gửi mail cho bạn, có thể là một dịch vụ relay giao dịch hoặc máy chủ mail do bạn tự vận hành. Hãy thiết lập các thông tin xác thực đó trong mục Settings và SMTP trên bảng điều khiển quản trị, không phải trong config.toml, vì các thiết lập mail được lưu trong cơ sở dữ liệu. Hãy sử dụng cổng 587 với STARTTLS hoặc cổng 465 với TLS ngầm định, vì hầu hết các nhà cung cấp VPS đều chặn cổng 25 chiều đi trên các tài khoản mới.

Tại sao các liên kết trong chiến dịch của tôi lại trỏ về localhost:9000?

Thiết lập root URL vẫn đang ở giá trị mặc định khi cài đặt là http://localhost:9000. Listmonk ghi giá trị đó vào các liên kết hủy đăng ký và URL phương tiện tại thời điểm chiến dịch được gửi đi. Hãy mở Settings trong bảng điều khiển quản trị, đặt root URL thành địa chỉ HTTPS thực của bạn và lưu lại. Các tin nhắn đã gửi không thể sửa được, vì vậy hãy tự gửi một chiến dịch thử nghiệm và nhấp vào liên kết hủy đăng ký trong đó trước khi gửi cho danh sách thực.

Việc chạy lại --install có làm mất danh sách người đăng ký của tôi không?

Có. --install là trình cài đặt lần đầu, nó sẽ xóa schema hiện tại của Listmonk, và --yes loại bỏ lời nhắc cảnh báo bạn. Trong bất kỳ script nào có khả năng chạy hai lần, hãy sử dụng --install --idempotent --yes, lệnh này sẽ không thực hiện gì nếu các bảng đã tồn tại. Để áp dụng các thay đổi schema trong một bản phát hành mới, hãy dừng dịch vụ, thực hiện pg_dump, sau đó chạy --upgrade.

Tại sao Listmonk báo xác thực mật khẩu thất bại cho người dùng listmonk?

Mật khẩu trong khối [db] của /etc/listmonk/config.toml không khớp với role Postgres cùng tên. Dòng log là pq: password authentication failed for user "listmonk", và pq là driver Postgres chuyển tiếp thông báo từ chối từ máy chủ, điều này có nghĩa là file cấu hình đã được tìm thấy và đọc. Hãy đặt lại mật khẩu của role đó bằng sudo -u postgres psql -c "ALTER USER listmonk WITH PASSWORD 'new-password';", ghi chuỗi ký tự giống hệt vào file cấu hình, sau đó chạy sudo systemctl restart listmonk.