Asegura OpenClaw con un reverse proxy Caddy + HTTPS
+En el Artículo 1 pusimos OpenClaw en
marcha — pero solo sobre HTTP plano, con el truco de allowInsecureAuth. Aquí
ponemos Caddy por delante como reverse proxy: HTTPS de
verdad, auth con emparejamiento de dispositivos y los puertos crudos del gateway
cerrados para que el proxy sea la única vía de entrada. Cada comando y error que
aparece abajo es del despliegue real.
Continúa desde el Artículo 1 en el mismo Instancia WEC — Ubuntu 22.04.5 LTS,
OpenClaw 2026.6.8. Usamos Caddy 2 (caddy:2). Como esta máquina no tiene
dominio público, usamos la CA interna de Caddy (tls internal) para HTTPS
autofirmado; la sección Pasar a producción muestra el cambio
de una línea a certificados reales de Let's Encrypt una vez que tengas un dominio.
Lo que vas a construir
Caddy termina el HTTPS y hace de reverse proxy hacia el gateway de OpenClaw por la
red interna de Docker. El gateway deja de publicar sus propios puertos — el único
punto de entrada pasa a ser Caddy en :443.
Requisitos previos
- Artículo 1 completado — OpenClaw corriendo en
~/openclaw - Acceso por shell a la VM
- Un hostname para el gateway. Con un dominio público, úsalo (y obtén certificados
reales de Let's Encrypt). En una máquina privada, usamos
openclaw.local+ una entrada en el archivo hosts.
Paso 1 — Confirmar el punto de partida
cd ~/openclaw
docker compose ps
Deberías ver openclaw-gateway Up (healthy), publicando 18789/18790/3978.
Ese es nuestro estado de partida: funcionando, pero solo HTTP.
Paso 2 — Escribir el Caddyfile
La config de Caddy: servir HTTPS y hacer proxy de todo hacia el gateway. tls internal usa la propia CA local de Caddy — no hace falta dominio público.
cat > Caddyfile <<'EOF'
# Caddy selects its certificate by SNI (the hostname in the TLS handshake).
# A bare IP sends no SNI, so we use a hostname. With a public domain, put your
# domain here and drop `tls internal` for automatic Let's Encrypt.
openclaw.local {
tls internal
reverse_proxy openclaw-gateway:18789
}
EOF
El destino del proxy es openclaw-gateway:18789 — Caddy alcanza el gateway por su
nombre de servicio de Compose a través de la red compartida de Docker.
Paso 3 — Añadir Caddy a Compose
docker compose fusiona automáticamente un docker-compose.override.yml, así que
añadimos un servicio caddy sin tocar el archivo compose oficial de OpenClaw:
cat > docker-compose.override.yml <<'EOF'
services:
caddy:
image: caddy:2
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
volumes:
caddy_data:
caddy_config:
EOF
caddy_data persiste la CA local + los certificados entre reinicios.
Paso 4 — Iniciar Caddy
docker compose up -d
docker compose ps
Caddy arranca junto al gateway con 443 publicado, y genera su CA interna + un
certificado en la primera ejecución.

docker compose up -d también inicia el helper de un solo uso openclaw-cli; que
quede en reposo aquí es inofensivo — lo invocamos con docker compose run --rm
cuando hace falta.
Paso 5 — Verificar HTTPS
Consulta el endpoint de salud del gateway a través de Caddy. --resolve hace
que curl envíe el hostname (SNI) mientras apunta a la máquina:
curl -k --resolve openclaw.local:443:127.0.0.1 https://openclaw.local/healthz
# {"ok":true,"status":"live"}
tlsv1 alert internal error?Eso pasa si usaste una IP pelada como dirección del sitio — mira Solución de problemas #1. Usa un hostname.
Paso 6 — Volver a asegurar el gateway
Ahora el tráfico llega por https://openclaw.local. Dos cambios de config:
- Añade el origen HTTPS a
allowedOrigins(la Control UI rechaza orígenes desconocidos). - Vuelve a desactivar
allowInsecureAuth— HTTPS es un contexto seguro, así que el truco del Artículo 1 ya no hace falta.
docker compose run --rm --no-deps --entrypoint node openclaw-gateway \
dist/index.js config set --batch-json '[{"path":"gateway.controlUi.allowedOrigins","value":["https://openclaw.local"]},{"path":"gateway.controlUi.allowInsecureAuth","value":false}]'
docker compose restart openclaw-gateway
Paso 7 — Emparejar tu navegador
Abre https://openclaw.local/ en tu navegador (en una máquina privada, primero
mapea 10.80.4.212 openclaw.local en el archivo hosts de tu máquina — /etc/hosts
en macOS/Linux, o C:\Windows\System32\drivers\etc\hosts editado como Administrador
en Windows).
Primero te toparás con una advertencia de certificado — "Tu conexión no es
privada" (NET::ERR_CERT_AUTHORITY_INVALID). Esto es esperado con tls internal:
el certificado está firmado por la CA local de Caddy, en la que tu sistema
operativo no confía. Haz clic en Avanzado → Continuar a openclaw.local (no
seguro) para seguir. (Con un dominio real + Let's Encrypt, el certificado tiene
confianza pública y esta advertencia nunca aparece — mira
Pasar a producción.)

Ahora la página carga — pero con allowInsecureAuth desactivado, el gateway exige
emparejamiento de dispositivo antes de que este navegador pueda usar la Control
UI. Ese es el flujo seguro haciendo su trabajo. La pantalla muestra un id de
solicitud; apruébalo en el host:
docker compose run --rm openclaw-cli devices approve <request-id-from-the-screen>

De vuelta en el navegador, haz clic en Connect otra vez — empareja y carga el dashboard.
Paso 8 — Cerrar el acceso directo (el endurecimiento de verdad)
El gateway todavía publica 18789 en el host, así que http://<vm-ip>:18789
sigue siendo alcanzable en HTTP plano, saltándose a Caddy. Ciérralo: deja de
publicar los puertos del gateway para que Caddy en :443 sea la única puerta. Caddy
sigue alcanzándolo por la red interna.
cat > docker-compose.override.yml <<'EOF'
services:
openclaw-gateway:
# Unpublish the gateway's host ports — Caddy reaches it internally, so the
# only entry point is HTTPS on :443.
ports: !reset []
caddy:
image: caddy:2
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
volumes:
caddy_data:
caddy_config:
EOF
docker compose up -d
ports: !reset [] limpia los puertos que publicaba el compose base.
Paso 9 — Comprobar que está cerrado
curl -s -m 5 http://<vm-ip>:18789/healthz ; echo " <- direct HTTP (should FAIL)"
curl -sk --resolve openclaw.local:443:127.0.0.1 https://openclaw.local/healthz ; echo " <- via Caddy (should work)"
El primero no devuelve nada (conexión rechazada) — la puerta insegura desapareció.
El segundo todavía devuelve {"ok":true,"status":"live"}.
Tu OpenClaw ahora es alcanzable solo por HTTPS, con dispositivo emparejado.

Solución de problemas (errores reales)
1. tlsv1 alert internal error
Aparece cuando la dirección del sitio en el Caddyfile era una IP pelada
(https://10.80.4.212):
curl: (35) error:0A000438:SSL routines::tlsv1 alert internal error
Los logs de Caddy mostraban que el certificado se obtuvo bien. Causa: TLS elige un certificado por SNI (el hostname en el handshake), pero las conexiones a una IP pelada no envían SNI — así que Caddy no puede emparejar un certificado y aborta. Solución: usa un hostname como dirección del sitio (Paso 2). Un lector con un dominio real nunca se topa con esto — el dominio es el SNI.
2. Emparejamiento de dispositivo requerido
En la primera conexión HTTPS tras desactivar allowInsecureAuth:
Device pairing required — This browser needs one-time approval from the Gateway
host before it can use the Control UI.
Causa: este es el flujo seguro correcto — con la auth insegura desactivada,
cada navegador nuevo debe ser aprobado. Solución: docker compose run --rm openclaw-cli devices approve <request-id>. (La CLI puede registrar scope upgrade pending approval … using local fallback pero igual completa la aprobación.)
3. NET::ERR_CERT_AUTHORITY_INVALID
Esperado con tls internal — el certificado está firmado por la CA local de Caddy,
en la que tu SO no confía. Haz clic para continuar en una máquina privada. Con un
dominio público + Let's Encrypt (abajo), el certificado tiene confianza y no hay
advertencia.
Pasar a producción
Con un dominio real, cambia dos líneas en el Caddyfile — apúntalo a tu dominio y
quita tls internal:
- openclaw.local {
- tls internal
+ openclaw.example.com {
reverse_proxy openclaw-gateway:18789
}
Luego apunta el registro A del DNS del dominio a la IP pública de la VM y abre
80 + 443 a internet. Caddy aprovisiona y renueva automáticamente un certificado
de Let's Encrypt — con confianza, sin advertencia, sin --resolve ni
/etc/hosts. Actualiza allowedOrigins a https://openclaw.example.com.
Notas de seguridad
- Punto de entrada único — solo el
:443de Caddy queda expuesto; el gateway no tiene puertos en el host (Paso 8). - Emparejamiento de dispositivo — cada navegador necesita aprobación de una sola
vez (
openclaw devices). - Lista de orígenes permitidos — la Control UI solo acepta
https://openclaw.local. - Rota los secretos — regenera
OPENCLAW_GATEWAY_TOKENsi alguna vez se mostró (capturas de pantalla, pantallas compartidas).
Qué sigue
Terminaste la Parte 2 de la serie Self-hosting OpenClaw. Sigue:
- Parte 3 — Añade un canal de Telegram (siguiente) — habla con tu agente, ya asegurado, desde el móvil.
- Parte 4 — Haz OpenClaw privado con una VPN mesh NetBird — ponlo en una mesh privada y cierra los puertos públicos.
Los archivos complementarios (Caddyfile, docker-compose.override.yml) viven en
el repo de manifiestos de WiLine (enlace pendiente con el repo).
Desmontaje
Para eliminar solo Caddy y reabrir los puertos directos, borra
docker-compose.override.yml y Caddyfile, luego docker compose up -d. Para
detener todo:
docker compose down
