Instala Listmonk en un VPS con Ubuntu 24.04
Instala Listmonk v6.2.0 en Ubuntu 24.04 con PostgreSQL, systemd, TLS y SMTP; conoce la configuración y por qué la reputación tarda semanas.
Qué necesita un boletín autohospedado en Listmonk
Listmonk es un administrador autohospedado de boletines y listas de correo: un binario de Go, una base de datos PostgreSQL, un archivo de configuración y una unidad de systemd. Un VPS pequeño lo ejecuta sin problemas, porque Listmonk almacena los suscriptores y pone en cola las campañas, pero nunca entrega el correo por sí mismo. Entrega cada mensaje a un servidor SMTP (protocolo simple de transferencia de correo), por lo que la tasa de entrega depende de la reputación de ese servidor y no de este software.
Esta guía instala Listmonk v6.2.0, la versión actual en julio de 2026, en Ubuntu 24.04. Necesita un VPS con una dirección IP pública, un nombre de dominio que controle y PostgreSQL 12 o posterior. La instalación tarda aproximadamente una hora. La reputación de envío tarda semanas en establecerse; esa parte se explica cerca del final.
Instalar PostgreSQL y crear la base de datos
Ubuntu 24.04 incluye PostgreSQL 16 en su propio repositorio, una versión muy posterior a la que necesita Listmonk.
sudo apt update
sudo apt install -y postgresql curl
sudo systemctl enable --now postgresqlCrea el rol y la base de datos en una sola sesión de psql. -v ON_ERROR_STOP=1 hace que psql salga ante la primera instrucción fallida, por lo que un error tipográfico no dejará una configuración incompleta que parezca terminada.
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 es necesario. La instalación del esquema crea tablas, tipos, índices y funciones, por lo que el rol debe ser propietario de la base de datos. Si configuras Listmonk para usar una base de datos cuyo propietario es otro rol, la instalación se detiene con permission denied, incluso después de ejecutar GRANT CONNECT.
Confirma que la base de datos existe antes de continuar.
sudo -u postgres psql -tAc "SELECT datname FROM pg_database WHERE datname='listmonk';"Esto muestra listmonk. Una línea vacía significa que la instrucción CREATE nunca se ejecutó, así que vuelve a revisar la salida de psql.
Instalar el binario de Listmonk
Listmonk publica un binario estático para cada arquitectura. Compruebe primero cuál utiliza su sistema, porque un binario amd64 en un VPS ARM es un archivo que el kernel se niega a ejecutar.
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 --versionEn un VPS ARM, sustituya amd64 por arm64 en el nombre del archivo. listmonk --version mostrar una cadena de versión es la primera prueba de que el binario coincide con la máquina.
Genere config.toml y restrinja sus permisos
--new-config escribe config.toml en el directorio de trabajo actual. Por eso cd se encuentra dentro de sh -c y no antes de sudo.
sudo install -d -m 750 /etc/listmonk
sudo sh -c 'cd /etc/listmonk && listmonk --new-config'El archivo generado es corto. En [app], address = "localhost:9000" vincula el servidor HTTP únicamente a la interfaz de loopback, por lo que el panel de administración no es accesible desde Internet hasta que coloque un proxy inverso delante. No modifique esa línea. En [db] encontrará host = "localhost", port = 5432, user = "listmonk", database = "listmonk" y ssl_mode = "disable". Esos valores predeterminados ya coinciden con la base de datos que creó, por lo que solo debe cambiar la línea de la contraseña.
ssl_mode = "disable" es correcto mientras Postgres escuche en loopback en el mismo equipo, porque ese tráfico nunca sale de la máquina. Si mueve la base de datos a otro host, configúrelo como require. De lo contrario, la contraseña atravesará la red sin cifrar.
Edite la línea de la contraseña en [db] para que coincida con el rol. Después, cree la cuenta de servicio y quite el acceso al archivo a todos los demás usuarios.
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.tomlAhora la cuenta de servicio puede leer el archivo y nadie más puede hacerlo.
sudo -u listmonk cat /etc/listmonk/config.toml > /dev/null && echo readable
stat -c '%U:%G %a' /etc/listmonk/config.tomlEl primer comando muestra readable. El segundo muestra root:listmonk 640. Cualquier otra cuenta sin privilegios que intente el mismo cat obtiene Permission denied. Ese es el objetivo: este archivo contiene la contraseña de la base de datos sin cifrar y, normalmente, un servidor tiene más de un usuario. El mismo principio se aplica a todos los servicios que ejecute. Lea una vez sobre usuarios de servicio con privilegios mínimos y aplíquelo en todos los casos.
Cree el esquema con --install
--install crea las tablas y carga la configuración predeterminada. Establezca el primer inicio de sesión de administrador con variables de entorno para que la cuenta exista antes de que el panel sea accesible.
sudo -u listmonk env LISTMONK_ADMIN_USER=admin \
LISTMONK_ADMIN_PASSWORD='another-long-random-password' \
listmonk --config /etc/listmonk/config.toml --install --yes--yes responde a la solicitud de confirmación. Lea esa solicitud una vez antes de automatizarla, porque --install es el instalador inicial y elimina un esquema de Listmonk existente. Ejecutarlo por segunda vez en una base de datos activa destruye los suscriptores. En cualquier script que pueda ejecutarse dos veces, use --install --idempotent --yes, que no hace nada cuando las tablas ya existen. Los cambios de esquema incluidos en una nueva versión se aplican con --upgrade, nunca con --install.
Compruebe el resultado desde la base de datos, no desde el navegador.
sudo -u postgres psql -d listmonk -c '\dt'
sudo -u postgres psql -d listmonk -tAc "SELECT username FROM users;"El primer comando muestra las tablas de Listmonk, entre ellas subscribers, lists, campaigns, templates y bounces. El segundo muestra admin. Un resultado vacío del segundo comando significa que las variables de entorno nunca llegaron al proceso, por lo que el panel le pedirá crear el primer usuario en el navegador.
Ejecutar Listmonk con systemd
Escriba /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 es importante porque Listmonk resuelve las rutas relativas, incluida una ruta del sistema de archivos para las cargas de medios, respecto a ella. After=postgresql.service solo ordena el inicio; no espera a que Postgres acepte conexiones. Por eso, Restart=on-failure cubre el caso en que Listmonk se inicia demasiado pronto y no puede conectarse.
sudo systemctl daemon-reload
sudo systemctl enable --now listmonk
ss -ltnp | grep 9000
curl -sI http://127.0.0.1:9000/ss debería mostrar 127.0.0.1:9000 en estado LISTEN. Si curl devuelve cualquier línea de estado HTTP, significa que el servidor responde. Si curl falla con Connection refused, significa que el proceso terminó durante el inicio; journalctl -u listmonk -n 50 --no-pager indicará el motivo. Tenga en cuenta que enable --now es la parte que sobrevive a un reinicio: un proceso iniciado manualmente desaparece después de la siguiente actualización del kernel.
Poner nginx y TLS delante
Listmonk usa HTTP sin cifrado en loopback, por lo que nginx termina TLS (seguridad de la capa de transporte) y reenvía la solicitud.
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;
}
}Es necesario aumentar client_max_body_size porque las importaciones de suscriptores y las cargas de archivos multimedia usan solicitudes de archivos, y nginx rechaza de forma predeterminada todo lo que supera 1 MB con 413 Request Entity Too Large. Emite el certificado con certbot. certbot también escribe las directivas listen 443 ssl y configura la redirección desde el puerto 80. Consulta los pasos en la guía de certificados de Let's Encrypt para nginx. Abre los puertos 80 y 443, y deja cerrado el puerto 9000, porque el proxy accede a él mediante loopback. Si el firewall todavía no está configurado, empieza por los conceptos básicos del firewall ufw.
Después, abre el panel de administración y establece la URL raíz en Settings como https://lists.example.com. Una instalación nueva conserva http://localhost:9000, y Listmonk escribe ese valor en cada enlace para cancelar la suscripción y en cada URL de archivos multimedia que incluye en un correo electrónico. Si envías una campaña antes de cambiarlo, cada destinatario recibirá enlaces que apuntan a su propia máquina. Los enlaces no funcionarán para el destinatario y, para un filtro antispam, parecerán indicar que el remitente no puede configurar su propio dominio.
Conectar SMTP, que no está en config.toml
Busque una sección SMTP en config.toml y no encontrará ninguna. La configuración del correo se almacena en la base de datos, en la tabla settings, y se edita en el panel de administración, en Settings y SMTP. Por eso el archivo generado es tan corto. También por eso un cambio de SMTP no requiere reiniciar el servicio.
Para el servidor SMTP existen dos opciones válidas. Puede ejecutar uno propio. En ese caso, la reputación depende completamente de usted y el proyecto requiere trabajo específico: ejecutar su propio servidor de correo con Mailcow explica lo que implica. También puede configurar Listmonk para usar un relay transaccional y dejar la reputación de la IP en manos de otro proveedor.
En cualquier caso, use el puerto 587 con STARTTLS o el puerto 465 con TLS implícito. No planifique el envío mediante el puerto 25. La mayoría de los proveedores de VPS lo bloquean de forma predeterminada en las cuentas nuevas. Además, un puerto 25 bloqueado se comporta exactamente como una conexión bloqueada, porque los paquetes se descartan en lugar de rechazarse. Por eso el cliente espera a que se agote el tiempo de espera en lugar de fallar de inmediato.
Pruébelo antes de confiar en él. Cree una lista, añada su propia dirección como suscriptor y envíe una campaña a un solo destinatario. Abra el mensaje recibido y lea todos los encabezados. El encabezado Authentication-Results, añadido por el servidor receptor, indica si SPF y DKIM se validaron correctamente.
La entregabilidad es todo el trabajo
Listmonk genera el mensaje, administra la lista y entrega el correo al servidor de destino. El proveedor receptor decide si el correo llega a la bandeja de entrada. Para ello, usa la dirección IP de envío y el dominio de envío. Una IP nueva de VPS no tiene historial. Todos los grandes proveedores de buzones consideran esa falta de historial un indicio leve de actividad sospechosa.
Hay cuatro elementos obligatorios:
- Un registro TXT de SPF (sender policy framework) que indique el host autorizado para enviar correo en nombre de tu dominio.
- Una clave DKIM (domainkeys identified mail) publicada como registro TXT. El servidor de correo debe firmar los mensajes, no Listmonk.
- Un registro DMARC (domain based message authentication, reporting and conformance), que indique a los receptores qué hacer cuando fallan los dos mecanismos anteriores.
- Un buzón para mensajes rebotados que Listmonk lea. Así, las direcciones que rechazan el correo salen de la lista en lugar de recibir reintentos indefinidamente.
Después, envía el correo lentamente al principio. Un dominio que nunca ha enviado correo y de repente entrega diez mil mensajes en una hora tiene exactamente el comportamiento de una cuenta vulnerada, por lo que los filtros lo tratan como tal. Empieza con los suscriptores más activos y aumenta el volumen durante varios días.
Todas las plantillas también necesitan un enlace de cancelación de suscripción operativo. En una plantilla de Listmonk, ese enlace es {{ UnsubscribeURL }}. El cuerpo de la campaña se inserta donde aparece {{ template "content" . }}, que debe aparecer exactamente una vez en cada plantilla. Una campaña sin enlace de cancelación de suscripción genera quejas por spam en lugar de cancelaciones, y las quejas son la forma más rápida de perder una reputación de envío que has tardado semanas en crear.
Copias de seguridad y lo que necesita una restauración
Deben salir del servidor dos elementos: el volcado de la base de datos y config.toml. Añada el directorio de medios si carga imágenes en las campañas.
sudo -u postgres pg_dump -Fc listmonk > listmonk-$(date +%F).dumpEse volcado contiene los suscriptores, las campañas, las plantillas y toda la configuración, incluidas las credenciales SMTP. Por tanto, cífrelo y guárdelo fuera de este servidor. La programación de esta tarea ya está resuelta: consulte copias de seguridad cifradas con restic en almacenamiento remoto. config.toml ocupa pocas líneas, pero contiene la contraseña de la base de datos. Trátelo del mismo modo.
Las actualizaciones siguen un orden concreto. Detenga el servicio, cree un volcado, sustituya el binario en /usr/bin, ejecute listmonk --config /etc/listmonk/config.toml --upgrade y, después, inicie el servicio. Las migraciones del esquema solo se ejecutan hacia delante. Por tanto, ese volcado es la única forma de volver atrás.
¿Por qué Listmonk no se inicia?
Lea primero el journal con journalctl -u listmonk -n 50 --no-pager. Casi todos los errores de inicio aparecen en una línea del bloque [db].
pq: password authentication failed for user "listmonk" significa que la contraseña de [db] no coincide con el rol de Postgres. El prefijo pq indica que el controlador de Postgres informa del rechazo del servidor. Esto confirma que la configuración se leyó correctamente y que las credenciales eran incorrectas. Restablezca el rol con sudo -u postgres psql -c "ALTER USER listmonk WITH PASSWORD 'new-password';" y coloque la misma cadena en el archivo.
pq: database "listmonk" does not exist significa que el valor database de [db] no identifica una base de datos real. sudo -u postgres psql -l muestra las bases de datos existentes en el servidor, incluida la escritura que usó por error.
permission denied durante --install significa que el rol puede conectarse, pero no es propietario de la base de datos y, por tanto, no puede crear tablas en ella. Corríjalo con sudo -u postgres psql -c "ALTER DATABASE listmonk OWNER TO listmonk;" y vuelva a ejecutar la instalación.
El servicio nunca se inicia y el journal indica el archivo de configuración. Un proceso que se ejecuta como listmonk no puede abrir un config.toml que tenga como propietario a root:root y permisos 600. stat -c '%U:%G %a' /etc/listmonk/config.toml debería mostrar root:listmonk 640, y el directorio superior debería ser root:listmonk 750.
El panel funciona, pero no llega ningún correo. Esto no es un problema de inicio. Compruebe primero Settings y SMTP. Después, revise el registro de la campaña en el panel de administración. Este registro contiene el error que devolvió el servidor de correo en cada intento.
FAQ
¿Necesito mi propio servidor de correo para usar Listmonk?
No. Listmonk no es un servidor de correo. Necesita las credenciales SMTP de un servidor que acepte sus mensajes y los entregue. Puede ser un relay transaccional o un servidor de correo que administre usted mismo. Configure esas credenciales en Settings y SMTP del panel de administración, no en config.toml, porque la configuración de correo se almacena en la base de datos. Use el puerto 587 con STARTTLS o el puerto 465 con TLS implícito, porque la mayoría de los proveedores de VPS bloquean el puerto saliente 25 en las cuentas nuevas.
¿Por qué los enlaces de mi campaña apuntan a localhost:9000?
La configuración de la URL raíz todavía tiene el valor predeterminado de la instalación: http://localhost:9000. Listmonk escribe ese valor en los enlaces de cancelación de suscripción y en las URL de medios cuando envía una campaña. Abra Settings en el panel de administración, establezca la URL raíz con su dirección HTTPS real y guarde los cambios. Los mensajes que ya se entregaron no se pueden corregir. Envíese una campaña de prueba y haga clic en el enlace de cancelación de suscripción antes de enviar mensajes a una lista real.
¿La ejecución de --install otra vez eliminará mis suscriptores?
Sí. --install es el instalador inicial y elimina el esquema existente de Listmonk. --yes elimina la confirmación que habría mostrado una advertencia. En cualquier script que pueda ejecutarse dos veces, use --install --idempotent --yes, que no hace nada si las tablas ya existen. Para aplicar los cambios de esquema de una nueva versión, detenga el servicio, cree un pg_dump y luego ejecute --upgrade.
¿Por qué Listmonk indica que falló la autenticación de contraseña para el usuario listmonk?
La contraseña del bloque [db] de /etc/listmonk/config.toml no coincide con la del rol de Postgres con el mismo nombre. La línea del journal es pq: password authentication failed for user "listmonk". pq es el controlador de Postgres que retransmite el rechazo del servidor, lo que significa que se encontró y leyó el archivo de configuración. Restablezca la contraseña del rol con sudo -u postgres psql -c "ALTER USER listmonk WITH PASSWORD 'new-password';", escriba la misma cadena en el archivo de configuración y luego ejecute sudo systemctl restart listmonk.