Self-hosted newsletter platform using Listmonk on Kubernetes (K3s).
- A server with at least 2 vCPUs and 8GB RAM (Hetzner CCX13 recommended)
- Ubuntu 24.04
- A domain name
- AWS account (for Parameter Store - free tier)
- Hetzner Cloud account (for Object Storage backups)
- Server: €12/month (Hetzner CCX13)
- Backups: ~€0-5/month (Hetzner Object Storage)
- Email sending: ~$0.4 per 1,000 emails (Maileroo or similar)
ssh root@<your-server-ip>
apt update && apt upgrade -y
apt install -y curl wget git ufw
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw allow 6443/tcp
ufw enablecurl -s https://get.k3s.io | \
INSTALL_K3S_CHANNEL=stable \
INSTALL_K3S_EXEC="--secrets-encryption" \
sh -
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
echo "export KUBECONFIG=/etc/rancher/k3s/k3s.yaml" >> ~/.bashrc
kubectl get nodeskubectl apply --server-side -f \
https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.27/releases/cnpg-1.27.1.yaml
kubectl rollout status deployment \
-n cnpg-system cnpg-controller-managerkubectl apply -f external-secrets/kubectl create secret generic aws-credentials \
--from-literal=access-key-id=YOUR_ACCESS_KEY \
--from-literal=secret-access-key=YOUR_SECRET_KEY \
-n external-secretsaws ssm put-parameter --name /listmonk/db/username --value "listmonk" --type SecureString --overwrite
aws ssm put-parameter --name /listmonk/db/password --value "CHANGE_ME_STRONG_PASSWORD" --type SecureString --overwrite
aws ssm put-parameter --name /listmonk/db/superuser/username --value "postgres" --type SecureString --overwrite
aws ssm put-parameter --name /listmonk/db/superuser/password --value "CHANGE_ME_SUPERUSER_PASSWORD" --type SecureString --overwritekubectl create namespace listmonk
kubectl apply -f postgres/
kubectl wait --for=condition=Ready cluster/pg-listmonk -n listmonk --timeout=5mkubectl apply -f cert-manager/
# Update email in cert-manager/cluster-issuer.yml before applying
kubectl apply -f cert-manager/cluster-issuer.ymlUpdate listmonk/config.toml with your domain, then:
kubectl apply -k listmonk/
kubectl wait --for=condition=Ready pod -l app=listmonk -n listmonk --timeout=5m# Create Hetzner Object Storage credentials
kubectl create secret generic hetzner-blob-storage \
--from-literal=ACCESS_KEY_ID=your-access-key \
--from-literal=ACCESS_SECRET_KEY=your-secret-key \
-n listmonk
# Apply backup configuration
kubectl apply -f postgres/objectstore.yml
kubectl apply -f postgres/scheduledbackup.ymlAdd an A record:
newsletter.yourdomain.com→<your-server-ip>
Wait 5-10 minutes for DNS propagation and SSL certificate issuance.
Navigate to https://newsletter.yourdomain.com
Default credentials are from your AWS Parameter Store values.
Edit listmonk/config.toml and listmonk/ingress.yml with your domain.
Configure in Listmonk UI under Settings → SMTP. Recommended providers:
- Maileroo
- AWS SES
- SendGrid
- Mailgun
apt update && apt upgrade -y
systemctl restart k3skubectl get backup -n listmonkkubectl logs -l app=listmonk -n listmonk -fEdit postgres/cluster.yml and listmonk/deployment.yml to adjust resource limits.
kubectl describe cluster pg-listmonk -n listmonk
kubectl logs -n cnpg-system -l app.kubernetes.io/name=cloudnative-pgkubectl get ingress -n listmonk
kubectl describe ingress listmonk -n listmonk
kubectl get certificate -n listmonkkubectl describe backup -n listmonk
kubectl logs -n listmonk -l postgresql=pg-listmonkApache License 2.0
Based on the guide: Own Your Newsletter with Listmonk