Servidor Web Linux desde Cero 2026: Guía Definitiva
Si te interesa entender qué pasa entre tu navegador y la base de datos cuando haces click en “guardar”, esta guía es para ti. Empezamos con un servidor Debian 12 recién instalado y terminamos con una pila nginx + PHP-FPM + MariaDB + Redis + Let’s Encrypt lista para producción, con hardening de SSH, fail2ban, firewall UFW y backups automatizados. No es un tutorial de 5 minutos — es la guía completa que documenta cada paso y cada decisión, para que cuando algo se rompa en producción entiendas qué pasa.
Si ya corres Kubernetes o usas managed services, esto te parece overkill. Si quieres entender tu infraestructura y tener control total de cada pieza, es exactamente lo que necesitas.
TL;DR — Lo que vas a tener al final
- Tiempo de setup: 4-6 horas para llegar al primer sitio funcional, 1-2 horas para re-deploys con el script de bootstrap.
- Requisitos: VPS o servidor físico Debian 12 con 1+ GB RAM, 20+ GB SSD, IP pública estática.
- Resultado: nginx 1.26 + PHP-FPM 8.3 + MariaDB 11 + Redis 7 + Let’s Encrypt, con SSH hardening + fail2ban + UFW.
- Troubleshooting típico: SELinux bloqueando nginx (Debian no), permisos en /var/www, fail2ban que se come tu IP, certificados que no renuevan. Todos diagnosticables con `journalctl -xe` + `nginx -T`.
Por qué importa montar tu propio servidor web en 2026
El cloud es cómodo, pero no es gratis ni soberano. En 2026, levantar un servidor web Linux propio sigue siendo la mejor forma de aprender cómo funciona realmente la web, controlar tus datos y ahorrarte la factura mensual de un VPS. Un stack mínimo con Nginx, PHP-FPM y MariaDB sobre Debian 12 o Ubuntu 24.04 LTS corre en un equipo de 1 GB de RAM, sirve tráfico real y te deja ver cada byte que entra y sale.
Esta guía va de eso: del cable de red al sitio respondiendo HTTPS en tu dominio. Sin Docker, sin Kubernetes, sin panel propietario. Solo el sistema operativo, los paquetes oficiales y los archivos de configuración que de verdad entiende un sysadmin.
Qué vas a montar exactamente
El objetivo es un stack LEMP clásico pero endurecido para producción: Nginx como reverse proxy y servidor web, PHP-FPM para el contenido dinámico, MariaDB como motor de base de datos, Certbot para los certificados TLS de Let’s Encrypt y un firewall UFW que solo expone los puertos 22, 80 y 443. Todo bajo el usuario www-data y con systemd administrando los servicios.
| Componente | Rol | Puerto |
|---|---|---|
| Nginx | Reverse proxy + servidor HTTP/HTTPS | 80, 443 |
| PHP-FPM | Procesa PHP (WordPress, Laravel, apps a medida) | unix socket |
| MariaDB | Base de datos relacional | 3306 (solo localhost) |
| Certbot | Certificados TLS automáticos | n/a |
| UFW | Firewall por defecto-deny | n/a |
| Fail2ban | Bloquea fuerza bruta contra SSH | n/a |
Funciona con Debian 12 (bookworm), Debian 13 (trixie) y Ubuntu 22.04/24.04 LTS. En Rocky o RHEL los nombres de paquetes cambian, pero la arquitectura es la misma.
Preparar el sistema base
Antes de instalar nada, el servidor tiene que estar actualizado y tener un hostname y zona horaria coherentes. Saltarse esto genera logs con timestamps rotos, certificados emitidos con el FQDN equivocado y warnings en cada reinicio de logrotate.
Empezamos por actualizar el índice de paquetes y aplicar todos los parches pendientes. Esto baja el árbol de dependencias a una versión conocida y evita el clásico “funciona en mi máquina” entre tu laptop y el server.
sudo apt update && sudo apt -y upgrade
sudo hostnamectl set-hostname web01.example.com
sudo timedatectl set-timezone America/Montreal
Si tu proveedor de cloud te da una IP pública dinámica, instala ddclient o el cliente de tu DNS dinámico. Para Hetzner, OVH y DigitalOcean lo más simple es apuntar un subdominio a la IP fija del servidor y olvidarse del drama del DNS dinámico.
Crear un usuario con sudo y desactivar el login root por SSH es obligatorio antes de exponer el servidor a internet. Los bots empiezan a probar contraseñas de root en los primeros 60 segundos de tener IP pública.
sudo adduser deploy
sudo usermod -aG sudo deploy
sudo cp -r ~/.ssh /home/deploy/.ssh
sudo chown -R deploy:deploy /home/deploy/.ssh
sudo chmod 700 /home/deploy/.ssh && sudo chmod 600 /home/deploy/.ssh/*
Edita /etc/ssh/sshd_config y cambia PermitRootLogin yes a PermitRootLogin no, luego sudo systemctl restart ssh. Si te equivocas, la sesión actual sigue viva hasta que salgas; verifica con una segunda conexión antes de cerrar la primera.
Firewall: cerrar todo antes de abrir lo justo
Un servidor recién provisionado en un cloud público tiene todos los puertos accesibles. UFW resuelve eso con tres comandos. La política por defecto es DENEGAR todo entrante y permitir todo saliente; después solo abrimos SSH, HTTP y HTTPS.
sudo apt install -y ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose
El último comando debería listar las tres reglas y Status: active. Si dice inactive es porque el ufw enable falló silenciosamente; revisa journalctl -u ufw. Si ufw entra en conflicto con reglas de iptables preexistentes del proveedor, usa sudo iptables -L para ver qué quedó antes de activar.
Una vez que UFW está activo, fail2ban protege SSH contra los bots de fuerza bruta. La instalación es directa, la jail por defecto para SSH ya viene activa en Debian y Ubuntu modernos.
sudo apt install -y fail2ban
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd
El último comando debería mostrar al menos un filtro activo con las IPs baneadas. Si no aparece nada después de una hora, es que nadie te ha intentado entrar todavía (raro pero posible si tu IP es muy nueva).
Instalar Nginx, PHP-FPM y MariaDB
Llegamos al stack. Los tres paquetes están en los repos oficiales, no hay que añadir PPA ni compilar nada. La opción -y asume que ya revisaste qué se va a instalar con un apt list previo, lo cual es buena práctica pero la mayoría de los admins lo saltan.
sudo apt install -y nginx mariadb-server php-fpm php-mysql php-cli php-curl php-xml php-mbstring php-zip
sudo systemctl enable --now nginx mariadb php8.2-fpm
El nombre exacto del servicio PHP-FPM depende de la versión: php8.2-fpm en Ubuntu 24.04 y Debian 12, php8.3-fpm en Debian 13 y Ubuntu 24.10. Verifica con systemctl list-units --type=service | grep fpm qué unidad quedó disponible. Si la activas manualmente, asegúrate de que el socket de Unix correspondiente apunte a /run/php/, que es lo que Nginx espera por defecto en Debian y Ubuntu.
MariaDB trae un script de hardenizado que desactiva el login remoto como root, borra usuarios anónimos y la base de datos de prueba. Es seguro correrlo en cualquier entorno.
sudo mysql_secure_installation
El prompt pide contraseña root (vacía al instalar por primera vez), después te pregunta si quieres cambiar a unix_socket como método de auth. Decí que sí, así sudo mysql funciona sin contraseña pero nadie puede entrar remotamente como root. El resto de las preguntas (borrar usuarios anónimos, base de prueba, recargar privilegios) responde que sí a todas.
Configurar Nginx y PHP-FPM juntos
El server block por defecto de Nginx apunta a /var/www/html y solo sirve HTML estático. Para que PHP funcione hay que añadir el bloque location ~ .php$ que reenvía la request a FPM via socket Unix. El archivo /etc/nginx/sites-available/default se reemplaza entero o se crea uno nuevo en /etc/nginx/sites-available/tu-dominio.com.
sudo nano /etc/nginx/sites-available/tu-dominio.com
El contenido mínimo funcional es este. La directiva server_name tiene que coincidir con el dominio que apunta a tu IP, y root con la carpeta donde vivirá tu sitio. PHP-FPM escucha en /run/php/php8.2-fpm.sock por defecto, que es lo que pasamos a fastcgi_pass.
server {
listen 80;
listen [::]:80;
server_name tu-dominio.com www.tu-dominio.com;
root /var/www/tu-dominio.com;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ .php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
}
location ~ /.ht {
deny all;
}
}
El bloque location ~ /.ht bloquea el acceso a archivos .htaccess y .htpasswd de Apache, que en Nginx no se usan pero conviene denegar explícitamente. La línea try_files es la que hace que WordPress y Laravel funcionen sin rewrite rules adicionales.
Activamos el sitio con un symlink a sites-enabled y verificamos la sintaxis antes de recargar Nginx. Si nginx -t falla, el servicio no se reinicia, así que siempre es seguro probarlo.
sudo ln -s /etc/nginx/sites-available/tu-dominio.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Si nginx -t dice test failed, casi siempre es por una llave } faltante o un ; que se te olvidó. Nginx te indica la línea exacta. Un error común es olvidar cerrar el bloque server con } cuando copias plantillas de internet; el mensaje es unexpected end of file.
HTTPS con Let’s Encrypt en cinco minutos
Certbot solicita, valida y renueva los certificados TLS automáticamente. El plugin nginx modifica los server blocks para redirigir HTTP a HTTPS y carga los certificados en la ruta correcta. Antes de correrlo, asegúrate de que tu dominio ya resuelve a la IP del servidor (consulta con dig +short tu-dominio.com).
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d tu-dominio.com -d www.tu-dominio.com
Certbot te pregunta email (para avisos de renovación), acepta términos y pregunta si quieres redirigir HTTP a HTTPS. La opción 2 (redirect) es la correcta: cualquier request a http:// termina en un 301 hacia https://. El certificado queda en /etc/letsencrypt/live/tu-dominio.com/ y se renueva automáticamente vía systemd timer.
verifica que el timer está activo. Si no lo está, los certificados vencen a los 90 días y tu sitio empieza a devolver 526 Invalid SSL Certificate sin aviso.
sudo systemctl status certbot.timer
sudo certbot renew --dry-run
El --dry-run simula la renovación sin tocar el certificado real. Si devuelve Congratulations, all simulated renewals succeeded, el timer está bien configurado y la próxima renovación será automática. Si falla, casi siempre es porque el puerto 80 está bloqueado por un firewall distinto a UFW (Cloudflare proxy en medio, NAT del proveedor, etc.).
Proteger el servidor antes de abrirlo al público
Con HTTPS funcionando, lo que queda es blindar la superficie de ataque. Los seis ajustes siguientes cubren el 90% de los intentos de intrusión automatizados que vas a recibir en las primeras 24 horas de tener la IP expuesta.
Primero, deshabilitar el login root por SSH (ya lo hiciste arriba) y desactivar la autenticación por contraseña. Las llaves SSH son la única vía razonable en 2026; las contraseñas caen ante cualquier fuerza bruta con diccionario.
sudo sed -i 's/^#?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo systemctl restart ssh
Segundo, ocultar la versión de Nginx y MariaDB en los headers HTTP. No es seguridad real, pero reduce el ruido de bots que buscan CVEs específicos. Editá /etc/nginx/nginx.conf y poné server_tokens off dentro del bloque http. Para MariaDB, en /etc/mysql/mariadb.conf.d/50-server.cnf ajustá bind-address = 127.0.0.1 para que solo escuche en localhost.
Tercero, configurar un usuario de MariaDB dedicado para tu aplicación con privilegios limitados. Nunca uses root desde PHP. La app solo necesita SELECT, INSERT, UPDATE, DELETE sobre su base de datos.
sudo mysql -e "CREATE DATABASE app CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'app'@'localhost' IDENTIFIED BY 'contraseña-larga-aleatoria'; GRANT ALL ON app.* TO 'app'@'localhost'; FLUSH PRIVILEGES;"
Cuarto, configurar backups automáticos con mariadb-dump y tar sobre /var/www. El cron mínimo corre cada noche, retiene 7 días y sube a un destino off-site (otro servidor, B2, S3, lo que tengas).
sudo crontab -e
Y agregá la línea siguiente, que ejecuta el backup a las 3:17 AM y lo sube con rsync a un server de backup:
17 3 * * * /usr/local/bin/backup-web.sh >> /var/log/backup-web.log 2>&1
El script backup-web.sh lo escribís tú: un dump de MariaDB, un tar czf de /var/www y un /etc, después rsync -az al destino. Es 30 líneas de bash. Invertir una hora en escribirlo te salva de un ransomware, un rm -rf accidental o un proveedor que cierra sin aviso.
Verificar que todo está respondiendo bien
Antes de declarar el servidor en producción, una verificación end-to-end. El comando curl con -I (HEAD) te dice qué status code responde y qué headers HTTP está sirviendo. Un sitio bien configurado devuelve 200 OK, strict-transport-security activo y un server que no revela la versión exacta.
curl -I https://tu-dominio.com
El header strict-transport-security (HSTS) lo agrega Let’s Encrypt automáticamente con un max-age de 90 días, suficiente para que el navegador siempre use HTTPS después de la primera visita. Si no aparece, revisa que el plugin nginx de Certbot haya modificado el server block.
Para confirmar que PHP está procesando requests correctamente, creá un archivo temporal en /var/www/tu-dominio.com/info.php con <?php phpinfo();, visitá https://tu-dominio.com/info.php y borrá el archivo inmediatamente. Dejarlo expuesto en producción filtra información de versión y rutas internas que un atacante puede usar.
echo "<?php phpinfo();" | sudo tee /var/www/tu-dominio.com/info.php
curl -s https://tu-dominio.com/info.php | head -20
sudo rm /var/www/tu-dominio.com/info.php
Por último, un test de carga con wrk o hey para ver cuántos requests por segundo aguanta tu configuración. En hardware modesto (1 vCPU, 1 GB RAM) deberías ver entre 2000 y 5000 req/s sirviendo contenido estático, y entre 200 y 500 req/s con PHP simple. Si los números están muy por debajo, casi siempre es php-fpm con pm.max_children mal dimensionado.
Cuándo NO hacerlo así
Esta guía está pensada para un sitio personal, un blog WordPress de tráfico bajo-medio, una app Laravel chica o un homelab. Si esperas más de 50 requests concurrentes sostenidos, o más de 50 GB de transferencia mensual, el cuello de botella va a aparecer en PHP-FPM y MariaDB antes que en Nginx.
Para escalar horizontalmente necesitas un load balancer (HAProxy, Nginx upstream, Cloudflare), múltiples servidores PHP-FPM detrás y MariaDB con réplica de lectura. Para WordPress con tráfico alto, considera un plugin de object cache (Redis o Memcached) y un CDN que sirva los assets estáticos desde el edge. Todo eso queda fuera de una guía de “montar desde cero” pero es la dirección correcta cuando el sitio empieza a doler.
Si lo que quieres es probar sin pagar nada, hay tres opciones razonables. Primero, una Raspberry Pi 4 o 5 vieja que tengas en un cajón, con la red de tu casa y un dominio dinámico. Segundo, el free tier de Oracle Cloud (4 OCPUs, 24 GB RAM, de verdad gratis para siempre). Tercero, un VPS de 4 USD/mes en Hetzner o Racknerd, suficiente para aprender todo el stack con tráfico real. Cualquiera de los tres es mejor ambiente de práctica que un contenedor Docker descartable.
Resumen
El servidor mínimo para servir HTTPS en 2026 son seis paquetes oficiales: nginx, mariadb-server, php-fpm, certbot, ufw y fail2ban. Lo que los conecta es un server block de Nginx bien escrito, un firewall que solo expone los puertos correctos, un certificado TLS auto-renovable y un cron de backup que sí ejecutas.
Una vez que esto está andando, todo lo demás son capas: object cache, CDN, WAF, monitoring, log centralizado. Pero la base es la misma desde hace quince años. Un sysadmin que entiende estos seis componentes entiende cualquier stack que se le cruce.
Preguntas frecuentes
¿Por qué Servidor Web Linux desde Cero 2026: Guía Definitiva es relevante para sysadmins Linux?
Forma parte del flujo diario de administración. Los conceptos explicados se aplican directamente a servidores en producción y resuelven problemas reales que enfrentan sysadmins hispanohablantes.
¿Qué necesito para empezar con Servidor Web Linux desde Cero?
Distribución Linux actualizada (Debian, Ubuntu, RHEL, Fedora o Arch), acceso root, y conexión a internet. Para producción: entorno de staging separado antes de aplicar cambios.
¿Cuáles son los errores más comunes con Servidor Web Linux desde Cero?
Los tres más frecuentes: (1) saltar la lectura de logs, (2) aplicar cambios directos en producción sin probar, (3) olvidar permisos y ownership. La documentación y un plan de rollback siempre.
¿Cuándo usar Servidor Web Linux desde Cero y cuándo una alternativa?
Esta solución es ideal para el caso descrito. Alternativas como Docker, Ansible o configuración manual pueden ser mejores según contexto: portabilidad, escala y reproducibilidad son los factores clave.
¿Cómo migro desde una configuración previa a Servidor Web Linux desde Cero?
Documentar el estado actual, probar la nueva configuración en staging, validar funcionalmente, y aplicar en producción con rollback plan listo. Los posts de SoyLinux incluyen troubleshooting para los problemas más comunes.
Lee también
- Guía Fedora 44: Novedades, Instalación y Upgrade para Sysadmins Linux
- Configura VLANs en Linux con nmcli: Guía paso a paso para sysadmins
- Protege tu SSH: Guía esencial para configurar Fail2ban en Linux
- Guía Definitiva: Configura Kubernetes con kubeadm en Ubuntu para Sysadmins Linux
- Configura Prometheus y Grafana: Guía DevOps para SysAdmins Linux
