Asegurar OpenClaw con un proxy inverso Caddy + HTTPS
+En Artículo 1 hicimos funcionar OpenClaw —
pero solo a través de HTTP sin cifrar, con un workaround allowInsecureAuth. Aquí colocamos
Caddy frente a él como un proxy inverso: verdadero HTTPS,
autenticación emparejada por dispositivo, y los puertos del gateway cerrados para que el proxy sea la única
forma de acceso. Cada comando y error a continuación proviene de la implementación real.
Continúa desde el Artículo 1 en la misma instancia WEC — Ubuntu 22.04.5 LTS, OpenClaw
2026.6.8. Usamos Caddy 2 (caddy:2). Debido a que esta máquina no tiene un dominio público,
usamos la CA interna de Caddy (tls internal) para HTTPS autofirmado; la
sección Pasando 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 construirás
Caddy termina HTTPS y hace un proxy inverso al gateway de OpenClaw a través de la
red interna de Docker. El gateway deja de publicar sus propios puertos — el único punto de entrada
se convierte en Caddy en :443.
Requisitos previos
- Artículo 1 completado — OpenClaw funcionando en
~/openclaw - Acceso a la terminal de la VM
- Un nombre de host 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 hosts.
Paso 1 — Confirma el punto de partida
cd ~/openclaw
docker compose ps
Deberías ver openclaw-gateway Up (healthy), publicando 18789/18790/3978.
Ese es nuestro estado inicial: funcionando, pero solo HTTP.
Paso 2 — Escribe el Caddyfile
La configuración de Caddy: servir HTTPS y hacer proxy a todo al gateway. tls internal
usa la propia CA local de Caddy — no se necesita un dominio público.
cat > Caddyfile <<'EOF'
# Caddy selecciona su certificado por SNI (el nombre de host en el apretón de manos TLS).
# Una IP desnuda no envía SNI, así que usamos un nombre de host. Con un dominio público, coloca tu
# dominio aquí y elimina `tls internal` para Let's Encrypt automático.
openclaw.local {
tls internal
reverse_proxy openclaw-gateway:18789
}
EOF
El objetivo del proxy es openclaw-gateway:18789 — Caddy accede al gateway por su
nombre de servicio de Compose a través de la red compartida de Docker.
Paso 3 — Agrega Caddy a Compose
docker compose fusiona automáticamente un docker-compose.override.yml, así que agregamos un servicio caddy
sin tocar el archivo de 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 + certificados a través de reinicios.
Paso 4 — Inicia Caddy
docker compose up -d
docker compose ps
Caddy se inicia 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 ayudante de un solo uso openclaw-cli; su inactividad
aquí es inofensiva — lo invocamos con docker compose run --rm cuando es necesario.
Paso 5 — Verifica HTTPS
Accede al endpoint de salud del gateway a través de Caddy. --resolve hace que curl envíe
el nombre de host (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 sucede si usaste una IP desnuda como dirección del sitio — consulta Solución de problemas #1. Usa un nombre de host.
Paso 6 — Reasegura el gateway
Ahora el tráfico llega a través de https://openclaw.local. Dos cambios de configuración:
- Agrega el origen HTTPS a
allowedOrigins(la UI de Control rechaza orígenes desconocidos). - Desactiva
allowInsecureAuth— HTTPS es un contexto seguro, por lo que el workaround del Artículo 1 ya no es necesario.
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 — Empareja 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 recibirás 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, que tu sistema operativo no
confía. Haz clic en Avanzado → Proceder a openclaw.local (inseguro) para continuar. (Con un
dominio real + Let's Encrypt, el certificado es públicamente confiable y esta advertencia
nunca aparece — consulta Pasando a producción.)

Ahora la página se carga — pero con allowInsecureAuth desactivado, el gateway requiere
emparejamiento de dispositivo antes de que este navegador pueda usar la UI de Control. 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 Conectar nuevamente — se empareja y carga el panel.
Paso 8 — Cierra el acceso directo (el verdadero endurecimiento)
El gateway aún publica 18789 al host, por lo que http://<vm-ip>:18789 es
todavía accesible en HTTP sin cifrar, eludiendo a Caddy. Ciérralo: deja de publicar los
puertos del gateway para que Caddy en :443 sea la única puerta. Caddy aún puede acceder a él a través
de la red interna.
cat > docker-compose.override.yml <<'EOF'
services:
openclaw-gateway:
# Deja de publicar los puertos del host del gateway — Caddy accede a él internamente, así que el
# único punto de entrada es HTTPS en :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 el compose base publicó.
Paso 9 — Prueba que está bloqueado
curl -s -m 5 http://<vm-ip>:18789/healthz ; echo " <- HTTP directo (debería FALLAR)"
curl -sk --resolve openclaw.local:443:127.0.0.1 https://openclaw.local/healthz ; echo " <- a través de Caddy (debería funcionar)"
El primero no devuelve nada (conexión rechazada) — la puerta insegura ha desaparecido. El
segundo aún devuelve {"ok":true,"status":"live"}.
Tu OpenClaw ahora es accesible solo a través de HTTPS, emparejado por dispositivo.

Ahora puedes poner verdadero HTTPS y autenticación frente a cualquier servicio autoalojado — y probar que la puerta trasera está cerrada.
Solución de problemas (errores reales)
1. tlsv1 alert internal error
Ocurre cuando la dirección del sitio en el Caddyfile era una IP desnuda (https://10.80.4.212):
curl: (35) error:0A000438:SSL routines::tlsv1 alert internal error
Los registros de Caddy mostraron que el certificado se obtuvo correctamente. Causa: TLS selecciona un certificado por SNI (el nombre de host en el apretón de manos), pero las conexiones a una IP desnuda no envían SNI — así que Caddy no puede coincidir un certificado y aborta. Solución: usa una dirección de sitio con nombre de host (Paso 2). Un lector con un dominio real nunca enfrenta esto — el dominio es el SNI.
2. Se requiere emparejamiento de dispositivo
En la primera conexión HTTPS después de desactivar allowInsecureAuth:
Se requiere emparejamiento de dispositivo — Este navegador necesita una aprobación única del host del Gateway
antes de que pueda usar la UI de Control.
Causa: este es el flujo seguro correcto — con la autenticación insegura desactivada, cada nuevo
navegador debe ser aprobado. Solución: docker compose run --rm openclaw-cli devices approve <request-id>. (El CLI puede registrar scope upgrade pending approval … using local fallback pero aún completa la aprobación.)
3. NET::ERR_CERT_AUTHORITY_INVALID
Esperado con tls internal — el certificado está firmado por la CA local de Caddy, que tu
sistema operativo no confía. Haz clic para continuar en una máquina privada. Con un dominio público + Let's
Encrypt (a continuación), el certificado es confiable y no hay advertencia.
Pasando a producción
Con un dominio real, intercambia dos líneas en el Caddyfile — apúntalo a tu dominio y
elimina tls internal:
- openclaw.local {
- tls internal
+ openclaw.example.com {
reverse_proxy openclaw-gateway:18789
}
Luego apunta el registro A del dominio a la IP pública de la VM y abre 80 + 443
a Internet. Caddy automáticamente provisiona y renueva un certificado de Let's Encrypt
— confiable, sin advertencias, sin --resolve o /etc/hosts necesarios. Actualiza
allowedOrigins a https://openclaw.example.com.
Notas de seguridad
- Único punto de entrada — solo el
:443de Caddy está expuesto; el gateway no tiene puertos de host (Paso 8). - Emparejamiento de dispositivo — cada navegador necesita aprobación única (
openclaw devices). - Lista de orígenes permitidos — la UI de Control solo acepta
https://openclaw.local. - Rotar secretos — regenera
OPENCLAW_GATEWAY_TOKENsi alguna vez ha sido mostrado (capturas de pantalla, comparticiones de pantalla).
¿Qué sigue?
Has terminado Parte 2 de la serie Autoalojamiento de OpenClaw. A continuación:
- Parte 3 — Agregar un canal de Telegram (siguiente) — habla con tu agente ahora asegurado desde tu teléfono.
- Parte 4 — Hacer OpenClaw privado con una VPN de malla NetBird — colócalo en una malla privada y cierra los puertos públicos.
Los archivos complementarios (Caddyfile, docker-compose.override.yml) se encuentran en el repositorio de manifiestos de WiLine (enlace que vendrá con el repositorio).
Desmontaje
Para eliminar solo Caddy y reabrir los puertos directos, elimina docker-compose.override.yml
y Caddyfile, luego docker compose up -d. Para detener todo:
docker compose down
