Problema
En entornos de auto‑hosting es frecuente combinar wg‑easy (interfaz web para WireGuard) con Caddy como terminador TLS y reverse proxy. El objetivo es acceder a https://wg-easy.example.com sin exponer puertos internos. Un síntoma típico es el mensaje del navegador “Can’t connect to the server” o un error de conexión TCP. El problema no es exclusivo de wg‑easy; ocurre siempre que el proxy no logra resolver o alcanzar el contenedor objetivo dentro de la red Docker. La raíz suele estar en la configuración de la red Docker, la definición del reverse_proxy en el Caddyfile o en la exposición de puertos del servicio wg‑easy.
Causa
- Red Docker aislada – Si Caddy y wg‑easy no comparten la misma red, el nombre del contenedor (
wg-easy) no se resuelve y Caddy devuelve una falla de conexión. - Puertos internos no expuestos – wg‑easy escucha por defecto en el puerto 80 dentro del contenedor. Si el
docker-compose.ymldel servicio definePORT=80pero no publica ese puerto (solo el UDP de WireGuard), Caddy necesita acceso interno, no externo; sin embargo, la directivaports:debe incluir el puerto TCP si el contenedor no está en la misma red. - Caddyfile mal estructurado – La sintaxis
reverse_proxy wg-easy:80requiere que el host sea resolvible dentro de la red de Caddy. Duplicar bloques, mezclar{}ytls internalsin separar correctamente puede generar que Caddy ignore la regla o caiga en un fallback que no sirve. - TLS interno mal configurado – Usar
tls internalsin habilitar el internal CA de Caddy o sin montar el directorio de datos (/data) puede provocar que Caddy no genere certificados y cierre la conexión antes de intentar el proxy. - Conflicto de puertos en el host – Publicar
80:80o443:443en varios contenedores genera colisiones y hace que el tráfico nunca llegue a Caddy.
Solución
1. Unificar la red Docker
Crea una red externa (por ejemplo caddy) y asegúrate de que ambos servicios la usen. En el docker-compose.yml de wg‑easy:
networks:
caddy:
external: true
En el docker-compose.yml de Caddy:
networks:
caddy:
name: caddy
Con esto, Caddy podrá resolver wg-easy mediante DNS interno de Docker.
2. Publicar solo los puertos necesarios
wg‑easy no necesita publicar su puerto HTTP al host; basta con que escuche internamente. Elimina cualquier línea - "80:80" bajo ports: del servicio wg‑easy. Mantén únicamente el UDP de WireGuard:
ports:
- "51820:51820/udp"
3. Simplificar el Caddyfile
Un bloque limpio evita confusiones. Usa una única definición para el dominio y coloca la directiva reverse_proxy dentro del mismo bloque:
{
email mymail@changed.com
# Opcional: habilitar el CA interno
# acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
}
wg-easy.example.com {
reverse_proxy wg-easy:80
tls internal
}
El bloque global {} define el correo y, si se desea, el CA. El bloque del dominio contiene solo reverse_proxy y tls internal. No es necesario envolverlo en otro par de llaves.
4. Montar los volúmenes de datos de Caddy
Para que tls internal funcione, Caddy necesita un directorio persistente donde guardar la autoridad certificadora. En el docker-compose.yml de Caddy:
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
Y declara los volúmenes al final del archivo:
volumes:
caddy_data:
caddy_config:
5. Reiniciar y validar la red
Después de ajustar los docker-compose.yml y el Caddyfile, ejecuta:
docker compose down
docker network create caddy # solo si la red no existía
docker compose up -d
Esto recrea los contenedores con la nueva red y elimina posibles residuos de configuraciones anteriores.
Cuándo aplicar esta solución
- Síntomas: Navegador muestra “Can’t connect to the server”,
curl -v https://wg-easy.example.comtermina en Connection refused o TLS handshake failure. - Entorno: Docker Compose con múltiples servicios, Caddy como único front‑end TLS, wg‑easy como backend sin exposición directa.
- No aplica: Si se usa un balanceador externo (Traefik, Nginx) o si wg‑easy se ejecuta en modo host network y ya está accesible directamente en el puerto 80 del host.
Código
# docker-compose.yml (wg-easy)
services:
wg-easy:
image: ghcr.io/wg-easy/wg-easy:15
container_name: wg-easy
environment:
- PORT=80
volumes:
- etc_wireguard:/etc/wireguard
- /lib/modules:/lib/modules:ro
ports:
- "51820:51820/udp"
restart: unless-stopped
cap_add:
- NET_ADMIN
- SYS_MODULE
sysctls:
- net.ipv4.ip_forward=1
- net.ipv4.conf.all.src_valid_mark=1
- net.ipv6.conf.all.disable_ipv6=0
- net.ipv6.conf.all.forwarding=1
- net.ipv6.conf.default.forwarding=1
networks:
- caddy
networks:
caddy:
external: true
volumes:
etc_wireguard:
# docker-compose.yml (caddy)
services:
caddy:
image: caddy:2.10.0-alpine
container_name: caddy
ports:
- "80:80"
- "443:443"
- "443:443/udp"
restart: unless-stopped
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
networks:
- caddy
networks:
caddy:
name: caddy
volumes:
caddy_data:
caddy_config:
# Caddyfile
{
email mymail@changed.com
}
wg-easy.example.com {
reverse_proxy wg-easy:80
tls internal
}
Verificación
-
Comprobar resolución DNS interna
docker exec caddy ping -c 3 wg-easyDebería responder con la IP 10.42.42.42 (o la asignada).
-
Probar el proxy con curl dentro del contenedor Caddy
docker exec caddy curl -I http://wg-easy:80Debería devolver
200 OKy el encabezadoServer: Caddy. -
Acceder desde el navegador
Visitahttps://wg-easy.example.com. El certificado será emitido por la CA interna y la página de wg‑easy debe cargarse sin errores. -
Revisar logs
docker logs caddy --followBusca líneas que indiquen
reverse_proxyexitoso y ausencia dedial tcp ...: connect: connection refused.
Notas adicionales
- Persistencia de datos de wg‑easy: El volumen
etc_wireguarddebe estar fuera del contenedor para que las claves y configuraciones sobrevivan a reinicios. - Firewall del host: Asegúrate de que los puertos 80 y 443 estén abiertos en el firewall del servidor; Caddy solo necesita escuchar en ellos.
- Modo “internal” vs Let’s Encrypt:
tls internales útil en entornos domésticos sin dominio público. Si el dominio es accesible desde internet, reemplazatls internalportls you@example.compara obtener certificados de Let’s Encrypt. - Depuración de red Docker:
docker network inspect caddymuestra los contenedores conectados y sus IPs; útil para validar que ambos están en la misma subred. - Actualizaciones: Cuando actualices la imagen de wg‑easy, verifica que el puerto interno siga siendo 80; algunas versiones permiten cambiarlo mediante la variable
PORT. Si lo cambias, actualiza también elreverse_proxyen el Caddyfile.