نصب Listmonk روی VPS با Ubuntu 24.04 و TLS
آموزش نصب Listmonk v6.2.0 روی Ubuntu 24.04 با PostgreSQL 16، config.toml، systemd و TLS؛ اتصال SMTP و هزینه واقعی بهبود نرخ تحویل ایمیل را بررسی کنید.
ملزومات خبرنامه خودمیزبان در Listmonk
Listmonk یک مدیر خبرنامه و فهرست ایمیل خودمیزبان است: یک باینری Go، یک پایگاهداده PostgreSQL، یک فایل پیکربندی و یک واحد systemd. یک VPS کوچک بدون دردسر آن را اجرا میکند، زیرا Listmonk مشترکها و صف کمپینها را ذخیره میکند، اما هرگز ایمیل را خودش ارسال نمیکند. این نرمافزار هر پیام را به یک سرور SMTP (پروتکل انتقال ساده ایمیل) تحویل میدهد؛ بنابراین نرخ تحویل شما به اعتبار آن سرور بستگی دارد، نه به این نرمافزار.
این راهنما Listmonk v6.2.0، یعنی نسخه فعلی در July 2026، را روی Ubuntu 24.04 نصب میکند. به یک VPS با آدرس IP عمومی، یک نام دامنه که کنترل آن را در اختیار دارید و PostgreSQL 12 یا جدیدتر نیاز دارید. نصب حدود یک ساعت زمان میبرد. ایجاد اعتبار ارسال چند هفته طول میکشد و این بخش در قسمتهای پایانی بررسی میشود.
نصب 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 صرفاً جنبه تزئینی ندارد. نصب schema، جدولها، نوعهای داده، indexها و functionها را ایجاد میکند؛ بنابراین نقش باید مالک پایگاهداده باشد. اگر Listmonk را به پایگاهدادهای متصل کنید که نقش دیگری مالک آن است، نصب با permission denied متوقف میشود؛ حتی اگر قبلاً GRANT CONNECT را اجرا کرده باشید.
پیش از ادامه، وجود پایگاهداده را تأیید کنید.
sudo -u postgres psql -tAc "SELECT datname FROM pg_database WHERE datname='listmonk';"این دستور listmonk را چاپ میکند. خط خالی یعنی دستور CREATE اجرا نشده است؛ بنابراین خروجی psql را دوباره بررسی کنید.
نصب باینری Listmonk
Listmonk برای هر معماری یک باینری ایستا منتشر میکند. ابتدا معماری سیستم خود را بررسی کنید، زیرا باینری amd64 روی یک VPS مبتنی بر ARM فایلی است که kernel از اجرای آن خودداری میکند.
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در یک VPS مبتنی بر ARM، 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 متصل میکند؛ بنابراین پنل مدیریت تا زمانی که reverse proxy را جلوی آن قرار ندهید، از اینترنت قابل دسترسی نیست. این خط را تغییر ندهید. در بخش [db]، host = "localhost"، port = 5432، user = "listmonk"، database = "listmonk" و ssl_mode = "disable" را دریافت میکنید. این مقادیر پیشفرض با پایگاه دادهای که ایجاد کردید مطابقت دارند؛ بنابراین تنها خطی که باید تغییر دهید، خط گذرواژه است.
ssl_mode = "disable" زمانی درست است که Postgres روی loopback و در همان سیستم گوش دهد، زیرا این ترافیک از ماشین خارج نمیشود. اگر پایگاه داده را به میزبان دیگری منتقل کردید، آن را روی require تنظیم کنید؛ در غیر این صورت گذرواژه بهصورت متن واضح از شبکه عبور میکند.
خط گذرواژه را در بخش [db] ویرایش کنید تا با role مطابقت داشته باشد. سپس service account را ایجاد کنید و فایل را از دسترسی همه loginهای دیگر خارج کنید.
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اکنون service account میتواند فایل را بخواند و هیچکس دیگر نمیتواند.
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 را چاپ میکند. هر unprivileged account دیگری که همان cat را اجرا کند، Permission denied را دریافت میکند. هدف همین است: این فایل گذرواژه پایگاه داده شما را بهصورت متن واضح نگه میدارد و یک سرور معمولاً بیش از یک login دارد. همین منطق درباره هر سرویسی که اجرا میکنید نیز صدق میکند؛ بنابراین یک بار کاربران سرویس با حداقل امتیاز را بخوانید و این روش را در همهجا اعمال کنید.
ایجاد schema با --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 نخستین اجرای installer است و schema موجود Listmonk را حذف میکند. اجرای دوباره آن روی پایگاهداده فعال، مشترکان شما را حذف میکند. در هر اسکریپتی که ممکن است دوبار اجرا شود، از --install --idempotent --yes استفاده کنید؛ این گزینه زمانی که جدولها از قبل وجود داشته باشند، کاری انجام نمیدهد. تغییرات schema که در release جدید ارائه میشوند، با --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 را چاپ میکند. خالی بودن نتیجه دستور دوم یعنی متغیرهای محیطی هرگز به process نرسیدهاند؛ بنابراین پنل بهجای آن، از شما میخواهد نخستین کاربر را در مرورگر ایجاد کنید.
اجرای Listmonk تحت systemd
/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 مسیرهای نسبی، از جمله مسیر بارگذاری رسانه در فایلسیستم، را نسبت به آن resolve میکند. 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 نشان دهد. بازگشت هر خط وضعیت HTTP توسط curl نشان میدهد که سرور پاسخ میدهد. شکست curl همراه با Connection refused به این معناست که فرایند هنگام راهاندازی متوقف شده است و journalctl -u listmonk -n 50 --no-pager علت را نشان میدهد. توجه کنید که enable --now بخشی است که پس از راهاندازی مجدد باقی میماند: فرایندی که بهصورت دستی اجرا شده باشد، پس از ارتقای بعدی kernel از بین میرود.
nginx و TLS را در بخش جلویی قرار دهید
Listmonk از HTTP ساده روی loopback استفاده میکند؛ بنابراین 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 بهصورت پیشفرض هر چیزی بزرگتر از 1 MB را با 413 Request Entity Too Large رد میکند. گواهی را با certbot صادر کنید. certbot خطوط listen 443 ssl و تغییر مسیر از پورت 80 را نیز برای شما مینویسد. مراحل در راهنمای گواهی Let's Encrypt برای nginx آمده است. پورتهای 80 و 443 را باز کنید و پورت 9000 را بسته نگه دارید، زیرا پراکسی از طریق loopback به آن دسترسی پیدا میکند. اگر فایروال هنوز پیکربندی نشده است، با مبانی فایروال ufw شروع کنید.
سپس پنل مدیریت را باز کنید و نشانی ریشه را در بخش Settings روی https://lists.example.com تنظیم کنید. نصب تازه دارای مقدار http://localhost:9000 است و Listmonk این مقدار را در هر پیوند لغو اشتراک و نشانی رسانهای که در ایمیل قرار میدهد، مینویسد. اگر پیش از تغییر این مقدار یک کمپین ارسال کنید، هر گیرنده پیوندهایی خواهد داشت که به دستگاه خودش اشاره میکنند. این پیوندها برای خواننده کار نمیکنند و برای فیلتر هرزنامه شبیه فرستندهای هستند که نمیتواند دامنه خودش را پیکربندی کند.
اتصال SMTP که در config.toml نیست
در config.toml بهدنبال بخش SMTP بگردید، اما چنین بخشی پیدا نخواهید کرد. تنظیمات ایمیل در پایگاهداده، در جدول settings قرار دارند و آنها را در پنل مدیریت، از بخش Settings و SMTP ویرایش میکنید. به همین دلیل فایل تولیدشده کوتاه باقی میماند و تغییر SMTP نیز به راهاندازی مجدد نیاز ندارد.
برای خود سرور SMTP دو گزینه واقعی وجود دارد. سرور خودتان را اجرا کنید؛ در این حالت اعتبار IP کاملاً در اختیار شماست و این کار بهتنهایی پروژهای مستقل محسوب میشود: اجرای سرور ایمیل شخصی با Mailcow جزئیات لازم را پوشش میدهد. یا Listmonk را به یک رله تراکنشی متصل کنید و مدیریت اعتبار IP را به ارائهدهنده دیگری بسپارید.
در هر دو حالت، از پورت 587 با STARTTLS یا از پورت 465 با TLS ضمنی استفاده کنید. برای ترافیک خروجی روی پورت 25 برنامهریزی نکنید. بیشتر ارائهدهندگان VPS این پورت را در حسابهای جدید بهطور پیشفرض مسدود میکنند. پورت 25 مسدودشده دقیقاً مانند اتصال متوقفشده به نظر میرسد، زیرا بستهها رد نمیشوند و فقط حذف میشوند. در نتیجه، کلاینت بهجای شکست سریع، تا پایان زمان انتظار منتظر میماند.
پیش از اعتماد به تنظیمات، آن را آزمایش کنید. یک فهرست ایجاد کنید، نشانی خودتان را بهعنوان مشترک اضافه کنید و یک کارزار تکگیرنده ارسال کنید. پیام دریافتشده را باز کنید و سرآیندهای کامل آن را بخوانید. سرآیند Authentication-Results که سمت دریافتکننده اضافه میکند، نشان میدهد SPF و DKIM با موفقیت عبور کردهاند یا خیر.
قابلیت تحویل، تمام کار است
Listmonk پیام را میسازد، فهرست را پیگیری میکند و نامه را تحویل میدهد. هر تصمیم درباره اینکه نامه به صندوق ورودی برسد یا نه، توسط ارائهدهنده دریافتکننده و با استفاده از نشانی IP ارسالکننده و دامنه ارسالکننده گرفته میشود. یک نشانی IP جدید برای VPS هیچ سابقهای ندارد و همه ارائهدهندگان بزرگ صندوقهای پستی، نبود سابقه را تا حدی مشکوک تلقی میکنند.
4 مورد اختیاری نیستند:
- یک رکورد TXT مربوط به SPF (چارچوب سیاست فرستنده) که میزبان مجاز برای ارسال از طرف دامنه شما را مشخص کند.
- یک کلید DKIM (نامه شناساییشده بر اساس دامنه) که بهصورت رکورد TXT منتشر شده باشد و امضای آن را mail server، نه Listmonk، انجام دهد.
- یک رکورد DMARC (احراز هویت، گزارشدهی و انطباق پیام مبتنی بر دامنه) که به گیرندهها بگوید هنگام شکست دو مورد اول چه کاری انجام دهند.
- یک mailbox برای پیامهای برگشتی که Listmonk آن را بخواند تا نشانیهایی که نامه را رد میکنند، بهجای تلاش مجدد نامحدود، از فهرست حذف شوند.
سپس در ابتدا بهآرامی ارسال کنید. دامنهای که تاکنون نامهای ارسال نکرده و ناگهان در یک ساعت 10000 پیام تحویل میدهد، دقیقاً شبیه یک حساب در معرض نفوذ است؛ بنابراین پیامهای آن نیز مانند پیامهای چنین حسابی فیلتر میشوند. کار را با مشترکانی آغاز کنید که بیشترین تعامل را دارند و حجم ارسال را طی چند روز افزایش دهید.
هر template نیز به یک لینک لغو اشتراک فعال نیاز دارد. در template مربوط به Listmonk، این لینک {{ UnsubscribeURL }} است و متن campaign در محل {{ template "content" . }} قرار میگیرد؛ این مورد باید در هر template دقیقاً یکبار ظاهر شود. campaign بدون لینک لغو اشتراک، بهجای لغو اشتراک، شکایتهای spam ایجاد میکند و شکایتها سریعترین راه برای از بین بردن اعتبار ارسال است؛ اعتباری که ساختن آن هفتهها زمان برده است.
نسخههای پشتیبان و نیازمندیهای واقعی بازیابی
دو مورد باید از سرور خارج شوند: dump پایگاهداده و config.toml. اگر تصاویر را در کمپینها بارگذاری میکنید، دایرکتوری رسانه را نیز اضافه کنید.
sudo -u postgres pg_dump -Fc listmonk > listmonk-$(date +%F).dumpاین dump شامل مشترکان، کمپینها، قالبها و تمام تنظیمات، از جمله اطلاعات احراز هویت SMTP، است. بنابراین آن را رمزنگاری کنید و خارج از این سرور نگه دارید. زمانبندی این کار مسئلهای حلشده است: به نسخههای پشتیبان رمزنگاریشده restic در فضای ذخیرهسازی راه دور مراجعه کنید. config.toml فقط چند خط است، اما رمز عبور پایگاهداده را در خود دارد؛ بنابراین با آن نیز به همین شکل برخورد کنید.
ارتقاها باید با ترتیب مشخصی انجام شوند. سرویس را متوقف کنید، یک dump بگیرید، باینری را در /usr/bin جایگزین کنید، listmonk --config /etc/listmonk/config.toml --upgrade را اجرا کنید، سپس سرویس را راهاندازی کنید. انتقالهای schema فقط به سمت جلو اجرا میشوند؛ بنابراین آن dump تنها راه بازگشت شما است.
چرا Listmonk اجرا نمیشود؟
ابتدا ژورنال را با journalctl -u listmonk -n 50 --no-pager بخوانید. تقریباً هر خطای راهاندازی در بلوک [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 یعنی مقدار database در [db] نام یک پایگاه داده واقعی نیست. sudo -u postgres psql -l پایگاههای داده موجود روی سرور را فهرست میکند، از جمله املایی که بهاشتباه استفاده کردهاید.
permission denied هنگام --install یعنی نقش میتواند متصل شود، اما مالک پایگاه داده نیست؛ بنابراین نمیتواند جدولها را در آن ایجاد کند. مشکل را با sudo -u postgres psql -c "ALTER DATABASE listmonk OWNER TO listmonk;" برطرف کنید و نصب را دوباره اجرا کنید.
سرویس هرگز اجرا نمیشود و ژورنال فایل پیکربندی را نام میبرد. پردازشی که با حساب listmonk اجرا میشود نمیتواند فایل config.toml را که مالک آن root:root است و سطح دسترسی آن 600 است، باز کند. stat -c '%U:%G %a' /etc/listmonk/config.toml باید root:listmonk 640 را چاپ کند و مالک دایرکتوری بالادست آن نیز باید root:listmonk 750 باشد.
پنل کار میکند، اما هیچ ایمیلی دریافت نمیشود. این مشکل مربوط به راهاندازی نیست. ابتدا Settings و SMTP را بررسی کنید، سپس گزارش اختصاصی کمپین را در پنل مدیریت بررسی کنید؛ این گزارش خطایی را که mail server برای هر تلاش ارسال برگردانده است، ثبت میکند.
FAQ
آیا برای استفاده از Listmonk به سرور ایمیل اختصاصی خودم نیاز دارم؟
خیر. Listmonk سرور ایمیل نیست. این برنامه به اطلاعات احراز هویت SMTP برای سروری نیاز دارد که ایمیل شما را دریافت و ارسال کند. این سرور میتواند یک relay تراکنشی یا سروری باشد که خودتان اجرا میکنید. این اطلاعات را در بخش Settings و SMTP در پنل مدیریتی تنظیم کنید، نه در config.toml، زیرا تنظیمات ایمیل در پایگاه داده ذخیره میشوند. از پورت 587 با STARTTLS یا پورت 465 با implicit TLS استفاده کنید، زیرا بیشتر ارائهدهندگان VPS، اتصال خروجی به پورت 25 را برای حسابهای جدید مسدود میکنند.
چرا لینکهای کمپین من به localhost:9000 اشاره میکنند؟
تنظیمات نشانی ریشه هنوز روی مقدار پیشفرض نصب، یعنی http://localhost:9000، قرار دارد. Listmonk این مقدار را هنگام ارسال کمپین در لینکهای لغو اشتراک و نشانیهای رسانه مینویسد. در پنل مدیریتی، Settings را باز کنید، نشانی ریشه را روی نشانی واقعی HTTPS خود تنظیم و ذخیره کنید. پیامهایی که قبلاً تحویل داده شدهاند قابل اصلاح نیستند. بنابراین، پیش از ارسال ایمیل به یک فهرست واقعی، یک کمپین آزمایشی برای خودتان ارسال کنید و لینک لغو اشتراک آن را باز کنید.
آیا اجرای دوباره --install مشترکان من را حذف میکند؟
بله. --install نصبکنندهای است که برای اجرای اولیه طراحی شده و schema موجود Listmonk را حذف میکند. --yes نیز اعلانی را حذف میکند که در غیر این صورت به شما هشدار میداد. در هر اسکریپتی که ممکن است دوبار اجرا شود، از --install --idempotent --yes استفاده کنید. این گزینه زمانی که جدولها از قبل وجود داشته باشند کاری انجام نمیدهد. برای اعمال تغییرات schema در یک release جدید، سرویس را متوقف کنید، یک pg_dump تهیه کنید، سپس --upgrade را اجرا کنید.
چرا Listmonk میگوید احراز هویت گذرواژه برای کاربر listmonk ناموفق بود؟
گذرواژه موجود در بخش [db] از /etc/listmonk/config.toml با گذرواژه نقش Postgres با همین نام مطابقت ندارد. خط journal برابر با 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 را اجرا کنید.