10 min read
Reverse proxy en el homelab: Caddy, TLS y cuando Pi-hole se interpone

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:

  1. Acceso a la red: Tailscale, WireGuard u OpenVPN
  2. Resolución de nombres: sslip.io como puente, MagicDNS, o Pi-hole
  3. Terminación TLS y enrutado HTTP ← aquí
  4. 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 proxyCon proxy
192.168.1.47:3000https://grafana.lan
Certificado por servicio (o ninguno)Un certificado en el borde
Recordar puertosRecordar 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ónRPSQué pagasQué ganas
Acceso directo por puertoPiedra mínimaSuperficie expuesta, sin TLS unificadoCero proxy
CaddyPiedra operativaAprender su DSL; acoplamiento al ecosistema CaddyTLS automático, config declarativa, bajo mantenimiento
nginxPiedra / papelMás config manual; Certbot aparteMáximo control, referencia universal
TraefikPiedra operativaEtiquetas Docker, modelo mental de routersDescubrimiento automático en stacks containerizados
Cloudflare TunnelPiedra operativaAncla en CloudflareSin 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 [email protected]
}

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:

ConsultaDesde internetDesde la LAN (Pi-hole)
app.midominio.comIP pública del routerIP 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.io192.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_on en 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ónTipoPor qué
Caddy con Caddyfile versionadoPuenteMigrar a nginx es trabajo, no rehacer la topología
Certificados Let’s Encrypt en CaddyPuenteEstándar; otro cliente ACME puede sustituirlo
Pi-hole como único resolver en la LANPiedra / anclaControl y fricción operativa a cambio de soberanía DNS
Mezclar sslip.io + Pi-hole + dominio propio sin mapaFalsa piedraTres fuentes de verdad, debugging infernal
Cloudflare Tunnel como único bordeAnclaTLS 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 Caddyfile expone 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.

Comentarios