Listmonk
Listmonk manages subscribers, sends campaigns, handles double opt-in and tracks opens and clicks. A single Go binary and PostgreSQL, with two one-shot services that prepare the schema and the mail settings before it starts.
Last run on a host 8 Nov 2026 · data restored from a backup
Pinned at v6.2.0 (released 26 Jun 2026) · the newest upstream release
In the upstream images: 2 CRITICAL · 53 HIGH with a published fix — per image
To try it: Docker, nothing else. See Try it locally.
To deploy it behind the proxy: Traefik running, a domain, and an SMTP relay you enter after installation.
Try it locally
Section titled “Try it locally”On your own machine, with no proxy, domain or certificate. From business/listmonk/:
cp .env.local.example .env.local
# The secrets ship empty; generate the ones this stack needsecho "DB_PASSWORD=$(openssl rand -hex 32)" >> .env.local
docker compose -f docker-compose.local.yml --env-file .env.local up -dThen open http://localhost:9000. Stop it again with the same command and down.
Installation
Section titled “Installation”From business/listmonk/:
-
Copy the environment file and set
APP_TRAEFIK_HOSTandTZ:Terminal window cp .env.example .env -
Generate the database password and write it into
.envas well. Listmonk has no_FILEform for its database password, so the value exists twice — once as the secret file, once inline:Terminal window mkdir -p .secrets volumes/postgres volumes/uploadssudo chown 1000:1000 volumes/uploads # listmonk runs as 1000:1000openssl rand -base64 32 | tr -d '\n' > .secrets/db_pwd.txtsed -i "s|^DB_PWD_INLINE=.*|DB_PWD_INLINE=$(cat .secrets/db_pwd.txt)|" .env -
Start it.
listmonk-installcreates the schema on the first boot and migrates it later;smtp-guardreplaces upstream’s SMTP placeholdersmtp.yoursite.com— a real domain — withsmtp.invalid, so nothing is sent before you set a relay. Both exit 0, thenlistmonk-appstarts:Terminal window docker compose up -ddocker compose ps -adocker compose logs smtp-guard -
Over the VPN, open
https://<APP_TRAEFIK_HOST>/admin. The first visit creates the super admin account, with no setup secret — the reason the route ships closed (access-vpn). -
In the interface, go to Settings → SMTP and enter the relay credentials. They are stored encrypted in the database rather than in a file. Until a relay is set, the subscription form answers
500and no mail leaves the host. -
Open the subscriber paths to the internet — see Admin behind the VPN.
Verify
Section titled “Verify”docker compose ps -a # db and listmonk-app healthy, the two one-shots exited 0Then send a campaign to a list containing only your own address, and confirm it arrives. Sending is the whole purpose of the stack and the part a running container says nothing about.
Going further
Section titled “Going further”Admin behind the VPN, subscribers on the internet
Section titled “Admin behind the VPN, subscribers on the internet”The stack ships access-vpn with limit-standard and headers-standard.
Subscriber endpoints — /subscription/*, /link/*, /campaign/* — have to
work for people who are not on your VPN. APP_TRAEFIK_ACCESS=access-public opens
the whole host, /admin included.
The better shape splits the two: a second Traefik router with a higher priority, scoped to
/admin and carrying access-vpn@file in its access position, the other three
positions as on the main router; the existing router keeps the subscriber paths.
The label block is in
business/listmonk/README.md.
Why frame-self is on the router
Section titled “Why frame-self is on the router”The campaign and template preview renders inside an iframe from the same origin.
headers-standard sends X-Frame-Options: DENY, which blocks it; the router
therefore carries frame-self@file before ${APP_TRAEFIK_HEADERS}@file, which
sends SAMEORIGIN instead.
Bounce handling
Section titled “Bounce handling”Bounces need either IMAP access to the sending mailbox or a relay that posts bounce webhooks. Without one of the two, addresses that no longer exist stay on the list and quietly damage the sending reputation.
What it sends outward
Section titled “What it sends outward”Listmonk requests update.listmonk.app to see whether a newer version exists.
It is a plain GET with no payload and no installation identifier — the vendor
learns the source address and the timing.
On by default. Turn it off in Settings → General → Check for updates; the setting lives in the database.
Backup
Section titled “Backup”| Database | PostgreSQL, container listmonk-db, database listmonk, user listmonk |
| Password | .secrets/db_pwd.txt |
| State | volumes/postgres · volumes/uploads (media used in campaigns) |
| Quiescing | Not needed — the dump is consistent on its own. |
For borgmatic:
postgresql_databases: - name: listmonk username: listmonk # The server's own client, inside its container pg_dump_command: docker exec listmonk-db pg_dump pg_restore_command: docker exec -i listmonk-db pg_restore psql_command: docker exec -i listmonk-db psqlSubscriptions and their opt-in state are rows, so an older snapshot brings back addresses that unsubscribed after it was taken, and the next campaign mails them again. Compare the subscriber table against the unsubscribe log before the first send following a restore.
Restore order: database, then the application.
Updates
Section titled “Updates”-
Read the release notes.
-
Back up the database and
volumes/uploadstogether. -
Raise
APP_TAGin.env, then:Terminal window docker compose pull && docker compose up -ddocker compose logs listmonk-app --follow -
Open a campaign and a template. Schema changes surface in the editor rather than at start.
Troubleshooting
Section titled “Troubleshooting”The interface asks for administrator credentials that are not in .env.
Listmonk v6 removed them from configuration. The wizard on first visit is where
the account is created; the older ADMIN_USER and ADMIN_PASSWORD variables are
ignored.
A banner asks you to remove admin_username from the configuration. Those
fields are already absent here. The banner can be dismissed.
The campaign preview is blank. The iframe is being blocked. Confirm
frame-self@file stands before ${APP_TRAEFIK_HEADERS}@file on the router.
Repository files
Section titled “Repository files”business/listmonk/README.md— setup, the access split, known issuesbusiness/listmonk/UPSTREAM.md— source and upgrade checklist
Official sources
Section titled “Official sources”© 2026 SecDockBlueCode Apache-2.0Site content CC BY-NC 4.0