En el post sobre sslip.io la capa de nombres quedó resuelta de forma mínima: hostnames que apuntan a una IP sin comprar dominio ni montar BIND. Pero un nombre no sirve de mucho si cada servicio sigue escuchando en su puerto (:3000, :8123, :9000) y el navegador sigue gritando por certificados autofirmados.
La siguiente capa es terminar TLS y enrutar HTTP: un reverse proxy que recibe en el 443, presenta un certificado válido y reparte el tráfico según el hostname. Caddy, nginx y Traefik hacen eso; este post se centra en Caddy porque encaja con el perfil del homelab personal (TLS automático, configuración legible, footprint modesto) y porque es donde aparecen las fricciones reales al mezclarlo con un resolver local como Pi-hole.
El hilo es el marco: qué problema resuelve el proxy, qué rompe cuando añades DNS interno, y cómo encaja en el principio RPS.
La pila hasta aquí
Recuerda el orden que venimos construyendo:
- Acceso a la red: Tailscale, WireGuard u OpenVPN
- Resolución de nombres: sslip.io como puente, MagicDNS, o Pi-hole
- Terminación TLS y enrutado HTTP ← aquí
- Identidad de aplicación: OIDC, forward auth; Zitadel y el espectro RPS
El proxy no sustituye la VPN ni el DNS. Asume que ya sabes llegar a la red y resolver el nombre. Su trabajo es que https://grafana.192-168-1-47.sslip.io llegue al contenedor correcto con un certificado que el navegador acepte.
Qué hace un reverse proxy (y qué no)
Un reverse proxy escucha en los puertos estándar (80 y 443) y reenvía el tráfico a servicios internos según reglas: hostname, path, headers. En el homelab eso traduce:
| Sin proxy | Con proxy |
|---|---|
192.168.1.47:3000 | https://grafana.lan |
| Certificado por servicio (o ninguno) | Un certificado en el borde |
| Recordar puertos | Recordar nombres |
Lo que no hace: autenticar usuarios de aplicación (eso es la capa 4) ni resolver nombres DNS (eso es la capa 2). Confundir capas lleva a montar OAuth en Caddy cuando el problema era solo enrutar, o a exponer servicios sin perímetro cuando aún no tienes VPN.
Caddy en el marco RPS
| Solución | RPS | Qué pagas | Qué ganas |
|---|---|---|---|
| Acceso directo por puerto | Piedra mínima | Superficie expuesta, sin TLS unificado | Cero proxy |
| Caddy | Piedra operativa | Aprender su DSL; acoplamiento al ecosistema Caddy | TLS automático, config declarativa, bajo mantenimiento |
| nginx | Piedra / papel | Más config manual; Certbot aparte | Máximo control, referencia universal |
| Traefik | Piedra operativa | Etiquetas Docker, modelo mental de routers | Descubrimiento automático en stacks containerizados |
| Cloudflare Tunnel | Piedra operativa | Ancla en Cloudflare | Sin abrir puertos; TLS gestionado fuera |
Caddy es piedra operativa en el sentido bueno: resuelve TLS + routing con poca ceremonia. El coste es una ancla blanda en el ecosistema (migrar a nginx no es imposible, pero reescribes configs) y la tentación de usar features propietarias de Caddy (forward_auth, módulos) sin evaluar reversibilidad.
Para un homelab de una persona que ya eligió simplicidad operativa en la capa VPN, Caddy es la continuación honesta.
TLS: HTTP-01, sslip.io y la LAN privada
En el post anterior vimos que Let’s Encrypt con HTTP-01 funciona con 203-0-113-7.sslip.io si la IP pública es alcanzable en el puerto 80. Caddy lo automatiza: declaras el hostname, Caddy obtiene y renueva el certificado.
Con IPs privadas (192.168.x.x) la cosa cambia. Let’s Encrypt no puede llegar a tu LAN desde internet. Opciones reales:
- Certificado autofirmado: rápido, el navegador protesta.
- CA interna: más trabajo, viable en red controlada.
- DNS-01 con wildcard: tijeras; sslip.io lo documenta, no es el camino por defecto.
- Estar dentro de la VPN/tailnet con TLS gestionado por Tailscale, ya cubierto en el post de acceso.
Caddy automatiza el baile cuando las condiciones se cumplen; la física de la red sigue ahí.
Subdominios con sslip.io
Un patrón habitual: grafana.192-168-1-47.sslip.io apunta a la misma IP que 192-168-1-47.sslip.io; Caddy enruta por hostname al backend correcto. Eso permite varios servicios detrás de un solo proxy sin Pi-hole ni dominio propio, mientras la IP no cambie y aceptes la estética del nombre.
Ejemplo mínimo de Caddyfile
Un bloque por hostname ilustra el patrón: TLS automático, backend en la LAN.
{
# Opcional: email para avisos de Let's Encrypt
email admin@example.com
}
grafana.192-168-1-47.sslip.io {
reverse_proxy 192.168.1.47:3000
}
homeassistant.192-168-1-47.sslip.io {
reverse_proxy 192.168.1.48:8123
}
Caddy escucha en 80 y 443, obtiene certificados para cada hostname y reenvía al puerto interno. Si la IP del proxy es 192.168.1.47 y los backends viven en otras máquinas, cambia las IPs de reverse_proxy: el hostname sslip.io sigue apuntando a la IP del proxy, no a la del backend.
Con IP privada y sin salida pública al 80, añade tls internal dentro del bloque para forzar certificado autofirmado de Caddy (útil en LAN, inútil para visitantes desde internet).
Pi-hole entra en escena
sslip.io resuelve nombres sin infraestructura propia. Pi-hole (con Unbound detrás) es el salto a DNS soberano en la LAN: registros arbitrarios (grafana.homelab, nas.lan), bloqueo de ads, control total del resolver.
El problema: mezclar sslip.io con Pi-hole sin criterio es falsa piedra. Dos resolvers, reglas contradictorias, y dolores de cabeza que solo aparecen cuando montas el proxy.
Split DNS
Split DNS significa que la misma pregunta DNS tiene respuesta distinta según quién pregunta:
| Consulta | Desde internet | Desde la LAN (Pi-hole) |
|---|---|---|
app.midominio.com | IP pública del router | IP interna del proxy (192.168.1.47) |
Sin split DNS, un dispositivo en casa puede resolver tu dominio a la IP pública, salir al router, hacer hairpin NAT y fallar, o ir por un camino lento e impredecible. Con split DNS, la LAN resuelve directo a la IP interna.
Caddy necesita que el cliente y el proxy estén de acuerdo en qué hostname usa cada servicio. Si Pi-hole devuelve una cosa y el resolver público otra, el proxy emite certificados para un nombre que desde dentro no resuelve igual.
Rebind protection
Pi-hole incluye protección contra DNS rebinding: bloquea respuestas que apuntan a IPs privadas para dominios públicos. Es una defensa legítima.
Pero sslip.io devuelve IPs privadas por diseño (192-168-1-47.sslip.io → 192.168.1.47). Pi-hole puede bloquear esas respuestas. Síntoma: el nombre resuelve fuera de casa y falla dentro, o al revés.
Solución: whitelist de dominios de confianza (sslip.io, nip.io) o desactivar rebind protection para esos casos, consciente del trade-off de seguridad.
Renovaciones ACME con Pi-hole
Cuando Caddy renueva un certificado con HTTP-01, Let’s Encrypt consulta DNS público y conecta a tu IP pública en el puerto 80. Si Pi-hole intercepta todas las consultas DNS (incluidas las de la propia máquina del proxy) y devuelve la IP interna para tu dominio, la renovación puede fallar: LE intenta llegar a 192.168.x.x desde internet.
Patrones que funcionan:
- Local DNS records en Pi-hole para el dominio público apuntando a la IP interna solo para clientes LAN, más regla de firewall que permita al proxy responder en el 80 desde fuera.
- DNS condicional: el proxy usa resolver upstream distinto al de los clientes para las consultas ACME.
- DNS-01 en lugar de HTTP-01 (más complejo, evita el problema del hairpin).
No hay una receta única; depende de si tienes IP pública, CG-NAT, o solo acceso por tailnet. Antes de culpar a Caddy, diagnostica con dig (@Pi-hole vs @8.8.8.8).
Orden de arranque y dependencias
Otra fricción silenciosa: Caddy arranca y quiere renovar certificados; Pi-hole aún no está listo; el proxy resuelve mal y entra en bucle de reintentos. O al revés: Pi-hole depende de un contenedor que Caddy aún no enruta.
Reglas prácticas:
- Pi-hole y el proxy en hosts estables (IP fija o DHCP reservation).
depends_onen Docker no garantiza que el servicio esté listo; healthchecks si la pila es frágil.- Documentar qué servicio debe arrancar primero en un apagado largo, no confiar en la memoria.
En el marco RPS: puentes y anclas
| Decisión | Tipo | Por qué |
|---|---|---|
Caddy con Caddyfile versionado | Puente | Migrar a nginx es trabajo, no rehacer la topología |
| Certificados Let’s Encrypt en Caddy | Puente | Estándar; otro cliente ACME puede sustituirlo |
| Pi-hole como único resolver en la LAN | Piedra / ancla | Control y fricción operativa a cambio de soberanía DNS |
| Mezclar sslip.io + Pi-hole + dominio propio sin mapa | Falsa piedra | Tres fuentes de verdad, debugging infernal |
| Cloudflare Tunnel como único borde | Ancla | TLS y routing delegados; difícil volver a self-hosted puro |
Conecta con soberanía digital: el proxy puede estar en tu mini PC y los certificados en Let’s Encrypt, pero si Pi-hole filtra mal una renovación, pierdes TLS y la cadena de confianza entera se resiente.
Cuándo usar qué
Caddy + sslip.io basta cuando:
- Estás probando la pila antes de comprar dominio.
- Tienes IP pública y pocos servicios.
- Accedes sobre todo desde la tailnet con TLS de Tailscale para lo crítico.
Añade Pi-hole (o Unbound) cuando:
- Necesitas docenas de nombres locales arbitrarios (
*.homelab). - Quieres bloqueo de ads en toda la LAN.
- Tienes dominio propio y necesitas split DNS serio.
Pasa a dominio propio + Caddy cuando:
- El servicio es público o semi-profesional.
- sslip.io ya te parece frágil o feo para compartir.
- Necesitas estabilidad de nombre independiente de la IP.
Limitaciones honestas
- Caddy no arregla CG-NAT. Sin IP pública alcanzable, HTTP-01 desde internet no funciona; necesitas tailnet, túnel, o DNS-01.
- Un proxy mal configurado es peor que ninguno. Superficie de ataque centralizada; un error en
Caddyfileexpone todo. - Pi-hole + proxy + sslip.io + dominio real puede coexistir, pero exige un mapa explícito de qué resolver responde qué para quién.
- Let’s Encrypt tiene límites de tasa. No pruebes renovaciones en bucle contra producción; usa staging o espera (cinco renovaciones fallidas por semana y empiezas a tener problemas).
Lo que viene después
Con nombre, certificado y enrutamiento HTTP resueltos, la pregunta siguiente es quién puede hacer login: Grafana, tu PWA, el panel de administración. Ahí entra la capa de identidad (OIDC, forward auth, Zitadel frente a Authelia frente a “auth básica y listo”) en el post 009.
El proxy termina TLS; el DNS nombra; la VPN te mete en la red. Sin un mapa de qué resolver responde qué, las tres se pisan. Cada capa en su sitio, anclas conscientes.