SSL/TLS no Postfix e no Dovecot: por que e como

Mascote do LinuxPro fechando um grande cadeado dourado no servidor de e-mail, com envelopes trancados passando por um túnel protegido até outro servidor e o cão caramelo ao lado

Servidor de e-mail sem TLS entrega duas coisas de graça a quem estiver no caminho: o conteúdo das mensagens e a senha de cada usuário, que viaja a cada vez que o celular confere a caixa. E não é só privacidade: desde fevereiro de 2024 o Gmail exige conexão TLS de todo remetente. Abaixo, o SSL/TLS completo num servidor Postfix + Dovecot — certificado Let’s Encrypt, as portas certas, versões mínimas, autenticação só com criptografia e a proteção contra o ataque que remove o STARTTLS. Tudo testado em laboratório no Ubuntu 24.04 e no 26.04, que trazem Dovecots de versões diferentes, com sintaxes diferentes.

Por que o servidor de e-mail precisa de TLS

  • Senhas. IMAP, POP3 e SMTP autenticado mandam usuário e senha no início de cada sessão. Sem TLS, qualquer um no Wi-Fi do café, no provedor ou num roteador comprometido lê em texto puro. Um único login capturado dá acesso à caixa inteira — e à redefinição de senha de tudo que está ligado àquele e-mail.
  • Conteúdo. Contrato, boleto, nota fiscal, exame médico: a mensagem inteira passa legível entre o cliente e o servidor e entre servidores.
  • Entregabilidade. As regras do Gmail para qualquer remetente incluem “Use a TLS connection for transmitting email”, ao lado de SPF/DKIM e DNS reverso — o mesmo pacote do post sobre DKIM e DMARC no Postfix.
  • Protocolo antigo é protocolo quebrado. A RFC 8996 (março de 2021) diz que TLS 1.0 e 1.1 “MUST NOT be used”. O mínimo hoje é TLS 1.2, e o TLS 1.3 é o que os clientes modernos negociam.

As portas e o tipo de TLS de cada uma

Diagrama das portas: o usuário envia pela 465 com TLS implícito ou pela 587 com STARTTLS obrigatório, lê a caixa pela 993 ou 995 com TLS implícito; o Postfix troca mensagens com outros servidores pela 25 com STARTTLS oportunista; o Dovecot autentica o SMTP do Postfix por socket local

  • 25 (SMTP) — servidor falando com servidor. Usa STARTTLS oportunista (RFC 3207): a conversa começa em texto puro e sobe para TLS se os dois lados oferecerem. Não dá para exigir TLS aqui de todo mundo — boa parte da internet ainda entrega sem ele —, então o padrão é may.
  • 465 (submissions) — o usuário enviando, com TLS implícito: a conexão já nasce criptografada, como no HTTPS.
  • 587 (submission) — o usuário enviando com STARTTLS, mas obrigatório: o servidor recusa qualquer comando antes do TLS.
  • 993 (IMAPS) e 995 (POP3S) — leitura da caixa com TLS implícito.
  • 143 e 110 — IMAP e POP3 sem TLS implícito. Não exponha para a internet.

A RFC 8314 (janeiro de 2018) recomenda TLS implícito para envio e leitura — 465, 993 e 995 — e aceita a 587 com STARTTLS durante a transição. Na prática, ofereça 465 e 587: clientes antigos e alguns celulares ainda vêm configurados na 587.

Versões no laboratório

Pacote 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 —

A linha que importa é a do Dovecot: o 2.4 mudou os nomes das opções de TLS e não aceita a configuração do 2.3 sem ajuste. Vou mostrar as duas.

O certificado: Let’s Encrypt no nome do servidor

O certificado tem de ter o nome que os clientes e os outros servidores usam para chegar até você — o do registro MX e o que está configurado no Thunderbird e no celular. Neste post, mail.example.com. Se você usa apelidos como smtp. e imap., inclua todos no mesmo certificado com vários -d.

Servidor de e-mail geralmente não tem site; o modo standalone do certbot sobe um servidor web temporário na porta 80 só para a validação:

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

Se já existe um nginx ou Apache na máquina, use --webroot -w /var/www/html no lugar de --standalone. O resultado fica em /etc/letsencrypt/live/mail.example.com/: fullchain.pem (certificado + intermediários) e privkey.pem (a chave). Os diretórios live e archive são legíveis só pelo root — e tudo bem: Postfix e Dovecot abrem o certificado como root, antes de largar os privilégios. Não mude as permissões.

Renovação e os prazos que estão encurtando

O pacote instala o certbot.timer, que renova sozinho. O que ele não faz é avisar o Postfix e o Dovecot: eles continuam servindo o certificado antigo da memória até recarregarem. Um deploy hook resolve — o certbot roda os scripts desse diretório só quando uma renovação dá certo:

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

No Ubuntu 24.04 (certbot 2.9), os hooks desse diretório só rodam com o subcomando renew — que é o que o timer usa, então a renovação automática funciona; na primeira emissão, recarregue na mão.

Isso deixou de ser detalhe. O Let’s Encrypt anunciou que o certificado padrão de 90 dias cai para 64 dias em 10 de fevereiro de 2027 e para 45 dias em 16 de fevereiro de 2028. E o CA/Browser Forum aprovou em 2025 um teto para todas as CAs: 200 dias desde 15 de março de 2026, 100 dias a partir de março de 2027 e 47 dias a partir de março de 2029. Renovação manual acabou; renovação sem reload é certificado vencido em produção.

Postfix: TLS no main.cf

O Debian e o Ubuntu já instalam o Postfix com TLS ligado, mas com o certificado autoassinado ssl-cert-snakeoil e sem piso de versão nas portas dos usuários. Troque o certificado e acerte o resto:

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+): a chave primeiro, depois a cadeia. Substitui o par smtpd_tls_cert_file/smtpd_tls_key_file, por isso o postconf -X antes.
  • smtpd_* é o Postfix recebendo; smtp_* é o Postfix entregando para outros servidores. Os dois precisam de configuração.
  • security_level = may na porta 25, nos dois sentidos: TLS quando o outro lado oferece. As portas dos usuários ficam com encrypt no master.cf, abaixo.
  • smtpd_tls_auth_only = yes: o servidor nem anuncia AUTH antes do TLS. Senha em texto puro fica impossível, não só desaconselhada.
  • *_mandatory_protocols = >=TLSv1.2 (sintaxe do Postfix 3.6+): vale onde o TLS é obrigatório — as portas 465 e 587 dos usuários, abaixo. No laboratório, com essa linha, as duas recusaram TLS 1.1 (Cipher is (NONE)) nos dois Ubuntu.
  • Por que não exigir TLS 1.2 também na porta 25? Porque ali o TLS é oportunista e, se o handshake falha, o Postfix entrega em texto puro. Recusar TLS 1.0 de um servidor antigo não bloqueia a mensagem — só a faz viajar sem criptografia nenhuma, o que é pior. Por isso a porta 25 fica com o padrão do Postfix (smtpd_tls_protocols e smtp_tls_protocols em >=TLSv1, sem SSLv2/SSLv3). No laboratório, o Postfix padrão do 24.04 e do 26.04 negociou TLS 1.0 e 1.1 de verdade na 25 — é esperado. Quem quiser TLS forte de verdade entre servidores usa MTA-STS ou DANE, no fim do post.
  • loglevel = 1 registra uma linha por conexão TLS, e received_header grava versão e cifra no cabeçalho Received: de cada mensagem — útil para provar, depois, que ela chegou criptografada.

Postfix: as portas 465 e 587 no master.cf

O /etc/postfix/master.cf do pacote traz as duas entradas comentadas. Descomente ou acrescente, usando na 5ª coluna o mesmo valor da linha smtp inet do seu arquivo (y no 24.04, que roda o smtpd em chroot; n no 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 torna o TLS obrigatório — é o que ativa o smtpd_tls_mandatory_protocols — e smtpd_tls_wrappermode=yes é o TLS implícito da 465. Nas duas, só passa quem autenticou. Quem confere a senha é o Dovecot, pelo SASL:

sudo postconf -e 'smtpd_sasl_type = dovecot' 'smtpd_sasl_path = private/auth'
sudo postfix check && sudo systemctl restart postfix

O caminho private/auth é relativo a /var/spool/postfix e funciona com e sem chroot.

Dovecot 2.3 (Ubuntu 24.04)

Crie /etc/dovecot/conf.d/99-tls.conf — o 99 garante que ele é lido por último e prevalece sobre o 10-ssl.conf do pacote:

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

No 2.3, o < antes do caminho é obrigatório: ele diz “leia o conteúdo deste arquivo”. Sem ele, o Dovecot tenta usar o próprio caminho como certificado.

Dovecot 2.4 (Ubuntu 26.04 e Debian 13)

O mesmo arquivo, com os nomes novos:

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_key viraram ssl_server_cert_file/ssl_server_key_file, com caminho puro, sem <.
  • disable_plaintext_auth = yes virou auth_allow_cleartext = no (que já é o padrão).
  • ssl_prefer_server_ciphers virou ssl_server_prefer_ciphers, e ssl_dh virou ssl_server_dh_file.
  • O dovecot.conf precisa começar com dovecot_config_version e ter dovecot_storage_version — o pacote já traz. Configuração copiada de um servidor 2.3 não sobe: veja o guia de migração 2.3 → 2.4.

O socket de autenticação para o Postfix (2.3 e 2.4)

Em /etc/dovecot/conf.d/99-postfix-auth.conf, igual nas duas versões:

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 com a autenticação sem texto puro faz o Dovecot recusar login fora do TLS. No laboratório, vindo de outra máquina pela porta 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.

Atenção ao testar: o Dovecot considera seguras as conexões do próprio 127.0.0.1. Login sem TLS a partir do servidor funciona — o teste que vale é de outra máquina.

Firewall

sudo ufw allow 25,465,587,993,995/tcp

Deixe 143 e 110 fechadas para fora. Quem usa webmail na mesma máquina acessa pelo localhost; os clientes externos vão pela 993 e pela 995.

Testando cada porta

O openssl s_client mostra versão, cifra e se a cadeia do certificado foi 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

Essa foi a saída nas cinco portas, nos dois Ubuntu. Para confirmar que TLS antigo é recusado nas portas dos usuários, force a versão — e olhe a cifra, não a linha de protocolo, que o openssl imprime mesmo quando o handshake falha:

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

Envio autenticado, verificando o certificado, com o 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

Troque :465 --tlsc por :587 --tls para testar a outra porta. Na 587, sem STARTTLS, o Postfix responde 530 5.7.0 Must issue a STARTTLS command first a qualquer comando — e o EHLO nem lista AUTH.

Para o lado servidor-servidor, o Postfix traz o posttls-finger, que se conecta como o Postfix se conectaria a um MX e diz o nível de confiança obtido:

posttls-finger -c -P /etc/ssl/certs "[mail.example.com]:25"

O resultado termina em Verified TLS connection established quando o nome bate com o certificado e a cadeia é válida. Untrusted indica cadeia desconhecida ou nome diferente — no laboratório, apareceu ao conectar pelo IP em vez do nome. Para uma análise completa de cifras e vulnerabilidades, o testssl.sh (GPLv2) aceita --starttls smtp; e o internet.nl testa STARTTLS, DANE e DNSSEC do seu domínio pela internet.

O ataque que remove o STARTTLS

O ponto fraco da porta 25 é o próprio “oportunista”. O STARTTLS é anunciado em texto puro; quem estiver no meio pode apagar a linha 250-STARTTLS da resposta, e o remetente, configurado com may, entrega em texto puro achando que o outro lado não suporta TLS. Nas portas 587 e 993 isso não acontece, porque o TLS é obrigatório e a conexão simplesmente cai.

Diagrama comparando dois cenários: sem proteção, um intermediário apaga o STARTTLS da resposta e a mensagem segue em texto puro; com MTA-STS ou DANE, o remetente já sabe que o destino exige TLS, recusa a conexão rebaixada e mantém a mensagem na fila

Duas soluções, que protegem as mensagens que chegam no seu domínio:

  • MTA-STS (RFC 8461): você publica, por HTTPS, uma política dizendo “meus MX exigem TLS com certificado válido”. Os remetentes que implementam o padrão passam a recusar entregar sem isso.
  • DANE (RFC 7672): o certificado do MX é fixado num registro TLSA assinado com DNSSEC. Exige DNSSEC no domínio.

O MTA-STS é o mais simples de começar. Dois registros e um arquivo:

_mta-sts.example.com.   IN TXT  "v=STSv1; id=20260923T120000;"
_smtp._tls.example.com. IN TXT  "v=TLSRPTv1; rua=mailto:tls-reports@example.com"

E em https://mta-sts.example.com/.well-known/mta-sts.txt, servido com um certificado válido:

version: STSv1
mode: testing
mx: mail.example.com
max_age: 604800

Comece com mode: testing: os remetentes entregam normalmente, mas mandam relatórios de falha ao endereço do registro _smtp._tls (TLS-RPT, RFC 8460). Sem surpresas por algumas semanas, mude para mode: enforce e troque o id do registro TXT — é a mudança do id que avisa os remetentes para buscarem a política de novo. Cada MX do domínio precisa estar numa linha mx:.

Para proteger as mensagens que saem, o Postfix sabe usar DANE do lado de quem envia: smtp_dns_support_level = dnssec e smtp_tls_security_level = dane. Destinos com registro TLSA passam a exigir TLS verificado; os sem registro continuam no may. Isso depende de um resolver local que valide DNSSEC (unbound, por exemplo) — sem ele, não ative.

O que costuma quebrar

  • Cliente reclama de certificado inválido — ele está configurado com um nome que não está no certificado (imap.example.com, o IP, o nome antigo do servidor). Emita com todos os nomes usados ou padronize os clientes.
  • Funciona no Thunderbird e falha em outro servidor — usou cert.pem em vez de fullchain.pem e faltou o intermediário. Navegadores e alguns clientes completam a cadeia sozinhos; servidores de e-mail não.
  • Certificado venceu, mas a renovação rodou — faltou o deploy hook e o serviço seguiu com o certificado antigo em memória. openssl s_client ... | openssl x509 -noout -enddate mostra o que está sendo servido de fato.
  • Dovecot não sobe depois de atualizar para o 26.04 — configuração 2.3 no 2.4. doveconf -n aponta a linha.
  • certbot --standalone falha — a porta 80 está ocupada por um servidor web ou fechada no firewall. Use --webroot.
  • Clientes antigos param de conectar após exigir TLS 1.2 — sistemas e clientes de e-mail muito antigos, sem suporte a TLS 1.2. É o preço certo; TLS 1.0 é proibido pela RFC 8996.
  • Nada sai pela porta 25 — muitos provedores de nuvem bloqueiam a 25 de saída por padrão. Não é TLS: é pedido de liberação no painel do provedor.

Resumo

Certificado válido no nome do MX, renovação com reload, TLS 1.2 como piso em tudo que leva senha (465, 587, 993, 995), a porta 25 oportunista como deve ser, senha só dentro do TLS, 465/587/993/995 abertas e 143/110 fechadas. Com isso o servidor está em dia; com MTA-STS, ele também passa a proteger o que chega. Os próximos passos da mesma pilha: DKIM e DMARC no Postfix, o painel go-postfixadmin para as caixas virtuais e a história das peças em A história do Postfix e A história do Dovecot.