
SaaS newsletter platforms get pricey once you cross a few thousand subscribers. If you’d rather pay a steady VPS bill and keep subscriber data under your own roof, you can self-host listmonk and run campaigns on infrastructure you control.
This is an open-source spotlight with an operator’s bias. You’ll see who listmonk fits, where hosted tools still win, and a repeatable deployment pattern for real servers.
We’ll use Docker Compose, PostgreSQL, Nginx with HTTPS, SMTP, backups, and low-drama updates.
Why listmonk is worth self-hosting
listmonk is a self-hosted newsletter and mailing list manager built for speed. Your audience lives in your database. Campaigns send through the SMTP provider you choose.
You still get segmentation and templates, without adopting a full marketing automation suite.
Who it’s for:
- Product teams sending release notes, weekly digests, or transactional-adjacent announcements.
- Agencies running newsletters for multiple client brands (especially if you already manage VPS hosting).
- Blog and WooCommerce site owners who want email ownership without per-subscriber pricing.
Who should pause: If you need heavy CRM workflows, built-in landing pages, complex drip automations, or multi-step customer journeys, listmonk will feel intentionally lean. In those cases, a hosted marketing platform may save time.
License note: listmonk is open-source (AGPL-3.0). This matters if you modify it and offer it as a networked service.
For straightforward self-hosting, it’s usually not an issue. Still, read the license if you’re running it commercially.
listmonk vs hosted newsletter platforms (the practical trade-offs)
Hosted tools (Mailchimp, Campaign Monitor, Brevo, etc.) feel effortless because maintenance and deliverability tooling are bundled. You pay for that bundle.
With listmonk, you trade SaaS pricing for a predictable VPS bill and day-to-day operational ownership.
- Cost model: listmonk is free to run; your costs are VPS + SMTP sending. Hosted platforms usually scale pricing with contacts.
- Data control: listmonk keeps subscribers in your PostgreSQL database. Export is straightforward and you’re not locked into a vendor’s data model.
- Performance: listmonk stays fast under load. With enough CPU and a tuned SMTP provider, sending large batches is realistic.
- Deliverability: you still need correct SPF/DKIM/DMARC and a reputable sending path. Self-hosting doesn’t remove email rules; it just makes them your job.
If you want to tighten deliverability basics on your domain, HostMyCode has a solid guide on setting up DKIM, SPF, and DMARC.
What you need to self-host listmonk on a VPS
A clean setup comes down to four pieces: a VPS, PostgreSQL, an SMTP relay/provider, and an HTTPS reverse proxy.
Recommended baseline for a small-to-medium list:
- VPS: 2 vCPU, 2–4 GB RAM, NVMe storage
- Disk: 25+ GB (more if you store lots of templates/assets and keep long retention of logs)
- Database: PostgreSQL on the same VPS (fine for most installs)
- OS: Ubuntu 24.04 LTS or Debian 12 on a clean server
Scale-up cues: If you send big campaigns (tens of thousands+) in tight windows, move to 4 vCPU / 8 GB RAM. Also watch Postgres I/O and connection counts.
If you’d rather not manage the OS layer yourself (updates, backups, security hardening), managed VPS hosting is the lowest-friction way to keep a listmonk deployment stable.
Deployment approach (Docker Compose + Postgres)
listmonk runs nicely in containers, so Docker Compose is a practical default. You’ll run two services: postgres and listmonk.
Keep persistent data in volumes. Keep secrets out of the compose file. Expose the app only through your reverse proxy.
Directory layout (example):
/opt/listmonk/(compose + env + scripts)/opt/listmonk/data/(optional bind-mounts)/var/backups/listmonk/(local backup staging)
Example Docker Compose (practical baseline):
mkdir -p /opt/listmonk
cd /opt/listmonk
cat > docker-compose.yml <<'YAML'
services:
db:
image: postgres:16
container_name: listmonk-db
environment:
POSTGRES_DB: listmonk
POSTGRES_USER: listmonk
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U listmonk -d listmonk"]
interval: 10s
timeout: 5s
retries: 10
restart: unless-stopped
app:
image: listmonk/listmonk:latest
container_name: listmonk-app
depends_on:
db:
condition: service_healthy
environment:
LISTMONK_app__address: 0.0.0.0:9000
LISTMONK_db__host: db
LISTMONK_db__port: 5432
LISTMONK_db__user: listmonk
LISTMONK_db__password: ${POSTGRES_PASSWORD}
LISTMONK_db__database: listmonk
LISTMONK_db__ssl_mode: disable
volumes:
- app_data:/listmonk
restart: unless-stopped
volumes:
db_data:
app_data:
YAML
# Create an env file for secrets
cat > .env <<'ENV'
POSTGRES_PASSWORD=replace-with-a-long-random-string
ENV
# Start services
docker compose up -d
This brings the containers up, but you still have two jobs left:
(1) database initialization/migrations and (2) a proper “front door” with HTTPS.
Common pitfall: Don’t publish the listmonk container directly to the public internet with ports:. Put it behind Nginx and expose only 80/443 at the VPS firewall.
Reverse proxy + HTTPS (Nginx + Let’s Encrypt)
TLS isn’t optional here. listmonk handles logins, subscriber imports, and campaign drafts.
Nginx is a good fit because it’s predictable and easy to secure.
DNS first: create an A/AAAA record like news.yourdomain.com pointing to your VPS IP. If you manage domains with HostMyCode, you can keep DNS and hosting together via HostMyCode domains.
Nginx site config (example):
sudo nano /etc/nginx/sites-available/listmonk.conf
server {
listen 80;
server_name news.example.com;
location /.well-known/acme-challenge/ {
root /var/www/html;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl http2;
server_name news.example.com;
ssl_certificate /etc/letsencrypt/live/news.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/news.example.com/privkey.pem;
# Basic hardening
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
client_max_body_size 50m;
location / {
proxy_pass http://127.0.0.1:9000;
proxy_http_version 1.1;
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;
}
}
Make the upstream reachable: since our compose file didn’t publish ports, you can either (a) add an internal Docker network + Nginx in Docker, or (b) publish listmonk to localhost only.
A simple pattern is binding listmonk to 127.0.0.1:
# In docker-compose.yml under app:
# ports:
# - "127.0.0.1:9000:9000"
For Let’s Encrypt, Certbot is still the straightforward choice. If you want a full, error-focused TLS walkthrough, follow HostMyCode’s TLS setup guide for 2026.
Quick firewall check: allow only SSH + HTTP + HTTPS. On Ubuntu with UFW:
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
If you want the reasoning behind those rules (and the usual foot-guns), see this UFW web server guide.
SMTP: choosing a sending path that won’t ruin deliverability
listmonk doesn’t try to be an MTA, and that’s a feature. Send through a reputable SMTP relay/provider unless you have a strong reason to own the full mail pipeline.
Typical options:
- SMTP provider: simplest operationally. You authenticate to SMTP, they handle queueing, bounce processing, reputation tooling, and scaling.
- Your own SMTP relay on a VPS: workable, but you inherit reputation management, rate limits, and bounce handling complexity.
If you send from your own infrastructure, nail the basics (SPF/DKIM/DMARC) and set realistic rate limits. HostMyCode’s SMTP relay guide is worth reading even if you end up using a provider.
listmonk configuration note: You set SMTP in the listmonk admin UI after the initial setup. Store credentials in a password manager.
When you can, lock down admin access (IP allowlist or VPN).
Backups you can actually restore (Postgres + config + uploads)
Backups are simple here, which is exactly what you want. Protect the small set of things that matter.
- PostgreSQL database (subscribers, lists, campaigns, templates, settings)
- listmonk app volume (uploads and local state, depending on how you run it)
- Nginx config + TLS renewal state (optional, but it speeds rebuilds)
Postgres dump (daily) example:
sudo mkdir -p /var/backups/listmonk
sudo chmod 700 /var/backups/listmonk
cat > /opt/listmonk/backup-db.sh <<'SH'
#!/usr/bin/env bash
set -euo pipefail
TS=$(date +%F_%H%M)
OUT="/var/backups/listmonk/listmonk_${TS}.sql.gz"
docker exec -t listmonk-db pg_dump -U listmonk -d listmonk | gzip > "$OUT"
# Keep 14 days locally
find /var/backups/listmonk -type f -name 'listmonk_*.sql.gz' -mtime +14 -delete
SH
chmod +x /opt/listmonk/backup-db.sh
Run it via cron or a systemd timer. If you prefer timers (better logging, fewer silent failures), HostMyCode has a clear systemd timer guide.
Offsite matters: keep at least one encrypted copy off the VPS. Object storage, another VPS, or a backup service all work.
What matters is testing a restore at least monthly.
Restore drill (quick sanity check):
# Copy a dump to the server, then:
zcat /var/backups/listmonk/listmonk_YYYY-MM-DD_HHMM.sql.gz | \
docker exec -i listmonk-db psql -U listmonk -d listmonk
Do this on a staging VPS if possible. Restores are where “we have backups” becomes either true or fantasy.
How to update listmonk safely (without surprising downtime)
Container updates are quick to do and easy to botch. Follow a boring routine and you’ll sleep better.
- Take a fresh database dump (even if you do daily backups).
- Pull new images and restart in a controlled window.
- Watch logs for migrations or startup errors.
cd /opt/listmonk
# 1) Backup
sudo /opt/listmonk/backup-db.sh
# 2) Pull + restart
docker compose pull
docker compose up -d
# 3) Verify
docker logs --tail=100 listmonk-app
docker logs --tail=100 listmonk-db
Practical tip: On multi-app VPS hosts, disk fills quietly from old image layers. Keep an eye on /var/lib/docker growth.
Prune only after you confirm what’s unused. If you’ve ever hit 100% disk mid-deploy, HostMyCode’s disk space troubleshooting runbook is worth saving.
Operational guardrails: monitoring, access, and sending hygiene
Newsletter systems stay quiet right up until send time. Then SMTP defers pile up, queues grow, and you end up troubleshooting under a deadline.
- Monitor the VPS: CPU spikes and disk I/O show up during imports and large sends. Netdata is a good “see it now” option; read how to run Netdata on a VPS.
- Add uptime checks: you want to know the login page is down before your team does. If you already run Uptime Kuma, the pattern is similar; see this Uptime Kuma VPS setup.
- Restrict admin access: at minimum, use strong passwords and 2FA if available. Better: keep admin behind a VPN (WireGuard) or an IP allowlist in Nginx.
- Respect unsubscribe and list hygiene: keep bounce handling and suppression clean. Your sending reputation doesn’t tolerate sloppy lists.
Picking the right infrastructure for listmonk
You can run listmonk on almost anything that can keep Docker and Postgres happy. In practice, stability comes from consistent CPU, decent disk I/O, and a reliable network path to your SMTP provider.
- Shared hosting: usually not a fit. You want Docker, background jobs, and control over Nginx/TLS.
- VPS: the sweet spot for most teams. Predictable resources, full control, easy upgrades.
- Dedicated server: only necessary if listmonk is part of a larger high-volume stack (multiple apps, databases, and internal tooling) or you want single-tenant hardware.
If you’re starting from zero, a HostMyCode VPS gives you the control you need without turning this into a platform project.
If you’d rather offload patching and day-2 operations, go with managed VPS hosting.
If you want to self-host listmonk without gambling on noisy neighbors or slow storage, run it on a VPS sized for always-on workloads. Start with a HostMyCode VPS, or choose managed VPS hosting if you want updates, monitoring, and baseline hardening handled for you.
FAQ: self-host listmonk on a VPS
Do I need my own mail server to use listmonk?
No. listmonk can send through any SMTP relay/provider. For most site owners, a reputable SMTP provider is the simplest route.
Can I run listmonk and my website on the same VPS?
Yes, as long as you size the VPS for combined load and keep services isolated (separate containers, sensible firewall rules, and distinct Nginx server blocks). If you share a database server, watch memory usage.
What’s the minimum VPS size that won’t feel slow?
For a small list and occasional campaigns, 2 vCPU and 2 GB RAM is workable. If you import large CSVs or send frequently, 4 GB RAM reduces database pressure and keeps the UI snappy.
How do I avoid breaking changes during updates?
Back up the database right before updating. Update in a maintenance window. Verify logs after restart.
If something fails, you want a fast rollback path (restore the dump and re-run the old image tag).
What are the first deliverability tasks after setup?
Set SPF/DKIM/DMARC for your sending domain, use a consistent From address, and warm up sending volume instead of blasting a cold list at full speed.
Summary
listmonk is a solid pick if you want a fast newsletter tool, direct control over subscriber data, and an escape from per-subscriber SaaS pricing. Put it on a VPS, back up Postgres, terminate TLS at Nginx, and treat SMTP deliverability as a core operational concern.
If you want a clean home for your newsletter stack, start with a HostMyCode VPS. If you’d rather focus on campaigns than patch cycles and alerts, managed VPS hosting is the more comfortable long-term setup.