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.

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:
- Solicitar la apertura — Solo funciona si tienes cuenta Pay-As-You-Go y abres un ticket de soporte. En Free Tier suelen negarlo.
- 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:
| Tipo | Host | Valor | TTL |
|---|---|---|---|
| A Record | mail | [IP pública de OCI] | Automatic |
| MX Record | @ | mail.yudexlabs.com | 10 / Automatic |
| CNAME | autodiscover | mail.yudexlabs.com | Automatic |
| CNAME | autoconfig | mail.yudexlabs.com | Automatic |
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:
- Mail server hostname (FQDN):
mail.yudexlabs.com - Timezone:
America/Bogota(o la tuya) - 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:
- Nginx recibe HTTPS en el puerto 443 y hace
proxy_passa Mailcow por HTTP (puerto interno) - Mailcow recibe una petición HTTP y, al tener
HTTP_REDIRECT=ypor defecto, responde con un redirect a HTTPS - 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 Requestporque 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:
| Tipo | Host | Valor |
|---|---|---|
| TXT | dkim._domainkey | v=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
| Error | Causa | Solución |
|---|---|---|
Cannot find command 'jq' | Paquete no instalado | sudo apt install jq -y |
address already in use (puerto 80) | Nginx del host ocupa el puerto | Reasignar Mailcow a un puerto interno libre ligado a 127.0.0.1 |
NXDOMAIN en Certbot | Subdominios DNS no existían | Crear CNAME para autodiscover y autoconfig |
ERR_TOO_MANY_REDIRECTS | Mailcow redirigía HTTP→HTTPS en bucle | Establecer HTTP_REDIRECT=n en mailcow.conf |
Login rechazado con admin/moohoo | Panel 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
autodiscoveryautoconfig, 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=ndeberí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

Hablemos
Tu negocio ya está listo para un agente de IA — solo falta que hablemos.
Respuesta rápida.
Respondemos en menos de 24 horas, siempre.
Sin rodeos.
En la primera llamada ya sabes si la IA es para ti y por dónde empezar.
¿Por dónde quieres empezar?
Elige tu canal.