Listmonk - Hardened Self-Hosted Newsletter and Mailing List
par Lynxroute
Listmonk 6.1.0 - CIS Level 1 hardened newsletter manager on Ubuntu 24.04 with SBOM and CIS
What is Listmonk
Listmonk is a high-performance, self-hosted newsletter and mailing list manager written in Go. It ships as a single static binary backed by PostgreSQL and covers the full email-marketing lifecycle: subscriber and list management with single or double opt-in, segmented campaigns, transactional email via a REST API, HTML and plain-text templates, scheduled and continuous sending, bounce processing, click and view tracking, public archive pages, multi-language i18n, custom subscriber attributes, and a built-in admin UI. It connects to any SMTP relay, so you keep full control over delivery and sender reputation while running every campaign on infrastructure you own.
Why self-host Listmonk
Self-hosting puts your subscriber list, campaign content, and engagement data entirely under your control - no per-subscriber SaaS pricing, no third-party visibility into who you email, and no risk of an account suspension cutting off communication with your audience. Ideal for organisations with data residency requirements (GDPR, HIPAA, ISO 27001), regulated industries, defence and government work, and operators who need predictable infrastructure costs instead of usage-based marketing platform pricing.
What this VM image adds
Security hardening:
- Unique super-admin password generated per instance - admin account created at first boot with a per-VM password (Azure vmId), written to /root/listmonk-credentials.txt, password change required on first login
- PostgreSQL 16 listening on localhost only - 127.0.0.1:5432, per-instance database password generated at first boot, no external exposure
- Listmonk admin UI bound to 127.0.0.1:9000 - reachable only through the Nginx reverse proxy with TLS, never directly exposed
- Nginx reverse proxy with TLS - HTTP to HTTPS redirect, hardened cipher suite, security headers (X-Frame-Options, X-Content-Type-Options, Referrer-Policy)
- Listmonk runs as non-root - dedicated listmonk system user, systemd UMask=0027, ProtectSystem=full, NoNewPrivileges
- CVE scan - every image is scanned for vulnerabilities with Trivy before release
- UFW firewall - only ports 22 (SSH), 80, and 443 open
- fail2ban - SSH brute-force protection
- AppArmor - mandatory access control
- Certbot pre-installed - one command issues a Let's Encrypt certificate after you point a domain at the VM
OS hardening (CIS Level 1):
- CIS Level 1 hardened - CIS Ubuntu 24.04 LTS Level 1 Benchmark via ansible-lockdown
- auditd - system call auditing for critical paths
- SSH hardening - PasswordAuthentication disabled, key-only access
- Kernel hardening - SYN cookies, ASLR, rp_filter, TCP BBR
- /tmp as tmpfs - nosuid, nodev, noexec
- Azure IMDS endpoints - egress rules pre-configured (169.254.169.254, 168.63.129.16)
Compliance artifacts (inside the VM):
- SBOM - CycloneDX 1.6 at /etc/lynxroute/sbom.json
- CIS Conformance Report - OpenSCAP HTML at /etc/lynxroute/cis-report.html
- Tailored CIS profile - /usr/share/doc/lynxroute/CIS_TAILORED_PROFILE.md
- Server credentials file - /root/listmonk-credentials.txt with public IP, web UI URL, the per-instance super-admin password, and the per-instance PostgreSQL password
Quick Start
- Deploy VM from Azure Marketplace (Standard_D2s_v3 or larger recommended)
- Open NSG: TCP 80 and 443 from your client networks, TCP 22 from your management IPs only
- SSH: ssh -i key.pem <username>@<PUBLIC_IP> (username set during VM creation, default: azureuser)
- Read connection details: sudo cat /root/listmonk-credentials.txt - contains web UI URL, super-admin password, and the PostgreSQL password
- Open https://<PUBLIC_IP> in your browser, accept the self-signed certificate, log in as admin, and set a new password
- Configure your outgoing SMTP relay in Settings -> SMTP - no SMTP credentials are baked into the image
- Issue a public TLS certificate (recommended before sharing with users): sudo certbot --nginx -d your.domain.com
The image ships with a self-signed certificate so the web UI works on first boot - replace it with a CA-signed certificate before exposing the server to end users.