Alrededor de mediados de 2024, después del quinto servidor ese mes donde olvidamos configurar DKIM y pasamos una hora preguntándonos por qué los correos de confirmación rebotaban, decidimos escribir un script de despliegue. Uno de verdad. No un archivo bash con 40 líneas y una plegaria, sino un sistema en Python que toma un VPS con Ubuntu limpio y lo convierte en un servidor web listo para producción en menos de 20 minutos. Con correo. Con SSL. Con reglas de firewall. Con backups. Con un health check que detecta errores de configuración antes de enviar cualquier tráfico.

Ocho meses y aproximadamente 7.000 líneas de código después, ese script despliega cada servidor que operamos en 9 mercados LATAM para nuestra operación de media buying. Este artículo explica por qué lo construimos, qué hace y qué aprendimos sobre configuración de servidores que la mayoría de equipos — ya sea en media buying, SEO o tráfico de pago — nunca piensan hasta que algo se rompe a las 2 AM un martes.

El Costo Real de Configurar Servidores Manualmente en Media Buying

Nuestra operación ejecuta activos digitales propios: landing pages, content hubs, sitios de comparación. Cada uno vive en su propio VPS con su propio dominio, su propio certificado SSL, su propia configuración de correo. Cuando gestionas un puñado de estos, la configuración manual es molesta pero sobrevivible. Cuando operas docenas en múltiples países, se convierte en el cuello de botella que ralentiza todo lo demás.

Rastreamos nuestro proceso de despliegue manual durante un mes. Los números no fueron bonitos.

Tiempo promedio para desplegar un servidor desde cero: 2 horas y 40 minutos. Eso incluía instalar paquetes, configurar nginx, configurar PHP-FPM, obtener SSL, configurar Postfix con DKIM, configurar acceso para transferencia de archivos, configurar el firewall y ejecutar verificaciones básicas. Dos horas y cuarenta minutos del tiempo de un operador, haciendo la misma secuencia de comandos con variaciones menores, en cada servidor.

Tasa de error en despliegues manuales: aproximadamente 1 de cada 4 tenía algo mal configurado. Registro DKIM faltante. Límite de memoria PHP incorrecto. Regla de firewall que bloqueaba el puerto de correo. Certificado SSL solicitado antes de que el DNS propagara, así que certbot falló silenciosamente y nadie se dio cuenta hasta una semana después cuando el certificado temporal expiró. Cada error costaba otros 30 a 60 minutos para diagnosticar y corregir.

Esa matemática resulta en unas 15 horas por mes solo en configuración de servidores. Para un equipo pequeño de media buying, eso es una cuarta parte completa del tiempo de trabajo de una persona gastada en trabajo de infraestructura repetitivo en lugar de optimización de campañas, creación de contenido o investigación de mercado.

La peor parte no era el tiempo. Era la inconsistencia. Dos servidores configurados por la misma persona con una semana de diferencia tenían diferentes configuraciones de PHP, diferentes reglas de firewall, diferentes configuraciones de correo. Cuando algo fallaba en un servidor, el proceso de debugging era único para ese servidor porque la configuración era única. No había dos servidores iguales.

Qué Construimos: Un Script, 40 Paquetes, 15 Minutos

El sistema es un único script de Python usando la librería Fabric para automatización SSH. Un bloque de configuración al inicio — IP del servidor, dominio, credenciales del proveedor DNS, unas cuantas flags de funcionalidad — y un solo comando para ejecutarlo. El script se conecta al VPS por SSH, instala todo, configura todo, prueba todo y produce un reporte de despliegue con todas las credenciales y detalles de conexión.

Esto es lo que se ejecuta, en orden, en cada despliegue:

Preparación del sistema. Actualización del SO, configuración del hostname basado en el dominio, instalación de paquetes. Instalamos aproximadamente 40 paquetes en un solo comando apt — nginx, PHP-FPM con la versión específica que elegimos, Postfix, OpenDKIM, certbot, fail2ban, UFW y todas las extensiones PHP que un sitio moderno necesita. El script preconfigura Postfix a través de debconf antes de la instalación para prevenir los prompts interactivos que rompen las instalaciones desatendidas. Después de la instalación, la caché de paquetes se limpia automáticamente — ahorrando 200 a 500 MB de espacio en disco en instancias VPS pequeñas.

PHP-FPM con auto-tuning. El script lee la RAM total del servidor y calcula los workers óptimos. Un VPS de 512 MB obtiene 5 workers. Uno de 1 GB obtiene 10. Uno de 4 GB obtiene 50. Límite de memoria, OPcache, límites de upload, timeouts de ejecución — todo se configura condicionalmente según si estamos desplegando un sitio PHP estático o WordPress. El pool se ejecuta bajo un nombre generado con su propio socket, no el pool www predeterminado.

SFTP para gestión de archivos. Abandonamos el FTP tradicional por completo. SFTP a través de SSH significa un servicio menos ejecutándose, un puerto menos abierto y transferencia de archivos encriptada por defecto. El script crea un usuario dedicado con el directorio web como home, genera una contraseña fuerte y produce un archivo de configuración XML compatible con FileZilla que importamos con un clic.

La secuencia completa — desde conexión SSH hasta reporte de despliegue — se completa en 14 a 18 minutos dependiendo de la velocidad del mirror de paquetes del proveedor VPS.

DNS por API y SSL que No se Rompe

Configurar DNS manualmente significa entrar al panel del registrador, crear registros A, registros MX, registros TXT SPF, y luego volver a añadir registros DKIM y DMARC después de que el servidor de correo genere sus claves. Cada uno de esos pasos es una oportunidad de error de copy-paste. Un carácter incorrecto en un registro TXT DKIM y tu correo saliente falla la verificación DKIM — silenciosamente.

El script soporta tres modos de DNS: API de Cloudflare, API de Namecheap y BIND9 local. Para Cloudflare y Namecheap, crea todos los registros automáticamente — registro A apuntando a la IP del servidor, registro MX para correo, registro SPF, y luego registros DKIM y DMARC. Sin logins en paneles. Sin copiar y pegar valores TXT. El script lee la clave pública DKIM generada y la envía directamente a la API DNS.

Los certificados SSL — Let's Encrypt o ZeroSSL — se obtienen con lógica de reintentos y verificaciones de propagación DNS. Antes de solicitar el certificado, el script verifica que el dominio realmente resuelve a la IP del servidor consultando Google DNS, Cloudflare DNS y Quad9. Si el DNS aún no ha propagado, el script espera y reintenta en lugar de dejar que certbot falle con un error críptico. El timer de auto-renovación se configura automáticamente.

Esto solo evitó más despliegues fallidos que cualquier otra funcionalidad individual. En nuestro proceso manual, alrededor del 30% de los fallos de SSL eran causados por solicitar el certificado antes de que el DNS hubiera propagado — un problema que simplemente desaparece cuando el script verifica primero.

Mail que Pasa Todos los Filtros de Bandeja de Entrada

Hacer que el correo funcione es fácil. Hacer que el correo realmente llegue a las bandejas de entrada — eso requiere cuatro cosas configuradas correctamente. La mayoría de tutoriales de despliegue cubren como máximo dos.

Postfix maneja envío y recepción. El script escribe el main.cf completo desde una plantilla con el dominio correcto, hostname, certificados TLS y restricciones de relay. También añade reglas anti-spam: validación HELO, verificación del dominio del remitente, verificaciones de destinatario. Estas cuatro líneas rechazan aproximadamente el 40% del correo basura antes de que el cuerpo del mensaje sea transferido. El tamaño del buzón está limitado a 500 MB por cuenta para prevenir el desbordamiento de disco por inundaciones de spam.

Firma DKIM a través de OpenDKIM. El script genera un par de claves RSA de 2048 bits, configura las tablas de firma y envía la clave pública a DNS automáticamente. Cada correo saliente recibe una firma criptográfica que los servidores receptores pueden verificar. Sin DKIM, Gmail y Outlook envían tus mensajes directamente al spam.

Registros SPF y DMARC van al DNS junto con DKIM. SPF indica a los servidores receptores qué IPs están autorizadas a enviar correo del dominio. DMARC les dice qué hacer cuando SPF o DKIM falla. Junto con DKIM, forman el trío de autenticación que la infraestructura de correo moderna requiere.

Dovecot IMAP con puertos de submission permite enviar y recibir correo a través de un cliente estándar como Thunderbird. El script configura IMAPS en el puerto 993, submission en el puerto 587 con STARTTLS y SMTPS en el puerto 465. Genera un archivo XML de autoconfig para Thunderbird para que el cliente de correo configure todo automáticamente — solo introduce email y contraseña.

Después de la configuración, el script prueba la entrega de correo a cada buzón y verifica que los mensajes lleguen al Maildir. También verifica si el puerto 25 de salida está abierto — muchos proveedores cloud lo bloquean por defecto para prevenir spam. Si está bloqueado, el reporte de despliegue incluye una solicitud lista para enviar al proveedor de hosting pidiendo que lo desbloqueen.

Nginx y PHP: La Configuración de la que Nadie Habla

La mayoría de guías de despliegue se detienen en instalar nginx y PHP, quizás configurar el límite de memoria. Hay varias configuraciones de producción que importan tanto para seguridad como para estabilidad y que raramente se mencionan en tutoriales.

Configuración de producción de Nginx. No la de Ubuntu por defecto con server_tokens activados y sin headers de seguridad. Nuestra configuración incluye redirección HTTPS con HSTS, HTTP/2, X-Frame-Options, X-Content-Type-Options, compresión gzip o brotli, y un bloque de servidor catch-all que rechaza solicitudes hechas directamente a la IP en lugar del dominio. La configuración SSL incluye session tickets, parámetros DH y suites de cifrado modernas para TLS 1.2 y 1.3. La ruta de auto-renovación de certbot está explícitamente en whitelist para que la renovación del certificado no se rompa con la regla de denegación de dotfiles.

PHP open_basedir restringe qué directorios puede acceder PHP. Lo limitamos al directorio web, /tmp y la ruta de librerías del sistema PHP. Si alguien explota una vulnerabilidad en un script PHP, no puede leer archivos del sistema como /etc/passwd, configuraciones del servidor de correo o claves SSH. Una línea en la configuración del pool FPM, cero costo en rendimiento, reducción significativa del radio de daño.

disable_functions bloquea funciones PHP peligrosas. Para sitios estáticos: exec, system, shell_exec, passthru, proc_open y popen están deshabilitadas. Para despliegues WordPress: proc_open y popen permanecen habilitadas porque WP-CLI y varios plugins las necesitan. El resto se bloquea. Esto previene que un script PHP comprometido ejecute comandos del sistema.

Aislamiento de sesiones. Las sesiones PHP van a un directorio por pool bajo /var/lib/php/sessions/ con permisos 700. Cada sitio tiene su propio almacenamiento de sesiones. La filtración de sesiones entre sitios es imposible incluso si múltiples sitios comparten servidor.

Límites de upload y ejecución se configuran diferente según el tipo de sitio. WordPress obtiene 128 MB de límite de upload, 120 segundos de timeout de ejecución y 5000 max_input_vars (los menús WordPress con muchos elementos lo necesitan). Los sitios PHP estáticos obtienen 32 MB de upload, 30 segundos de timeout y 1000 max_input_vars — límites más estrictos significan una superficie de ataque menor.

OPcache obtiene 128 MB de memoria compartida, valida timestamps de archivos cada 2 segundos en lugar de cada solicitud, y cachea hasta 10.000 archivos. La caché realpath se configura a 4 MB en lugar del default de PHP de 4 KB — este cambio solo reduce el overhead de include/require de forma medible en sitios WordPress con docenas de plugins.

Firewall, Fail2ban y Aislamiento de Acceso a Archivos

El firewall UFW arranca con una política de denegar todo el tráfico entrante. Solo se abren los puertos realmente necesarios: SSH, HTTP, HTTPS, SMTP. Si el stack completo de correo está habilitado, se añaden submission (587), SMTPS (465) e IMAPS (993). Si se usa FTP tradicional en lugar de SFTP, se abren el puerto 21 y el rango pasivo. Nada más. El firewall se activa temprano en el despliegue — antes de que la mayoría de servicios estén instalados — para que el servidor nunca esté completamente abierto durante la configuración.

Fail2ban monitoriza logs de autenticación y bloquea automáticamente IPs después de intentos fallidos de login. Se configuran jails para SSH, autenticación HTTP de nginx y detección de bots de nginx. Cuando el stack completo de correo está habilitado, se activan jails adicionales para Dovecot (fuerza bruta IMAP) y Postfix SASL (fuerza bruta de autenticación SMTP). Los logs rotan semanalmente con retención de 7 días.

Los límites de descriptores de archivo se elevan a 65.535 tanto para nginx como para PHP-FPM a través de overrides de servicios systemd. El default de Ubuntu de 1.024 es suficiente para tráfico ligero pero causa errores "too many open files" bajo carga — un problema extremadamente molesto de debugear cuando ocurre a escala porque el error es intermitente y depende del número de conexiones concurrentes.

El swap se dimensiona automáticamente según la RAM del servidor (2x para servidores bajo 1 GB, 1x para 2 a 4 GB, con tope de 4 GB para servidores más grandes). El swappiness se configura a 15 en lugar del default de Ubuntu de 60, lo que significa que el kernel mantiene los workers de PHP-FPM en RAM en lugar de enviarlos al swap cuando el uso de memoria alcanza el 40%.

36 Verificaciones Automatizadas Antes de Enviar Tráfico

El despliegue termina con un health check automatizado que verifica cada componente. No solo "¿arrancó nginx?" — verificación funcional del comportamiento real.

Nginx: ejecutándose y la sintaxis de configuración pasa nginx -t. PHP-FPM: ejecutándose y el archivo socket existe. Solicitud HTTP a localhost con el header Host correcto devuelve un 200. Solicitud HTTPS al dominio real verifica el certificado SSL. Postfix: ejecutándose y postfix check valida la configuración de correo y el puerto 25 está escuchando. Registros DKIM, SPF y DMARC consultados a través de Google DNS para confirmar que son visibles globalmente.

Para SFTP: el subsistema SSH está habilitado, el usuario existe con el directorio home correcto, y — cuando sshpass está disponible — se realiza un login SFTP real y listado de directorio.

Swap activo, swappiness en 15, límites de descriptores de archivo de nginx en 65.535 (leídos desde /proc, no desde la configuración — el valor real en runtime), caché apt limpiada, timer de renovación de certbot activo, actualizaciones de seguridad desatendidas habilitadas, fail2ban ejecutándose, backups automáticos programados.

Cuando el stack completo de correo está activado: Dovecot ejecutándose, puerto 993 escuchando, puertos 587 y 465 escuchando, autoconfig de Thunderbird accesible vía HTTPS.

Si algo falla, el reporte lo marca con la verificación específica que falló y cuál era el valor esperado. Hemos detectado registros DNS mal configurados, emisión fallida de SSL, pools PHP-FPM muertos y servicios de correo sin respuesta — todo antes de que un solo visitante llegara al servidor. Encontrar estos problemas durante el despliegue lleva segundos. Encontrarlos después de que el tráfico está corriendo cuesta horas y conversiones perdidas.

Seis Meses de Despliegues Automatizados: Los Números

Llevamos ejecutando el sistema automatizado desde finales de 2024. La comparación con nuestro proceso manual:

El tiempo de despliegue cayó de 2 horas 40 minutos a 14 a 18 minutos. La parte más larga es la instalación de paquetes, que depende de la velocidad del mirror del proveedor VPS. Todo lo demás — DNS, SSL, correo, nginx, PHP, firewall, health check — se ejecuta en menos de 5 minutos combinados.

La tasa de error pasó del 25% a efectivamente cero. El health check lo captura todo. No hemos tenido un incidente de "olvidé el DKIM" o "versión de PHP incorrecta" desde que empezamos a usar el script. Cuando ocurren errores, son ambientales — puerto 25 de salida bloqueado por el proveedor, propagación DNS más lenta de lo esperado — y el script los reporta claramente en lugar de fallar silenciosamente.

La rotación de servidores se volvió trivial. Cuando necesitamos mover un sitio a una nueva IP, el proceso es: levantar un nuevo VPS, ejecutar el script, subir el sitio vía SFTP, cambiar DNS. Menos de 30 minutos en total. Antes de la automatización, esto era un proyecto de medio día que nadie quería empezar.

El script también genera un reporte de despliegue completo con cada credencial, dirección IP, ruta de archivo y parámetro de configuración. No más buscar en el historial del terminal para encontrar la contraseña FTP configurada hace tres semanas. Cada servidor tiene un archivo de reporte con timestamp con todo lo necesario para gestionarlo.

La inversión — varios cientos de horas de desarrollo a lo largo de múltiples meses — se pagó sola dentro del primer mes de uso regular. No desde alguna métrica abstracta de eficiencia, sino desde horas concretas recuperadas y errores concretos eliminados. Cada servidor que desplegamos arranca desde la misma base endurecida, probada y documentada. Sin excepciones. Sin atajos. Sin "configuraré el firewall después."