
Un servidor de correo sin TLS entrega dos cosas gratis a quien esté en medio: el contenido de los mensajes y la contraseña de cada usuario, que viaja cada vez que el móvil consulta el buzón. Y no es solo privacidad: desde febrero de 2024 Gmail exige conexión TLS al todo remitente. A continuación, SSL/TLS completo en un servidor Postfix + Dovecot: certificado Let's Encrypt, los puertos correctos, versiones mínimas, autenticación solo con cifrado y la protección contra el ataque que elimina STARTTLS. Todo probado en laboratorio en Ubuntu 24.04 y en 26.04, que incluyen Dovecots de versiones distintas, con sintaxis distintas.
Por qué el servidor de correo necesita TLS
- Contraseñas. IMAP, POP3 y SMTP autenticado envían usuario y contraseña al inicio de cada sesión. Sin TLS, cualquiera en el Wi-Fi del café, en el proveedor o en un router comprometido lee en texto plano. Un único inicio de sesión capturado da acceso a toda la bandeja — y al restablecimiento de contraseña de todo lo que esté vinculado a ese correo.
- Contenido. Contrato, recibo, factura, informe médico: el mensaje completo pasa legible entre el cliente y el servidor y entre servidores.
- Entregabilidad. Las reglas de Gmail para cualquier remitente incluyen “Use a TLS connection for transmitting email”, junto a SPF/DKIM y DNS inverso — el mismo paquete del artículo sobre DKIM y DMARC en Postfix.
- Un protocolo antiguo es un protocolo roto. A RFC 8996 (marzo de 2021) dice que TLS 1.0 y 1.1 “MUST NOT be used”. El mínimo hoy es TLS 1.2, y el TLS 1.3 es el que negocian los clientes modernos.
Los puertos y el tipo de TLS de cada uno

- 25 (SMTP) — servidor hablando con servidor. Usa STARTTLS oportunista (RFC 3207): la conversación empieza en texto plano y sube a TLS si ambos lados lo ofrecen. No se puede exigir TLS aquí a todo el mundo — buena parte de internet todavía lo entrega sin él —, así que el estándar es
may. - 465 (submissions) — el usuario enviando, con TLS implícito: la conexión ya nace cifrada, como en HTTPS.
- 587 (submission) — el usuario enviando con STARTTLS, pero obligatorio: el servidor rechaza cualquier comando antes del TLS.
- 993 (IMAPS) y 995 (POP3S) — lectura del buzón con TLS implícito.
- 143 y 110 — IMAP y POP3 sin TLS implícito. No exponer a internet.
A RFC 8314 (enero de 2018) recomienda TLS implícito para envío y lectura — 465, 993 y 995 — y acepta la 587 con STARTTLS durante la transición. En la práctica, ofrezca 465 y 587: clientes antiguos y algunos teléfonos móviles siguen viniendo configurados en la 587.
Versiones en el laboratorio
| Paquete | Ubuntu 24.04 | Ubuntu 26.04 | Debian 13 |
|---|---|---|---|
| postfix | 3.8.6 | 3.10.6 | 3.10.13 |
| dovecot-core | 2.3.21 | 2.4.2 | 2.4.1 |
| certbot | 2.9.0 | 4.0.0 | 4.0.0 |
| openssl | 3.0.13 | 3.5.5 | — |
La línea que importa es la de Dovecot: el 2.4 cambió los nombres de las opciones de TLS y no acepta la configuración del 2.3 sin ajustes. Voy a mostrar las dos.
El certificado: Let's Encrypt en el nombre del servidor
El certificado tiene que tener el nombre que los clientes y los otros servidores usan para llegar hasta usted — el del registro MX y el que está configurado en Thunderbird y en el móvil. En esta entrada, mail.example.com. Si usas alias como smtp. e imap., inclúyelos todos en el mismo certificado con varios -d.
El servidor de correo generalmente no tiene sitio web; el modo standalone de certbot levanta un servidor web temporal en el puerto 80 solo para la validación:
sudo apt install -y certbot
sudo ufw allow 80/tcp
sudo certbot certonly --standalone -d mail.example.com \
--agree-tos -m postmaster@example.com --no-eff-email
Si ya hay un nginx o Apache en la máquina, usa --webroot -w /var/www/html en lugar de --standalone. El resultado queda en /etc/letsencrypt/live/mail.example.com/: fullchain.pem (certificado + intermedios) y privkey.pem (la clave). Los directorios live e archive son legibles solo por root — y está bien: Postfix y Dovecot abren el certificado como root, antes de soltar privilegios. No cambies los permisos.
Renovación y los plazos que se están acortando
El paquete instala el certbot.timer, que renueva solo. Lo que no hace es avisar a Postfix y a Dovecot: siguen sirviendo el certificado antiguo desde memoria hasta recargarlo. Un deploy hook resolve — certbot ejecuta los scripts de ese directorio solo cuando una renovación sale bien:
sudo tee /etc/letsencrypt/renewal-hooks/deploy/recarrega-email.sh >/dev/null <<'EOF'
#!/bin/sh
systemctl reload postfix dovecot
EOF
sudo chmod 755 /etc/letsencrypt/renewal-hooks/deploy/recarrega-email.sh
sudo certbot renew --dry-run
En Ubuntu 24.04 (certbot 2.9), los hooks de ese directorio solo se ejecutan con el subcomando renew — que es lo que utiliza el timer, así que la renovación automática funciona; en la primera emisión, recargue manualmente.
Esto dejó de ser un detalle. Let’s Encrypt anunció que el certificado por defecto de 90 días baja a 64 días el 10 de febrero de 2027 y a 45 días el 16 de febrero de 2028. Y el CA/Browser Forum aprobó en 2025 un límite máximo para todas las CA: 200 días desde el 15 de marzo de 2026, 100 días a partir de marzo de 2027 y 47 días a partir de marzo de 2029. La renovación manual se acabó; renovar sin recarga es certificado caducado en producción.
Postfix: TLS en main.cf
Debian y Ubuntu ya instalan Postfix con TLS activado, pero con el certificado autofirmado ssl-cert-snakeoil y sin versión mínima en los puertos de los usuarios. Cambia el certificado y ajusta lo demás:
sudo postconf -X smtpd_tls_cert_file smtpd_tls_key_file
sudo postconf -e \
'smtpd_tls_chain_files = /etc/letsencrypt/live/mail.example.com/privkey.pem, /etc/letsencrypt/live/mail.example.com/fullchain.pem' \
'smtpd_tls_security_level = may' \
'smtpd_tls_auth_only = yes' \
'smtpd_tls_mandatory_protocols = >=TLSv1.2' \
'smtp_tls_security_level = may' \
'smtp_tls_mandatory_protocols = >=TLSv1.2' \
'smtpd_tls_loglevel = 1' \
'smtp_tls_loglevel = 1' \
'smtpd_tls_received_header = yes'
smtpd_tls_chain_files(Postfix 3.4+): primero la clave, después la cadena. Sustituye el parsmtpd_tls_cert_file/smtpd_tls_key_file, por eso elpostconf -Xantes.smtpd_*es Postfix recibiendo;smtp_*es Postfix entregando a otros servidores. Ambos necesitan configuración.security_level = mayen el puerto 25, en ambos sentidos: TLS cuando el otro lado lo ofrece. Los puertos de los usuarios quedan conencryptnomaster.cf, a continuación.smtpd_tls_auth_only = yes: el servidor ni anunciaAUTHantes del TLS. Contraseña en texto plano resulta imposible, no solo desaconsejada.*_mandatory_protocols = >=TLSv1.2(sintaxis de Postfix 3.6+): sirve donde el TLS es obligatorio — los puertos 465 y 587 de los usuarios, a continuación. En el laboratorio, con esta línea, ambos rechazaron TLS 1.1 (Cipher is (NONE)) en ambos Ubuntu.- ¿Por qué no exigir TLS 1.2 también en el puerto 25? Porque allí el TLS es oportunista y, si el handshake falla, Postfix entrega en texto plano. Rechazar TLS 1.0 de un servidor antiguo no bloquea el mensaje — solo hace que viaje sin ningún cifrado, lo que es peor. Por eso el puerto 25 se queda con el valor por defecto de Postfix (
smtpd_tls_protocolsesmtp_tls_protocolsen>=TLSv1, sin SSLv2/SSLv3). En el laboratorio, el Postfix por defecto de 24.04 y de 26.04 negoció TLS 1.0 y 1.1 de verdad en la 25 — es lo esperado. Quien quiera TLS fuerte de verdad entre servidores usa MTA-STS o DANE, al final del post. loglevel = 1registra una línea por conexión TLS, yreceived_headergraba la versión y el cifrado en la cabeceraReceived:de cada mensaje — útil para demostrar, después, que llegó cifrado.
Postfix: los puertos 465 y 587 en master.cf
O /etc/postfix/master.cf del paquete trae ambas entradas comentadas. Descomente o añada, usando en la 5ª columna el mismo valor que la línea smtp inet de su archivo (y en 24.04, que ejecuta smtpd en chroot; n en 26.04):
submission inet n - y - - smtpd
-o syslog_name=postfix/submission
-o smtpd_tls_security_level=encrypt
-o smtpd_sasl_auth_enable=yes
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
-o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
submissions inet n - y - - smtpd
-o syslog_name=postfix/submissions
-o smtpd_tls_wrappermode=yes
-o smtpd_tls_security_level=encrypt
-o smtpd_sasl_auth_enable=yes
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
-o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
smtpd_tls_security_level=encrypt hace TLS obligatorio — es lo que activa el smtpd_tls_mandatory_protocols — y smtpd_tls_wrappermode=yes es el TLS implícito de 465. En ambas, solo pasa quien se ha autenticado. Quien comprueba la contraseña es Dovecot, mediante SASL:
sudo postconf -e 'smtpd_sasl_type = dovecot' 'smtpd_sasl_path = private/auth'
sudo postfix check && sudo systemctl restart postfix
La ruta private/auth es relativa a /var/spool/postfix y funciona con y sin chroot.
Dovecot 2.3 (Ubuntu 24.04)
Cree /etc/dovecot/conf.d/99-tls.conf — el 99 garantiza que se lea en último lugar y prevalezca sobre el 10-ssl.conf del paquete:
ssl = required
ssl_cert = </etc/letsencrypt/live/mail.example.com/fullchain.pem
ssl_key = </etc/letsencrypt/live/mail.example.com/privkey.pem
ssl_min_protocol = TLSv1.2
ssl_prefer_server_ciphers = yes
disable_plaintext_auth = yes
En 2.3, el < antes de la ruta es obligatorio: indica “lee el contenido de este archivo”. Sin él, Dovecot intenta usar la propia ruta como certificado.
Dovecot 2.4 (Ubuntu 26.04 y Debian 13)
El mismo archivo, con los nombres nuevos:
ssl = required
ssl_server_cert_file = /etc/letsencrypt/live/mail.example.com/fullchain.pem
ssl_server_key_file = /etc/letsencrypt/live/mail.example.com/privkey.pem
ssl_min_protocol = TLSv1.2
auth_allow_cleartext = no
ssl_cert/ssl_keyse han convertido enssl_server_cert_file/ssl_server_key_file, con ruta pura, sin<.disable_plaintext_auth = yesse convirtió enauth_allow_cleartext = no(que ya es el valor por defecto).ssl_prefer_server_ciphersse convirtió enssl_server_prefer_ciphers, yssl_dhse convirtió enssl_server_dh_file.- O
dovecot.confdebe comenzar condovecot_config_versiony tenerdovecot_storage_version— el paquete ya lo trae. Configuración copiada de un servidor 2.3 no arranca: consulta la guía de migración 2.3 → 2.4.
El socket de autenticación para Postfix (2.3 y 2.4)
En /etc/dovecot/conf.d/99-postfix-auth.conf, igual en ambas versiones:
service auth {
unix_listener /var/spool/postfix/private/auth {
mode = 0660
user = postfix
group = postfix
}
}
sudo doveconf -n >/dev/null && echo config OK
sudo systemctl restart dovecot
ssl = required con la autenticación sin texto plano hace que Dovecot rechace el inicio de sesión fuera de TLS. En el laboratorio, llegando desde otra máquina por el puerto 143:
# Dovecot 2.4
a1 NO [PRIVACYREQUIRED] Cleartext authentication disallowed on non-secure (SSL/TLS) connections.
# Dovecot 2.3 (POP3 na 110)
-ERR [AUTH] Plaintext authentication disallowed on non-secure (SSL/TLS) connections.
Atención al probar: Dovecot considera seguras las conexiones del propio 127.0.0.1. El inicio de sesión sin TLS desde el servidor funciona — la prueba que vale es desde otra máquina.
Firewall
sudo ufw allow 25,465,587,993,995/tcp
Deja 143 y 110 cerradas hacia fuera. Quien use webmail en la misma máquina accede por localhost; los clientes externos van por 993 y por 995.
Probando cada puerto
O openssl s_client muestra versión, cifrado y si la cadena del certificado fue verificada:
openssl s_client -brief -starttls smtp -connect mail.example.com:25 </dev/null
openssl s_client -brief -connect mail.example.com:465 </dev/null
openssl s_client -brief -starttls smtp -connect mail.example.com:587 </dev/null
openssl s_client -brief -connect mail.example.com:993 </dev/null
openssl s_client -brief -connect mail.example.com:995 </dev/null
Protocol version: TLSv1.3
Ciphersuite: TLS_AES_256_GCM_SHA384
Verification: OK
Esa fue la salida en los cinco puertos, en ambos Ubuntu. Para confirmar que TLS antiguo se rechaza en los puertos de los usuarios, fuerza la versión — y mira el cifrado, no la línea de protocolo, que el openssl imprime incluso cuando el handshake falla:
openssl s_client -tls1_1 -cipher 'DEFAULT:@SECLEVEL=0' \
-connect mail.example.com:465 </dev/null 2>&1 | grep 'Cipher is'
# Cipher is (NONE) ← recusado, como deve ser
Envío autenticado, verificando el certificado, con el swaks:
sudo apt install -y swaks
swaks --server mail.example.com:465 --tlsc --tls-verify \
--auth PLAIN --auth-user ana --from ana@example.com --to voce@gmail.com
=== TLS started with cipher TLSv1.3:TLS_AES_256_GCM_SHA384:256
<~ 235 2.7.0 Authentication successful
<~ 250 2.0.0 Ok: queued as 4D83AB47D87
Sustituya :465 --tlsc por :587 --tls para probar el otro puerto. En 587, sin STARTTLS, Postfix responde 530 5.7.0 Must issue a STARTTLS command first a cualquier comando — y el EHLO ni lista AUTH.
Para el lado servidor-servidor, Postfix trae el posttls-finger, que se conecta como Postfix se conectaría a un MX y dice el nivel de confianza obtenido:
posttls-finger -c -P /etc/ssl/certs "[mail.example.com]:25"
El resultado termina en Verified TLS connection established cuando el nombre coincide con el certificado y la cadena es válida. Untrusted indica cadena desconocida o nombre diferente — en el laboratorio, apareció al conectar por la IP en lugar del nombre. Para un análisis completo de cifrados y vulnerabilidades, el testssl.sh (GPLv2) acepta --starttls smtp; y el internet.nl prueba STARTTLS, DANE y DNSSEC de su dominio desde internet.
El ataque que elimina STARTTLS
El punto débil de la puerta 25 es el propio “oportunista”. STARTTLS se anuncia en texto plano; quien esté en medio puede borrar la línea 250-STARTTLS de la respuesta, y el remitente, configurado con may, entrega en texto plano pensando que el otro lado no soporta TLS. En las puertas 587 y 993 eso no ocurre, porque TLS es obligatorio y la conexión simplemente se cae.

Dos soluciones, que protegen los mensajes que llegan a tu dominio:
- MTA-STS (RFC 8461): publicas, vía HTTPS, una política diciendo “mis MX exigen TLS con un certificado válido”. Los remitentes que implementan el estándar pasan a rechazar entregas sin esto.
- DANE (RFC 7672): el certificado del MX se fija en un registro TLSA firmado con DNSSEC. Requiere DNSSEC en el dominio.
MTA-STS es el más sencillo de empezar. Dos registros y un archivo:
_mta-sts.example.com. IN TXT "v=STSv1; id=20260923T120000;"
_smtp._tls.example.com. IN TXT "v=TLSRPTv1; rua=mailto:tls-reports@example.com"
Y en https://mta-sts.example.com/.well-known/mta-sts.txt, servido con un certificado válido:
version: STSv1
mode: testing
mx: mail.example.com
max_age: 604800
Empieza con mode: testing: los remitentes entregan normalmente, pero envían informes de fallo a la dirección del registro _smtp._tls (TLS-RPT, RFC 8460). Sin sorpresas durante algunas semanas, cambia a mode: enforce y sustituye el id del registro TXT — es el cambio del id que avisa a los remitentes para que vuelvan a buscar la política. Cada MX del dominio debe estar en una línea mx:.
Para proteger los mensajes que salen, Postfix sabe usar DANE del lado del remitente: smtp_dns_support_level = dnssec e smtp_tls_security_level = dane. Los destinos con registro TLSA pasan a exigir TLS verificado; los que no lo tienen siguen con may. Esto depende de un resolver local que valide DNSSEC (unbound, por ejemplo) — sin él, no lo actives.
Lo que suele romperse
- El cliente se queja de certificado inválido — está configurado con un nombre que no está en el certificado (
imap.example.com, la IP, el nombre antiguo del servidor). Emítelo con todos los nombres usados o estandarice los clientes. - Funciona en Thunderbird y falla en otro servidor — usó
cert.pemen lugar defullchain.pemy faltó el intermedio. Los navegadores y algunos clientes completan la cadena por sí solos; los servidores de e-mail no. - El certificado caducó, pero la renovación se ejecutó — faltó el deploy hook y el servicio siguió con el certificado antiguo en memoria.
openssl s_client ... | openssl x509 -noout -enddatemuestra lo que realmente se está sirviendo. - Dovecot no arranca después de actualizar a 26.04 — configuración 2.3 en 2.4.
doveconf -napunta a la línea. certbot --standalonefallo — el puerto 80 está ocupado por un servidor web o cerrado en el firewall. Use--webroot.- Clientes antiguos dejan de conectar tras exigir TLS 1.2 — sistemas y clientes de correo muy antiguos, sin soporte para TLS 1.2. Es el precio justo; TLS 1.0 está prohibido por la RFC 8996.
- No sale nada por el puerto 25 — muchos proveedores de nube bloquean la 25 de salida por defecto. No es TLS: es solicitar la liberación en el panel del proveedor.
Resumen
Certificado válido en el nombre del MX, renovación con recarga, TLS 1.2 como mínimo en todo lo que lleva contraseña (465, 587, 993, 995), el puerto 25 oportunista como debe ser, contraseña solo dentro del TLS, 465/587/993/995 abiertas y 143/110 cerradas. Con esto el servidor está al día; con MTA-STS, también pasa a proteger lo que llega. Los próximos pasos del mismo pílón: DKIM y DMARC en Postfix, el panel go-postfixadmin para los buzones virtuales y la historia de las piezas en La historia de Postfix e La historia de Dovecot.