Yudex Labs
DevOpsSelf-HostingDockerInfraestructuraMailcow

Cómo construí mi propio servidor de correo corporativo desde cero (sin morir en el intento)

El proceso real y sin filtros de montar un servidor de correo autogestionado con Mailcow, Docker y Nginx en Oracle Cloud. Errores incluidos.

23 de junio de 2026
12 min de lectura
Cómo construí mi propio servidor de correo corporativo desde cero (sin morir en el intento)

Llega un momento como founder en el que tener soporte@gmail.com empieza a verse mal. Quería soporte@yudexlabs.com y noreply@yudexlabs.com, pero no quería depender de Google Workspace ni pagar por buzones que apenas uso. Tenía un servidor Ubuntu corriendo en Oracle Cloud y decidí que era momento de montar mi propio servidor de correo.

Lo que parecía un proyecto de una tarde terminó siendo una sesión de depuración de errores encadenados, decisiones de arquitectura inesperadas y un par de lecciones que no vienen en ningún tutorial. Este artículo documenta el proceso real, paso a paso, incluyendo los errores y cómo los resolví.

Qué necesitas antes de empezar

Antes de tocar ningún comando, hay cosas que debes tener resueltas. Saltarse este checklist es la razón número uno por la que los tutoriales de servidores de correo no funcionan:

Infraestructura:

  • Un servidor Ubuntu con mínimo 4 GB de RAM (Mailcow corre varios contenedores en paralelo y con menos se traba)
  • Docker y Docker Compose instalados
  • Una IP pública fija asignada al servidor
  • El puerto 25 desbloqueado por tu proveedor cloud — esto es crítico y lo explico más abajo

Dominio:

  • Un dominio propio (yo uso Namecheap con yudexlabs.com)
  • Acceso al panel de DNS para crear registros A, MX, CNAME y TXT

Conocimientos mínimos:

  • Saber editar archivos en la terminal con nano
  • Entender qué es un proxy inverso (Nginx apuntando tráfico hacia otro servicio)
  • Paciencia con la propagación DNS — algunos cambios tardan hasta 30 minutos en verse

La advertencia sobre Oracle Cloud (y proveedores similares)

Estoy en OCI (Oracle Cloud Infrastructure) usando la capa gratuita. Aquí hay un problema que no tiene solución fácil: OCI bloquea el puerto 25 de salida por defecto, especialmente en cuentas Free Tier. El puerto 25 es el que usan los servidores para enviarse correos entre ellos.

Si estás en la misma situación, tienes dos caminos:

  1. Solicitar la apertura — Solo funciona si tienes cuenta Pay-As-You-Go y abres un ticket de soporte. En Free Tier suelen negarlo.
  2. Usar un SMTP Relay (lo que haré eventualmente) — Servicios como SendGrid, Brevo o Amazon SES reciben tus correos y los entregan usando su propia reputación de IP. Tu servidor gestiona los buzones, ellos gestionan la entrega.

Para este artículo me enfoco en levantar la infraestructura base. La configuración del relay SMTP la cubriré en un artículo aparte.

La arquitectura: por qué usé Mailcow con Docker

Cuando investigué las opciones, me encontré con dos corrientes:

  • Instalación manual (Postfix + Dovecot + SpamAssassin + Roundcube): control total, pero mantenimiento constante y configuración compleja
  • Stack en contenedores: todo orquestado, interfaz web incluida, actualizaciones simples

Elegí Mailcow Dockerized porque es la solución más completa del mercado open source. Incluye en un solo stack: servidor SMTP/IMAP, webmail (SOGo), antispam (Rspamd), antivirus (ClamAV) y un panel de administración moderno. Además, ya tenía Docker corriendo en el servidor.

La arquitectura final quedó así:

Internet
    │
    ▼
Nginx (host) — puertos 80 y 443
    │
    │  proxy_pass → puerto interno (solo accesible desde el host)
    ▼
Mailcow (Docker) — ligado a 127.0.0.1
    └── SMTP, IMAP, Webmail, Antispam

Nginx actúa como intermediario: recibe el tráfico externo con SSL y lo pasa internamente a Mailcow. Mailcow no queda expuesto directamente a internet.

Paso 1: Configurar el DNS en Namecheap

Antes de instalar nada en el servidor, el dominio tiene que apuntar correctamente a la IP del servidor. En el panel Advanced DNS de Namecheap creé estos registros:

TipoHostValorTTL
A Recordmail[IP pública de OCI]Automatic
MX Record@mail.yudexlabs.com10 / Automatic
CNAMEautodiscovermail.yudexlabs.comAutomatic
CNAMEautoconfigmail.yudexlabs.comAutomatic

Los subdominios autodiscover y autoconfig son necesarios para que clientes como Outlook o Thunderbird detecten automáticamente la configuración del servidor al agregar una cuenta. Olvídate de crearlos y Certbot te dará un error — lo aprendí por las malas.

Paso 2: Instalar Mailcow en el servidor

Con el DNS configurado, entro al servidor y ejecuto:

sudo su
cd /opt
git clone https://github.com/mailcow/mailcow-dockerized
cd mailcow-dockerized

El siguiente paso es generar la configuración inicial con el script interactivo:

./generate_config.sh

Aquí me encontré el primer error:

Error: Cannot find command 'jq'. Cannot proceed.

jq es una herramienta para procesar JSON que el script necesita pero Ubuntu no trae instalada por defecto. La solución es simple:

sudo apt update && sudo apt install jq -y

Una vez instalada, vuelvo a ejecutar ./generate_config.sh. El script hace tres preguntas:

  1. Mail server hostname (FQDN): mail.yudexlabs.com
  2. Timezone: America/Bogota (o la tuya)
  3. Branch: 1 (master, la versión estable)

Esto genera el archivo mailcow.conf con contraseñas y claves criptográficas únicas para tu instalación.

Paso 3: Resolver el conflicto de puertos con Nginx

Intenté levantar los contenedores:

docker compose pull
docker compose up -d

Y apareció el segundo error:

failed to bind host port for 0.0.0.0:80:172.22.1.11:80/tcp: address already in use

Tiene sentido: mi servidor ya tiene Nginx corriendo y sirviendo otras aplicaciones. El contenedor Nginx interno de Mailcow intentó tomar el puerto 80 (y 443), que ya estaban ocupados.

La solución es configurar Mailcow para que escuche en puertos alternativos internos y usar el Nginx del host como proxy inverso. Edito mailcow.conf:

nano /opt/mailcow-dockerized/mailcow.conf

Busco y ajusto estas variables para redirigir Mailcow a puertos libres en mi servidor y ligarlo exclusivamente a la interfaz local (de modo que nunca quede expuesto directamente a internet):

# Elige un puerto HTTP libre en tu servidor (no uses 80 ni 443)
HTTP_PORT=XXXX
HTTP_BIND=127.0.0.1

# Elige un puerto HTTPS libre en tu servidor
HTTPS_PORT=YYYY
HTTPS_BIND=127.0.0.1

# Le decimos a Mailcow que el SSL lo gestiona Nginx externamente
SKIP_LETS_ENCRYPT=y

# Clave para evitar el bucle de redirecciones — lo explico en el paso 6
HTTP_REDIRECT=n

El HTTP_BIND=127.0.0.1 es lo más importante aquí: garantiza que esos puertos solo sean accesibles desde el propio servidor, nunca desde internet.

Levanto los contenedores nuevamente:

docker compose down
docker compose up -d

Esta vez arranca sin errores.

Paso 4: Configurar Nginx como proxy inverso

Creo el archivo de configuración de Nginx para el servidor de correo:

sudo nano /etc/nginx/sites-available/mailcow.conf

Configuración inicial para la validación de Certbot:

server {
    listen 80;
    server_name mail.yudexlabs.com autodiscover.yudexlabs.com autoconfig.yudexlabs.com;

    location / {
        proxy_pass http://127.0.0.1:XXXX/;  # el puerto HTTP que elegiste en mailcow.conf
        proxy_set_header Host $http_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;
        client_max_body_size 0;
    }
}

Activo el sitio y recargo Nginx:

sudo ln -s /etc/nginx/sites-available/mailcow.conf /etc/nginx/sites-enabled/
sudo systemctl reload nginx

Paso 5: Instalar el certificado SSL con Certbot

sudo certbot --nginx -d mail.yudexlabs.com -d autodiscover.yudexlabs.com -d autoconfig.yudexlabs.com

El tercer error llegó aquí:

DNS problem: NXDOMAIN looking up A for autoconfig.yudexlabs.com
DNS problem: NXDOMAIN looking up A for autodiscover.yudexlabs.com

NXDOMAIN significa que Let's Encrypt no pudo encontrar esos subdominios en el DNS. Había creado el registro mail pero me olvidé de autodiscover y autoconfig. Los añadí en Namecheap como CNAME apuntando a mail.yudexlabs.com, esperé unos minutos para la propagación y volví a ejecutar Certbot. Esta vez funcionó sin problemas.

Certbot modifica automáticamente el archivo de Nginx y deja la configuración SSL lista.

Paso 6: El bucle de redirecciones (el error más frustrante)

Con los certificados instalados, fui al navegador a https://mail.yudexlabs.com y me encontré con el cuarto y más complicado error:

ERR_TOO_MANY_REDIRECTS

Esto ocurre por un conflicto de lógica entre las dos capas:

  1. Nginx recibe HTTPS en el puerto 443 y hace proxy_pass a Mailcow por HTTP (puerto interno)
  2. Mailcow recibe una petición HTTP y, al tener HTTP_REDIRECT=y por defecto, responde con un redirect a HTTPS
  3. El navegador vuelve a https://mail.yudexlabs.com, Nginx vuelve a pasar por HTTP a Mailcow, y el ciclo se repite infinitamente

Intenté dos caminos intermedios que no funcionaron:

  • Pasar el tráfico al puerto HTTPS interno de Mailcow: generó un error 400 Bad Request porque Nginx enviaba tráfico en texto plano a un puerto que espera conexión cifrada
  • Forzar el header X-Forwarded-Proto: https: no rompió el ciclo porque el redirect de Mailcow no lee ese header para tomar la decisión

La solución real fue más simple de lo que pensé: decirle a Mailcow directamente que no haga redirect, ya que Nginx ya se encarga de eso externamente. En mailcow.conf:

HTTP_REDIRECT=n

Aplicar el cambio:

docker compose down && docker compose up -d

La configuración final de Nginx quedó así:

server {
    listen 80;
    server_name mail.yudexlabs.com autodiscover.yudexlabs.com autoconfig.yudexlabs.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name mail.yudexlabs.com autodiscover.yudexlabs.com autoconfig.yudexlabs.com;

    ssl_certificate /etc/letsencrypt/live/mail.yudexlabs.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mail.yudexlabs.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    location / {
        proxy_pass http://127.0.0.1:XXXX/;  # el puerto HTTP que elegiste en mailcow.conf
        proxy_set_header Host $http_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;
        client_max_body_size 0;
    }
}

Paso 7: Configurar Mailcow desde el panel de administración

Al entrar a https://mail.yudexlabs.com ya cargó el panel. Aquí el quinto error fue humano: intenté iniciar sesión con admin / moohoo directamente en la página principal y me rechazaba la contraseña.

La razón: en las versiones recientes de Mailcow, la página principal es el webmail para usuarios finales. El panel de administración está en /admin. Hay que entrar a:

https://mail.yudexlabs.com/admin

Una vez dentro, seguí estos pasos en orden:

1. Cambiar la contraseña de administrador System → Administrators → editar admin y asignar una contraseña segura. Las credenciales por defecto son públicas y conocidas.

2. Agregar el dominio Configuration → Mail Setup → Domains → Add domain Ingresé yudexlabs.com y confirmé con Add domain and restart SOGo.

3. Crear los buzones Mail Setup → Mailboxes → Add mailbox

  • Username: soporte / Domain: yudexlabs.com
  • Username: noreply / Domain: yudexlabs.com

4. Generar la firma DKIM Configuration → ARC/DKIM keys

  • Domain: yudexlabs.com
  • Selector: dkim
  • Longitud: 2048 bits

Mailcow genera un bloque de texto largo que comienza con v=DKIM1; k=rsa; .... Ese valor va como registro TXT en Namecheap:

TipoHostValor
TXTdkim._domainkeyv=DKIM1; k=rsa; p=...

Paso 8: Sellar la entregabilidad (SPF y DMARC)

Sin estos registros, los correos llegan a spam o son rechazados directamente por Gmail y Outlook.

SPF — indica qué servidores tienen permiso para enviar en nombre de tu dominio:

Tipo: TXT
Host: @
Valor: v=spf1 mx a:mail.yudexlabs.com ~all

(Si usas un relay como SendGrid, el valor cambia a v=spf1 include:sendgrid.net ~all)

DMARC — política de qué hacer cuando un correo no pasa la validación SPF o DKIM:

Tipo: TXT
Host: _dmarc
Valor: v=DMARC1; p=quarantine; rua=mailto:soporte@yudexlabs.com

Resumen de errores y soluciones

ErrorCausaSolución
Cannot find command 'jq'Paquete no instaladosudo apt install jq -y
address already in use (puerto 80)Nginx del host ocupa el puertoReasignar Mailcow a un puerto interno libre ligado a 127.0.0.1
NXDOMAIN en CertbotSubdominios DNS no existíanCrear CNAME para autodiscover y autoconfig
ERR_TOO_MANY_REDIRECTSMailcow redirigía HTTP→HTTPS en bucleEstablecer HTTP_REDIRECT=n en mailcow.conf
Login rechazado con admin/moohooPanel de admin está en /admin, no en /Acceder a https://mail.yudexlabs.com/admin

Lo que aprendí (y lo que harías diferente si empiezas de cero)

Lo que funciona muy bien:

  • Mailcow es genuinamente robusto. Una vez que está corriendo, el panel de administración es limpio y claro, incluso para alguien no técnico.
  • Docker hace que todo el stack sea portable y fácil de actualizar: docker compose pull && docker compose up -d.
  • Let's Encrypt + Certbot automatiza la renovación del certificado SSL. No hay que preocuparse por eso nunca más.

Lo que cambiaría:

  • Crear todos los registros DNS desde el principio, incluyendo autodiscover y autoconfig, antes de ejecutar Certbot.
  • Documentar desde el inicio qué puertos están ocupados en el servidor host para evitar conflictos al elegir los puertos de Mailcow.
  • El problema de HTTP_REDIRECT=n debería ser el primer paso en cualquier guía de Mailcow con proxy inverso, no el último recurso.

Lo que queda pendiente:

  • Configurar el SMTP Relay (Brevo o SendGrid) para el envío de correos salientes, dado el bloqueo del puerto 25 en OCI.
  • Configurar registros PTR/rDNS una vez que OCI o el relay estén resueltos.
  • Probar la entregabilidad con herramientas como mail-tester.com para asegurar que los correos llegan a la bandeja de entrada.

El servidor está corriendo, los buzones están creados y el dominio está correctamente autenticado. Para una startup que quiere control total sobre su infraestructura sin pagar suscripciones mensuales por buzón, esta arquitectura vale absolutamente el tiempo de configuración.

Artículos recientes