
Servidor de correo sin DKIM y DMARC en 2026 es servidor con mensajes yendo al spam — o volviendo. Desde febrero de 2024, Gmail y Yahoo exigen autenticación de todo remitente, y a partir de noviembre de 2025 Gmail pasó a rechazar, temporal o definitivamente, el tráfico fuera de las reglas. A continuación, el camino completo en un Postfix: SPF, firma DKIM con OpenDKIM, política DMARC y la verificación por parte de quien recibe con OpenDMARC. Todo probado en laboratorio en Ubuntu 24.04 y en 26.04.
Lo que exigen los grandes proveedores
Las reglas que empujaron a todo el mundo hacia esto:
- Gmail, desde el 1 de febrero de 2024: todo remitente necesita SPF o DKIM, DNS inverso (PTR) válido, TLS y tasa de spam inferior a 0,3%. Quien envíe más de 5.000 mensajes al día a cuentas Gmail necesita SPF e DKIM, DMARC publicado (
p=nonebasta),From:alineado y baja definitiva en un clic en los correos de marketing. En noviembre de 2025 Google comenzó a endurecer la aplicación, con rechazos temporales y permanentes. - Yahoo: las mismas exigencias desde febrero de 2024, sin divulgar un número mínimo de mensajes para ser considerado envío masivo.
- Outlook.com: desde el 5 de mayo de 2025, los dominios que envían más de 5.000 mensajes por día necesitan SPF, DKIM y DMARC. Quien no los tenga acabará en la carpeta de correo no deseado.
Incluso quien envía pocos mensajes sale ganando con los tres: sin ellos, cualquiera puede mandar correo con tu dominio en el From:, y el destino no tiene cómo distinguir.
Las tres piezas y la alineación
- SPF (RFC 7208): un registro TXT en el dominio lista qué IPs pueden entregar correo en su nombre. El destino lo compara con la IP que abrió la conexión. Se aplica a la dirección del sobre (
MAIL FROM), no para elFrom:que el lector ve. - DKIM (RFC 6376): el servidor de salida firma cabeceras y cuerpo con una clave privada; la clave pública queda en el DNS. Si alguien toca el mensaje por el camino, la firma se rompe. Sobrevive al reenvío, lo que SPF no hace.
- DMARC: ata los dos al
From:. Exige que SPF o DKIM pase e que el dominio validado sea el mismo delFrom:— esto es el alineamiento. También indica al destino qué hacer cuando falla (none,quarantine,reject) y adónde enviar informes. En mayo de 2026 la especificación se convirtió en estándar de la IETF como RFC 9989, con los informes en las RFCs 9990 y 9991, sustituyendo a la RFC 7489.

En este post: dominio example.com, servidor mail.example.com, IP de salida 203.0.113.10. Sustitúyelos por los tuyos.
Versiones y el detalle del chroot
| Paquete | Ubuntu 24.04 | Ubuntu 26.04 | Debian 13 |
|---|---|---|---|
| postfix | 3.8.6 | 3.10.6 | 3.10.13 |
| opendkim | 2.11.0~beta2 | 2.11.0~beta2 | 2.11.0~beta2 |
| opendmarc | 1.4.2 | 1.4.2 | 1.4.2 |
| postfix-policyd-spf-python | 3.0.4 | 3.1.0 | 3.1.0 |
OpenDKIM nunca tuvo una 2.11 final: el upstream se detuvo en la beta2 de 2018 y las distribuciones empaquetan esa, con sus propias correcciones. OpenDMARC está en la 1.4.2 desde 2021. Los dos siguen siendo los milters estándar para Postfix.
La diferencia que importa: en Ubuntu 24.04 y en Debian 13 el smtpd de Postfix se ejecuta en chroot en /var/spool/postfix; en 26.04 el paquete por fin desactivó el chroot por defecto. Compruébalo en tu:
grep -E '^smtp\s+inet' /etc/postfix/master.cf
# smtp inet n - y - - smtpd ← "y" na 5ª coluna = chroot
Por eso los sockets de los milters van a quedar dentro de /var/spool/postfix y Postfix apuntará a ellos con ruta relativa — funciona con y sin chroot, y así lo probé en los dos Ubuntu.
SPF: el primer registro
En el DNS del dominio, un TXT en la raíz:
example.com. IN TXT "v=spf1 mx ip4:203.0.113.10 -all"
mx autoriza los servidores del registro MX, ip4: la IP de salida, y -all dice que el resto no puede. Si otro servicio envía en su nombre (newsletter, ERP, Google Workspace), añada el include: que él documenta. Dos cuidados:
- Un registro SPF solamente. Dos TXT comenzando con
v=spf1dan error permanente. - Como máximo 10 consultas DNS (
include,a,mx,redirect…) en la evaluación completa, contando lasincludede los demás. Si se supera, el SPF dapermerror.
Mientras no tengas la certeza de haberlo listado todo, usa ~all (softfail). Con DMARC funcionando, el -all pesa menos de lo que parece — ya explico por qué.
Aprovecha y comprueba el DNS inverso: el PTR de 203.0.113.10 debe apuntar a mail.example.com, y ese nombre debe resolverse de vuelta a la misma IP. Gmail lo exige.
OpenDKIM: generar la clave
sudo apt install -y opendkim opendkim-tools
sudo mkdir -p /etc/opendkim/keys/example.com
sudo opendkim-genkey -b 2048 -h sha256 -r -s s2026 -d example.com \
-D /etc/opendkim/keys/example.com
Esto crea s2026.private (la clave privada) y s2026.txt (el registro DNS listo). -s s2026 es el selector, el nombre de la clave; usar el año facilita la rotación más adelante. -b 2048 sigue la RFC 8301, que exige como mínimo 1024 bits y recomienda 2048; el paquete de Debian/Ubuntu ya usa 2048 por defecto, pero dejarlo explícito no cuesta. -r restringe la clave al correo electrónico. El opendkim-genkey empaquetado solo genera RSA: nada de Ed25519 por ahora.
Permisos: la clave privada solo puede ser leída por OpenDKIM:
sudo chown -R opendkim:opendkim /etc/opendkim
sudo chmod 700 /etc/opendkim/keys/example.com
sudo chmod 600 /etc/opendkim/keys/example.com/s2026.private
OpenDKIM: tablas y opendkim.conf
Tres archivos pequeños. /etc/opendkim/key.table indica qué clave usa cada selector:
s2026._domainkey.example.com example.com:s2026:/etc/opendkim/keys/example.com/s2026.private
/etc/opendkim/signing.table indica qué remitentes firman con qué clave:
*@example.com s2026._domainkey.example.com
/etc/opendkim/trusted.hosts lista de dónde procede el correo que debe ser firmado (no verificado):
127.0.0.1
::1
localhost
Si aplicaciones de otras máquinas usan este Postfix como relay, añada las IP de ellas. Quien envíe autenticado vía SMTP AUTH (puerto 587, cliente de correo, webmail) se firma automáticamente: OpenDKIM firma cuando la IP es interna o cuando Postfix informa de que la sesión fue autenticada.
Ahora sustituya el /etc/opendkim.conf:
Syslog yes
SyslogSuccess yes
LogWhy yes
Canonicalization relaxed/simple
Mode sv
OversignHeaders From
UserID opendkim
UMask 007
Socket local:/var/spool/postfix/opendkim/opendkim.sock
PidFile /run/opendkim/opendkim.pid
TrustAnchorFile /usr/share/dns/root.key
KeyTable refile:/etc/opendkim/key.table
SigningTable refile:/etc/opendkim/signing.table
ExternalIgnoreList /etc/opendkim/trusted.hosts
InternalHosts /etc/opendkim/trusted.hosts
Mode sv: firma lo que sale y verifica lo que llega.Canonicalization relaxed/simple: tolera cambios de espacio en las cabeceras por el camino; el cuerpo tiene que llegar idéntico.OversignHeaders From: impide que alguien añada un segundoFrom:sin romper la firma.refile:noSigningTablees lo que hace el*@example.comfuncionar como comodín.Socketdentro de/var/spool/postfix, por motivo del chroot.
El archivo /etc/default/opendkim todavía existe, pero el propio paquete lo marca como legado: todo va en el opendkim.conf. Cree el directorio del socket, ponga Postfix en el grupo de OpenDKIM (el UMask 007 da acceso al grupo) y pruebe la configuración:
sudo mkdir -p /var/spool/postfix/opendkim
sudo chown opendkim:postfix /var/spool/postfix/opendkim
sudo chmod 750 /var/spool/postfix/opendkim
sudo usermod -aG opendkim postfix
sudo opendkim -n -x /etc/opendkim.conf && echo config OK
sudo systemctl restart opendkim
Publicar la clave en el DNS
sudo cat /etc/opendkim/keys/example.com/s2026.txt
s2026._domainkey IN TXT ( "v=DKIM1; h=sha256; k=rsa; s=email; "
"p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2pGx9Ms2..."
"tyJOiuSbPxdG4hH7kpWfJscarmix9gP6iAVhM+Dieij2..." )
Cree un TXT con nombre s2026._domainkey en el dominio. Una clave de 2048 bits no cabe en una única cadena DNS (límite de 255 caracteres), por eso el archivo viene partido en varias cadenas entre comillas; el destino las une sin espacios. En BIND, pegue el bloque tal cual. En paneles de proveedor, la mayoría acepta el valor entero pegado en una sola línea — v=DKIM1; h=sha256; k=rsa; s=email; p=MIIB...IDAQAB — y lo divide solo.
Cuando el DNS propague, pruebe:
dig +short TXT s2026._domainkey.example.com
sudo opendkim-testkey -d example.com -s s2026 -vvv
opendkim-testkey: checking key 's2026._domainkey.example.com'
opendkim-testkey: key not secure
opendkim-testkey: key OK
key OK lo que importa: la clave pública en el DNS coincide con la privada en el disco. key not secure solo avisa de que el dominio no tiene DNSSEC — no impide nada. Si aparece Revoked key, el DNS devolvió un registro con p= vacío: selector incorrecto, registro aún no propagado o un comodín en el dominio.
Un detalle que confunde en la red interna: con TrustAnchorFile configurado, OpenDKIM resuelve el DNS por sí solo, desde la raíz, e ignora el /etc/resolv.conf. Si publicaste la clave solo en un DNS interno, no la va a encontrar.
Conectar los milters en Postfix
sudo postconf -e \
'smtpd_milters = local:opendkim/opendkim.sock, local:opendmarc/opendmarc.sock' \
'non_smtpd_milters = $smtpd_milters' \
'milter_default_action = accept'
sudo postfix check
La ruta relativa (opendkim/opendkim.sock) se resuelve desde /var/spool/postfix, con chroot o sin él. non_smtpd_milters hace firmar también lo que entra por el comando sendmail local (cron, scripts, aplicaciones PHP). OpenDMARC se trata en la siguiente sección; si quieres probar solo DKIM ahora, deja únicamente el primer socket.
milter_default_action decide qué pasa si el milter está fuera de servicio. El valor predeterminado es tempfail: Postfix rechaza temporalmente y el remitente vuelve a intentarlo después — no se pierde nada, pero no entra ni sale nada mientras OpenDKIM esté parado. Con accept, el correo sigue fluyendo, solo que sin firma. Elija con conocimiento; yo prefiero accept con monitorización del servicio.
sudo systemctl restart postfix
echo "teste" | mail -s "teste DKIM" voce@gmail.com # pacote mailutils
sudo grep opendkim /var/log/mail.log | tail -3
opendkim[4242]: 52C29B42EDE: DKIM-Signature field added (s=s2026, d=example.com)
DMARC: publicar la política
Otro TXT, en _dmarc.example.com. Empiece observando:
_dmarc.example.com. IN TXT "v=DMARC1; p=none; rua=mailto:dmarc@example.com"
p=none: no cambia nada en la entrega; los proveedores solo empiezan a enviar informes.rua=: adónde van los informes agregados — un XML por día, por proveedor, listando cada IP que envió mensaje con su dominio y si pasó SPF y DKIM. Ahí aparece el sistema olvidado que también envía en su nombre.adkimeaspf(predeterminador, relajado): en el modo relajado,news.example.comse alinea conexample.com. Deje el predeterminado.sp=: política para subdominios, si quieres distinta.
Después de dos a cuatro semanas leyendo informes sin sorpresas, sube a p=quarantine (el fallo va al spam) y, por último, p=reject (el fallo es rechazado). La etiqueta pct=, utilizada para aplicar la política solo a un porcentaje de los mensajes, fue eliminada en la RFC 9989 y sustituida por t=y (modo de prueba). Muchos receptores siguen todavía la RFC antigua; el aumento gradual en escalones de none para reject funciona con ambas.
Si el rua= apuntar a otro dominio (un servicio de informes, por ejemplo), ese dominio debe autorizar publicando example.com._report._dmarc.relatorios.example.net TXT "v=DMARC1" — sin esto, los proveedores no envían.
Para leer los XML sin sufrimiento, el parsedmarc (Apache 2.0) lee el buzón de informes por IMAP y genera JSON, CSV o los envía a Elasticsearch/OpenSearch. El propio OpenDMARC incluye opendmarc-import e opendmarc-reports, pero sirven para generar informes sobre quién le envía a usted — y requieren un MySQL.
OpenDMARC: verificar lo que llega
Hasta aquí firma y publica. Por el otro lado, su servidor también recibe correo — y puede rechazar a quienes falsifiquen dominios con p=reject. Es el trabajo de OpenDMARC:
sudo apt install -y opendmarc publicsuffix
Durante la instalación, debconf pregunta si debe configurar una base de datos con dbconfig-common; responda no — solo sirve para generar informes. Sustituya el /etc/opendmarc.conf:
AuthservID mail.example.com
TrustedAuthservIDs mail.example.com
IgnoreAuthenticatedClients true
IgnoreHosts /etc/opendmarc/ignore.hosts
RejectFailures true
RequiredHeaders true
SPFSelfValidate true
FailureReports false
PidFile /run/opendmarc/opendmarc.pid
PublicSuffixList /usr/share/publicsuffix/public_suffix_list.dat
Socket local:/var/spool/postfix/opendmarc/opendmarc.sock
Syslog true
UMask 0002
UserID opendmarc
AuthservIDeTrustedAuthservIDs: el nombre que aparece en las cabecerasAuthentication-Results. OpenDMARC lee el resultado del DKIM que OpenDKIM anotó con ese nombre.RejectFailures true: rechaza cuando el dominio del remitente publicap=rejecty el mensaje falla. Confalse, solo anota la cabecera.SPFSelfValidate true: el propio OpenDMARC evalúa el SPF, sin depender de otro componente.IgnoreAuthenticatedClientseIgnoreHosts: no verifica el correo de sus usuarios ni el local.RequiredHeaders true: rechaza mensajes sinFrom:válido — un truco habitual de falsificación.FailureReports false: no envía informes de fallo individuales, que contienen fragmentos del mensaje.
sudo mkdir -p /etc/opendmarc /var/spool/postfix/opendmarc
printf '127.0.0.1\n::1\n' | sudo tee /etc/opendmarc/ignore.hosts
sudo chown opendmarc:postfix /var/spool/postfix/opendmarc
sudo chmod 750 /var/spool/postfix/opendmarc
sudo usermod -aG opendmarc postfix
sudo opendmarc -n -c /etc/opendmarc.conf && echo config OK
sudo systemctl restart opendmarc postfix
O smtpd_milters de la sección anterior ya lista el socket de OpenDMARC después de OpenDKIM; el orden importa, porque el DMARC necesita el resultado de DKIM.
La prueba que convence
En el laboratorio, con los tres registros publicados en un DNS local y p=reject, envié desde fuera dos mensajes con From: ana@example.com, desde una IP que el SPF no autoriza. La primera, con la firma DKIM original, se entregó con estas cabeceras:
Authentication-Results: mail.example.com; dmarc=pass (p=reject dis=none) header.from=example.com
Authentication-Results: mail.example.com;
dkim=pass (2048-bit key; unprotected) header.d=example.com header.i=@example.com header.a=rsa-sha256 header.s=s2026
Received-SPF: Fail (mailfrom) identity=mailfrom; client-ip=172.17.0.2; ...
El SPF falló, pero el DKIM pasó y está alineado con el From: — así que el DMARC pasa. Es exactamente el caso de un mensaje reenviado. La segunda era el mismo mensaje con el cuerpo cambiado por “Pague el boleto adjunto.”:
opendkim[5617]: 8F25AB44DDF: bad signature data
opendmarc[5622]: 8F25AB44DDF: example.com fail
postfix/cleanup: 8F25AB44DDF: milter-reject: END-OF-MESSAGE ... 5.7.1 rejected by DMARC policy for example.com
Rechazada. Lo mismo ocurrió con un mensaje falsificado en nombre de paypal.com, que publica p=reject de verdad.
Para probar tu dominio de verdad, envía un mensaje a una cuenta de Gmail y abre Mostrar original: la parte superior muestra SPF, DKIM y DMARC con PASS. El mail-tester.com proporciona una dirección desechable y una nota con el diagnóstico de cada elemento.
SPF en Postfix: registrar, no rechazar
Es tentador rechazar directamente en el RCPT TO todo lo que falla en SPF, con el postfix-policyd-spf-python. En el laboratorio, esto hizo que Postfix rechazara el mensaje legítimo de la prueba anterior antes de que se mirara DKIM: el SPF falló y se terminó ahí. Los reenvíos y las listas de correo caen en este agujero todo el tiempo. Con DMARC, la decisión queda mejor con él. Si quieres la cabecera Received-SPF para tu antispam, instala policyd-spf solo para anotar:
sudo apt install -y postfix-policyd-spf-python
sudo sed -i 's/^Mail_From_reject = Fail/Mail_From_reject = False/' \
/etc/postfix-policyd-spf-python/policyd-spf.conf
En el /etc/postfix/master.cf:
policyd-spf unix - n n - 0 spawn
user=policyd-spf argv=/usr/bin/policyd-spf
Y en el main.cf, con el check_policy_service después del reject_unauth_destination:
sudo postconf -e 'policyd-spf_time_limit = 3600' \
'smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination, check_policy_service unix:private/policyd-spf'
sudo systemctl reload postfix
Dos trucos. En el policyd-spf.conf, TestOnly = 1 es el modo normal, que aplica los rechazos; 0 es que lo apaga — el nombre engaña. Y el proceso de policyd-spf se queda vivo hasta una hora (policyd-spf_time_limit) sin releer la configuración: después de editar, reinicie Postfix.
Cambiar la clave sin perder mensajes
Cambie la clave DKIM una vez al año, e inmediatamente si la privada se filtra. El selector en el nombre es lo que permite hacerlo sin ventana de fallo:
- Genere la nueva con otro selector:
opendkim-genkey -b 2048 -h sha256 -r -s s2027 -d example.com -D /etc/opendkim/keys/example.com, y ajuste los permisos. - Publique
s2027._domainkeyen el DNS y espere a que propague (opendkim-testkey -s s2027). - Sustituya
s2026pors2027nokey.tabley ensigning.tabley reinicie OpenDKIM. - Mantenga el registro
s2026en el DNS durante una o dos semanas — los mensajes firmados con él aún están en colas y bandejas siendo verificadas. - Elimine el
s2026del DNS y borre la clave antigua.
Lo que suele romperse
warning: connect to Milter service local:opendkim/opendkim.sock: No such file or directory— elSocketdelopendkim.confno está dentro de/var/spool/postfix, o el servicio no ha arrancado.Permission denieden el socket — faltóusermod -aG opendkim postfix(y reiniciar Postfix después), o el directorio del socket no pertenece al grupopostfix.- El mensaje sale sin firma — el remitente no coincide con el
signing.table(sinrefile:, el*@no funciona) o la IP de origen no está enInternalHostsy la sesión no fue autenticada.LogWhy yesse explica en el log. dkim=failen destino con firma presente — algo modificó el mensaje después de firmarlo: un filtro de contenido, un disclaimer o un antivirus reinyectando después del milter.- Registro DKIM pegado con espacio o comillas en medio de la clave en el panel del proveedor. Compara con
dig +short TXT s2026._domainkey.example.com. - Consultas lentas — el SPF de algunos dominios grandes genera decenas de consultas DNS. En el laboratorio, con el DNS lento de un router, una sola evaluación tardó casi un minuto. Un resolver local con caché en el servidor de correo lo soluciona.
¿Y Rspamd?
Rspamd realiza firma DKIM, verificación de SPF, DKIM y DMARC, ARC y antispam en un único servicio, también como milter de Postfix, y está empaquetado en todas las distribuciones mencionadas. Para un servidor nuevo con volumen, es la elección moderna. OpenDKIM y OpenDMARC siguen teniendo sentido cuando solo quieres la autenticación, con piezas pequeñas y configuración en archivos de texto, que es lo que he mostrado aquí.
Para montar el resto del servidor: go-postfixadmin para los buzones virtuales, y la historia detrás de las piezas en La historia de Postfix e La historia de Dovecot. Quien usa panel tiene DMARC integrado en el ISPConfig 3.3.2.