01. La Paradoja del Homelab en VPS
Tener un Servidor Privado Virtual (VPS) operando de forma ininterrumpida es la piedra angular de cualquier infraestructura de self-hosting moderna: ancho de banda simétrico no estrangulado por ISPs domésticos, disponibilidad 24/7 y una dirección IP fija o gestionada. Sin embargo, en el mismo instante en que se despliega el primer contenedor de monitoreo, un gestor de descargas o una base de datos, surge una disyuntiva de seguridad de primer orden:
La solución intuitiva (y peligrosa) adoptada por administradores con poca experiencia consiste en publicar puertos arbitrarios en el firewall: 3001 para Uptime Kuma, 8091 para Beszel, 8384 para Syncthing, 9000 para Portainer o 8080 para microservicios internos. Esta aproximación convierte al servidor en un blanco trivial:
- Indexación y Catalogación en minutos: Motores de búsqueda de infraestructura (Shodan, Censys, Project Sonar) barren el espacio IPv4 completo diariamente. Sus spiders catalogan títulos HTML, versiones exactas de bibliotecas HTTP y certificados TLS autofirmados.
- Superficie de Ataque Polimórfica: Cada contenedor web incorpora su propio runtime (Node.js/Express, Python/FastAPI, Go HTTP, Rust/Actix). Un desbordamiento de búfer, inyección SQL o escape de contenedor en cualquiera de ellos compromete el servidor anfitrión.
- Saturación de Logs por Ataques de Diccionario: Bots de fuerza bruta prueban credenciales por defecto (
admin/admin,root/root) sin descanso, degradando el ancho de banda y llenando gigabytes de logs con ruido inútil.
Para erradicar este vector sin sacrificar accesibilidad, la arquitectura de mi infraestructura se rige por un dogma irrompible: Zero Exposed Management Ports. Todo el tráfico de control se encapsula en una red de malla virtual (overlay mesh) gobernada por el protocolo criptográfico WireGuard.
02. WireGuard & Handshake Criptográfico Noise IK
A diferencia de suites heredadas como OpenVPN o IPsec (que arrastran más de 100.000 líneas de código en espacio de usuario, complejas jerarquías PKI con certificados X.509 y lentas transiciones de contexto), WireGuard fue concebido desde sus cimientos para operar en el espacio del kernel de Linux (wireguard.ko), ocupando menos de 4.000 líneas de código auditables.
El motor de WireGuard implementa el marco criptográfico Noise Protocol Framework, específicamente el patrón Noise_IKpsk2:
| Primitiva | Algoritmo Empleado | Función en el Túnel |
|---|---|---|
| Intercambio de Claves | Curve25519 (ECDH) | Diffie-Hellman elíptico de 256 bits para derivar secretos de sesión en 1 solo Round-Trip (1-RTT). |
| Cifrado Simétrico | ChaCha20 | Cifrado de flujo de alta velocidad insensible a ataques de temporización por cache-timing. |
| Autenticación (MAC) | Poly1305 | Código de autenticación de mensajes de un solo uso para verificar integridad estricta. |
| Función Hash & KDF | BLAKE2s | Derivación de claves de transporte y cálculo de tokens de mitigación anti-DoS. |
| Protección Anti-Replay | Sliding Window (64-bit) | Contador de paquetes con ventana deslizante para neutralizar ataques de repetición. |
El Principio de "Silencio de Red" (Stealth Protocol)
Uno de los rasgos más formidables de WireGuard es su comportamiento ante sondas externas. Cuando un escáner como Nmap envía paquetes UDP arbitrarios al puerto del VPS:
- El kernel calcula la firma criptográfica
MAC1utilizando la clave pública registrada en la interfazwg0. - Si el paquete carece de una firma válida o la clave del peer no está en el listado de
AllowedIPs, el kernel simplemente destruye el paquete en silencio. - No se emite ninguna respuesta
ICMP Port Unreachableni paquetesTCP RST.
03. Topología Visual Hub-and-Spoke & Flujo de Paquetes
La arquitectura de red utiliza un esquema Hub-and-Spoke segmentado. El nodo VPS actúa como concentrador central (Hub), mientras que las estaciones de trabajo, dispositivos móviles y pantallas táctiles operan como nodos satélite (Spokes).
04. Docker & la Trampa del 0.0.0.0 (Netfilter / iptables bypass)
Este es uno de los fallos de configuración más extendidos en la comunidad de self-hosting. Muchos usuarios configuran reglas en ufw asumiendo que su servidor está protegido con una política por defecto de denegación:
# El usuario ejecuta:
$ sudo ufw default deny incoming
$ sudo ufw allow 22/tcp
$ sudo ufw allow 80/tcp
$ sudo ufw allow 443/tcp
$ sudo ufw enable
# ...pero luego en su docker-compose.yml escribe:
ports:
- "3001:3001" # <--- ¡BRECHA CRÍTICA! Bindea en 0.0.0.0
PREROUTING y DOCKER de iptables. Las reglas de Docker se evalúan antes de que el paquete alcance la cadena INPUT gestionada por UFW. Como consecuencia, el puerto 3001 queda expuesto en la IP pública del VPS, anulando por completo la directiva deny incoming de UFW.
La Solución: Hardened Binding Explícito
Para impedir que Docker inyecte reglas públicas en PREROUTING, forzamos siempre la dirección IP de enlace (binding IP) en cada servicio:
- Binding a Loopback (
127.0.0.1): El puerto solo es alcanzable por procesos del host (como Nginx en modo reverse proxy local). - Binding a WireGuard (
10.66.66.1): El puerto únicamente procesa paquetes que ya han sido autenticados y desencriptados por la interfaz virtual del kernel.
# Configuración segura: ningun servicio escucha en 0.0.0.0
services:
uptime-kuma:
image: louislam/uptime-kuma:1
restart: unless-stopped
ports:
# Solo accesible mediante el proxy local Nginx
- "127.0.0.1:3001:3001"
volumes:
- ./kuma-data:/app/data
searxng-proxy:
image: caddy:latest
restart: unless-stopped
ports:
# Solo accesible para clientes autenticados en la VPN wg0
- "10.66.66.1:8444:8444"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
05. El Doble Nginx: Pasarelas Públicas vs. Pasarelas VPN
En este modelo de defensa en profundidad, el servidor web Nginx opera como un enrutador inteligente segmentado en dos zonas estrictamente estancas:
Zona A: El Reino Público (Edge & CDN Proxificado)
Servicios orientados al exterior como davito.es, davito.dev o status.davito.dev. Cuentan con:
- Proxy DNS inverso con mitigación DDoS y cifrado Cloudflare Full (Strict).
- Cabeceras HSTS con
max-age=63072000; includeSubDomains; preload. - Content Security Policy (CSP) que bloquea la inyección de recursos externos no autorizados.
- Compresión Brotli y Gzip para maximizar tiempos de respuesta.
Zona B: El Reino Privado (VPN Server Blocks en 10.66.66.1)
Herramientas como Stirling PDF, el panel de telemetría de hardware, IT-Tools o interfaces de administración. No poseen subdominio público en Cloudflare ni registro DNS público alguno. Sus bloques de servidor escuchan de manera exclusiva en la IP del túnel:
# Servidor Nginx accesible ÚNICAMENTE dentro de la VPN
server {
# Escucha estricta en el socket de la interfaz virtual wg0
listen 10.66.66.1:8181 ssl;
server_name 10.66.66.1;
ssl_certificate /etc/ssl/internal/fullchain.pem;
ssl_certificate_key /etc/ssl/internal/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
# Proxy pass al contenedor local en loopback
proxy_pass http://127.0.0.1:8181;
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 https;
# Soporte para WebSockets (dashboards interactivos)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Si un paquete TCP intenta conectar al puerto 8181 desde la IP pública del servidor, el socket del kernel responde con un RST o es descartado de inmediato sin permitir que Nginx procese ni un solo byte de la petición HTTP.
06. Mitigación Activa con CrowdSec IPS & nftables a Wirespeed
Incluso con los puertos de gestión totalmente blindados, los puertos web públicos (80 y 443) deben permanecer abiertos para servir las aplicaciones de producción. Un firewall estático es insuficiente frente a escaneos persistentes de vulnerabilidades (LFI, SQLi, rastreo de archivos .env o wp-login.php).
Para resolver esto, opera como centinela un agente de CrowdSec como Sistema de Prevención de Intrusiones (IPS):
- Ingesta de Telemetría Multi-Fuente: CrowdSec parsea en streaming los registros de
/var/log/nginx/access.log, los logs de autenticación de SSH (/var/log/auth.log) y las salidas de contenedores Docker. - Escenarios de Detección Heurística: Reglas como
crowdsecurity/http-crawl-non_statics,crowdsecurity/http-sensitive-filesycrowdsecurity/ssh-bfdetectan ataques distribuidos en milisegundos. - Remediación mediante nftables bouncer: Cuando se emite un baneo, el bouncer inserta la IP atacante en un conjunto dinámico del kernel (
nft set).
DROP) con una complejidad algorítmica de O(1) antes de consumir ciclos de CPU en el espacio de usuario.
07. Caso Práctico: Obsidian Vault & Syncthing BEP TLS 1.3
El banco de pruebas más crítico de esta topología es mi sistema de gestión de conocimiento personal y documentación técnica (Bóveda de Obsidian: La Cúpula). Este entorno alberga miles de archivos Markdown, esquemas de bases de datos, diagramas y notas de investigación sincronizados en tiempo real entre mi estación de trabajo, mi teléfono móvil y el VPS.
El protocolo subyacente para la sincronización es el Block Exchange Protocol (BEP) de Syncthing, configurado bajo directivas de máximo aislamiento:
<!-- Ajustes estrictos de sincronización privada en Syncthing -->
<options>
<globalAnnounceEnabled>false</globalAnnounceEnabled>
<localAnnounceEnabled>false</localAnnounceEnabled>
<relaysEnabled>false</relaysEnabled>
<natEnabled>false</natEnabled>
</options>
<device id="VPS-NODE-ID" name="VPS-Core">
<!-- Dirección estática forzada dentro del túnel wg0 -->
<address>tcp://10.66.66.1:22000</address>
</device>
Ventajas directas de este diseño:
- Cero filtraciones a servidores de descubrimiento: Al desactivar Global Discovery y Relaying, el identificador único del dispositivo nunca se anuncia en servidores relay públicos de terceros en Internet.
- Transferencias en Bloques Cifrados: BEP divide cada archivo en bloques de 128 KiB a 16 MiB calculando hashes SHA-256 de integridad. La transmisión viaja doblemente encapsulada (TLS 1.3 nativo de Syncthing dentro del túnel ChaCha20-Poly1305 de WireGuard).
- Sincronización Inmediata en Movilidad: Al editar una nota en el móvil mediante la app de Obsidian, el túnel WireGuard transmite los deltas al VPS en cuestión de 50 milisegundos con confirmación instantánea.
08. MTU Clamping (1420 Bytes) & Keepalive Tuning
Uno de los problemas más elusivos en el despliegue de VPNs móviles es la degradación de rendimiento provocada por la fragmentación de paquetes TCP/IP. Cuando un paquete supera la Unidad Máxima de Transferencia (MTU) de la red física subyacente, los routers intermedios se ven obligados a dividir el paquete en fragmentos, multiplicando la latencia o descartando paquetes si el bit Don't Fragment (DF) está activado.
Cálculo del Overhead de Encapsulamiento
En una conexión Ethernet estándar, el MTU base es de 1500 bytes. WireGuard añade las siguientes cabeceras a cada paquete encapsulado:
- Cabecera IPv4: 20 bytes (o 40 bytes en IPv6).
- Cabecera UDP: 8 bytes.
- Cabecera WireGuard: 32 bytes (Tipo de mensaje: 4B, Índice de receptor: 4B, Contador anti-replay: 8B, Autenticación Poly1305 MAC: 16B).
- Total de Overhead:
60 bytesen IPv4 /80 bytesen IPv6.
MTU_Túnel = MTU_Físico (1500) - Overhead_IPv6 (80) = 1420 bytes
Al fijar MTU = 1420 en la configuración de la interfaz virtual, se garantiza compatibilidad total con redes móviles 4G/5G (que habitualmente manejan MTUs de 1420 a 1440 por encapsulamientos GTP) y conexiones de fibra con PPPoE (MTU 1492), evitando fragmentación y retardos de reensamblado en el kernel.
Persistencia de Estado en CGNAT: PersistentKeepalive
Las operadoras de telefonía móvil sitúan a los smartphones tras cortafuegos CGNAT con tiempos de expiración de estado NAT sumamente agresivos (en ocasiones eliminando asociaciones UDP tras 30 segundos de inactividad). Para solventar esto y mantener el túnel siempre receptivo a eventos Push entrantes sin agotar la batería:
[Interface]
PrivateKey = a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v=
Address = 10.66.66.3/32
DNS = 10.66.66.1
MTU = 1420
[Peer]
PublicKey = z9y8x7w6v5u4t3s2r1q0p9o8n7m6l5k4j3h2g1f0e9d=
Endpoint = vpn.tu-dominio.com:51820
AllowedIPs = 10.66.66.0/24
# Envía un pulso mínimo cifrado cada 25 segundos para conservar el mapeo en CGNAT
PersistentKeepalive = 25
09. Protocolo de Auditoría: Verificación en Terminal
Para garantizar que ninguna actualización inadvertida de contenedores o reinicio del sistema haya desalineado los sockets, ejecuto un protocolo de validación rutinario mediante comandos nativos:
# 1. Comprobar todos los sockets TCP/UDP escuchando en interfaces públicas
# Salida esperada: ÚNICAMENTE SSH (puerto administrativo), HTTP (80) y HTTPS (443)
$ sudo ss -tulpn | grep LISTEN | grep -v '127.0.0.1\|10.66.66.1\|::1'
# 2. Inspeccionar el estado de la malla y últimos handshakes de peers
$ sudo wg show wg0
# peer: PC-Principal
# endpoint: 198.51.100.42:58210
# latest handshake: 14 seconds ago
# transfer: 2.14 GiB received, 8.42 GiB sent
# 3. Comprobar decisiones activas de baneo en el motor CrowdSec
$ sudo cscli decisions list
# +------+----------+-------------------+----------------------+--------+
# | ID | Source | Scope:Value | Reason | Action |
# +------+----------+-------------------+----------------------+--------+
# | 1402 | crowdsec | Ip:203.0.113.88 | http-sensitive-files | ban |
# +------+----------+-------------------+----------------------+--------+
10. Conclusiones y Reglas de Oro Zero-Trust
La seguridad integral en un entorno de homelab no proviene de una herramienta mágica aislada, sino de la defensa en profundidad (Defense in Depth) implementada a través de capas complementarias:
- Principio de Mínima Exposición: Si un servicio no necesita ser consumido por el público general, no tiene justificación técnica para escuchar en
0.0.0.0. - Cripto-Enrutamiento Determinista: WireGuard resuelve enrutamiento y autenticación de un plumazo mediante pares de claves estáticas, eliminando la complejidad y vulnerabilidad de las contraseñas débiles.
- Desacoplamiento Estricto en Docker: Forzar siempre
127.0.0.1:PUERTO:PUERTOo10.66.66.1:PUERTO:PUERTOpara neutralizar el bypass automático de iptables. - Mitigación Activa en el Kernel: Con CrowdSec y
nftables, los atacantes oportunistas son neutralizados a nivel de tarjeta de red sin consumir recursos de CPU en el servidor.