Tutorial: Secure OpenClaw with CloudConnexa (Docker — Advanced)
Overview
This guide shows how to securely deploy OpenClaw using Docker and make it accessible through CloudConnexa without exposing any public ports.
This setup uses:
CloudConnexa Host Connector — provides private, always-on access.
Caddy reverse proxy — enables HTTPS access.
Docker Compose — runs OpenClaw and supporting services.
Security hardening — based on OpenClaw best practices.
OpenClaw runs inside a container and is only accessible through CloudConnexa.
Architecture
[Authorized CloudConnexa Users]
↓ (Tunnel)
[CloudConnexa]
↓
[Host Connector on your VM] ← always-on tunnel, stable IP (e.g., 100.96.x.x)
↓
[Caddy Reverse Proxy] ← HTTPS/TCP443, shared network namespace with OpenClaw gateway
↓
[OpenClaw Gateway] ← localhost:18789 inside container, token auth + device pairing
↓
[Claude / OpenAI / ... APIs]Before you begin
Ensure you have the following:
A Linux VM (Ubuntu 22.04/24.04 recommended) with at least 2 GB RAM (4 GB recommended) and 10 GB disk space.
Root or sudo access.
A CloudConnexa account.
AI provider API key (OpenAI, Anthropic, etc.)
Step 1: Install Docker
Install Docker Engine using the official documentation: Docker Install.
Verify the installation:
docker --version docker compose versionExpected results:
docker --version # Should show: Docker version 20.10+ or higher docker compose version # Should show: Docker Compose version v2.x.x or higher
Step 2: Create a host in CloudConnexa
The CloudConnexa host connector installs directly on your application server, enabling access to applications running on that machine.
Sign in to your WPC's Administration portal by entering your Cloud ID in a web browser (for example,
company_name.openvpn.com).Navigate to Hosts → Hosts.
Click Add Host.
Configure:
Name:
OpenClawDomain Name:
openclaw.local(CloudConnexa will automatically resolve this domain to the Connector's tunnel IP for users connected to the WPC.)Connector name
Region
Click Finish.
Step 3: Deploy the host connector
Open the new Host, and click the Connectors tab.
Click Deploy → Deploy Connector.
Select your Linux distribution running on your VM.
Run the provided script on your VM's terminal.
Paste the token shown under "Generate Token" (located below the script).
Click Next.
Verify that the Connector shows "Connected."
Record the tunnel IP shown next to the Connector (e.g.,
100.96.1.50) for a later step.
Step 4: Configure applications (optional)
Define which services are exposed so that granular access control can be configured.
Open the new Host, and click the Applications tab.
Click Add Application.
Add an application for HTTPS:
Name: OpenClaw HTTPS
Protocol: TCP
Port: 443
(Optional) Add an application for HTTP (useful for
curltesting from the tunnel):Name: OpenClaw HTTP
Protocol: TCP
Port: 80
Save and ensure the appropriate Access Groups have permission to reach these Applications.
Step 6: Prepare the project directory
Prepare the project directory on Docker:
mkdir -p ~/openclaw-docker cd ~/openclaw-docker mkdir -p config workspaceSet ownership to uid 1000 (the
nodeuser inside the container) and lock down permissions:sudo chown -R 1000:1000 config workspace chmod 700 config workspaceKeep
~/openclaw-docker/config/and~/openclaw-docker/workspace/at700to prevent other users or processes from reading tokens and credentials.
Step 7: Create the Caddyfile
Caddy shares the gateway container's network namespace, so it can reach the gateway at localhost:18789. It serves two listeners:
Port 80 (HTTP): For local
curltesting from the host.Port 443 (HTTPS): For browser access through CloudConnexa. Uses
tls internalto auto-generate a self-signed certificate for the hostname.
Create the Caddyfile in the project directory:
tee Caddyfile > /dev/null << 'EOF' :80 { reverse_proxy localhost:18789 } openclaw.local { tls internal reverse_proxy localhost:18789 } EOFReplace
openclaw.localwith your Host's domain name if different.
Step 8: Create docker-compose.yml
This Compose file is built from the official OpenClaw Docker documentation and the upstream docker-compose.yml in the OpenClaw repository, with Caddy added as a reverse proxy.
Detect the CloudConnexa tunnel IP and store it for use in the compose file:
TUNNEL_IP=$(sudo openvpn3 sessions-list | grep 'tun' | awk -F ':' '{print $3}') && \ TUNNEL_IP=$(ip -4 addr show $TUNNEL_IP | grep -oP '(?:\b\.?(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){4}' | head -1) echo "Detected tunnel IP: $TUNNEL_IP"Verify this matches the tunnel IP shown in the CloudConnexa Admin portal (Step 3: Deploy the host connector). If it doesn't, set it manually:
TUNNEL_IP='100.96.1.50'Create the
docker-compose.ymlfile using the detected tunnel IP:tee docker-compose.yml > /dev/null << EOF services: openclaw-gateway: image: ghcr.io/openclaw/openclaw:latest container_name: openclaw-gateway restart: unless-stopped init: true ports: - "127.0.0.1:80:80" # HTTP — local access for curl testing - "$TUNNEL_IP:443:443" # HTTPS — CloudConnexa tunnel access volumes: - ./config:/home/node/.openclaw - ./workspace:/home/node/.openclaw/workspace environment: HOME: /home/node TERM: xterm-256color healthcheck: test: [ "CMD", "node", "-e", "fetch('http://127.0.0.1:18789/healthz').then((r)=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))", ] interval: 30s timeout: 5s retries: 5 start_period: 20s openclaw-cli: image: ghcr.io/openclaw/openclaw:latest container_name: openclaw-cli profiles: ["cli"] network_mode: "service:openclaw-gateway" cap_drop: - NET_RAW - NET_ADMIN security_opt: - no-new-privileges:true stdin_open: true tty: true init: true entrypoint: ["node", "dist/index.js"] volumes: - ./config:/home/node/.openclaw - ./workspace:/home/node/.openclaw/workspace environment: HOME: /home/node TERM: xterm-256color BROWSER: echo depends_on: - openclaw-gateway caddy: image: caddy:latest container_name: caddy restart: unless-stopped network_mode: "service:openclaw-gateway" volumes: - ./Caddyfile:/etc/caddy/Caddyfile:ro - caddy_data:/data depends_on: - openclaw-gateway volumes: caddy_data: EOF
Step 9: Run onboarding and start services
9.1 Run onboarding
Run the onboarding wizard:
docker compose run --rm openclaw-cli onboardFollow the prompts to:
Select your AI provider.
Enter your API key.
Set onboarding mode to Manual.
Use default values for Config handling.
Set Gateway mode to Local.
Leave Gateway bind as default (loopback).
9.2 Retrieve the gateway token
The gateway token is displayed at the end of the onboarding wizard. Copy it for use later.
If you need to find it again:
Run:
openclaw dashboard --no-openCopy the token value from:
Dashboard URL: http://127.0.0.1:18789/#token=
9.3 Start all services
Start the OpenClaw gateway and Caddy reverse proxy.
Start all services defined in the Docker Compose file and run them in the background:
docker compose up -dVerify the services are running:
docker compose psExpected output:
NAME IMAGE STATUS caddy caddy:latest Up openclaw-gateway ghcr.io/openclaw/openclaw:latest Up (healthy)
9.4 Test local access
Verify that Caddy is correctly proxying requests to the OpenClaw gateway.
Confirm the reverse proxy is working:
curl -s http://127.0.0.1/You should see the OpenClaw Control UI HTML (starting with
<!doctype html>).
Step 10: Apply security hardening
This step applies recommended security settings for running OpenClaw behind CloudConnexa and a reverse proxy.
10.1 Enable gateway authentication (token mode)
Gateway auth is required by default; if no token is configured, the gateway refuses WebSocket connections (fail-closed).
Verify the current authentication mode:
Gateway auth is required by default; if no token is configured, the gateway refuses WebSocket connections (fail-closed).Expected result:
token.If not set, enable token authentication:
docker compose run --rm openclaw-cli config set gateway.auth.mode token
10.2 Configure trusted proxy
Since Caddy is acting as a reverse proxy, OpenClaw must trust it to correctly identify client requests.
Allow traffic forwarded from localhost (Caddy) and ensure client IPs and requests are handled correctly:
docker compose run --rm openclaw-cli config set gateway.trustedProxies '["127.0.0.1"]' --strict-json
10.3 Configure allowed origins
The Control UI rejects requests from origins not in its allowlist.
Configure the allowed HTTPS domain and tunnel IP used by CloudConnexa:
TUNNEL_IP=$(sudo openvpn3 sessions-list | grep 'tun' | awk -F ':' '{print $3}') && \ TUNNEL_IP=$(ip -4 addr show $TUNNEL_IP | grep -oP '(?:\b\.?(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){4}' | head -1) openclaw config set gateway.controlUi.allowedOrigins "[\"https://$TUNNEL_IP\",\"https://openclaw.local\"]" --strict-jsonReplace
openclaw.localwith your Host domain if different.
10.4 (Optional) Disable mDNS discovery
The gateway broadcasts its presence via mDNS on the local network by default, which can leak operational details like filesystem paths and SSH port availability.
Disable local network discovery:
docker compose run --rm openclaw-cli config set discovery.mdns.mode off
10.5 (Optional) Enable log redaction
Redaction prevents sensitive information from appearing in logs.
Mask sensitive tool output in logs:
docker compose run --rm openclaw-cli config set logging.redactSensitive tools
10.6 Apply configuration changes
Restart all services to apply the updated settings:
docker compose down && docker compose up -d
Step 11: (Optional) Configure firewall hardening
Docker-published container ports bypass the host's INPUT iptables rules. Even though we bind ports to specific IPs, it's good practice to enforce rules in the DOCKER-USER chain as defense-in-depth.
Add firewall rules that restrict container traffic, allow localhost and CloudConnexa tunnel traffic, and block other inbound connections:
sudo tee -a /etc/ufw/after.rules > /dev/null << 'EOF' *filter :DOCKER-USER - [0:0] -A DOCKER-USER -m conntrack --ctstate ESTABLISHED,RELATED -j RETURN -A DOCKER-USER -s 127.0.0.0/8 -j RETURN -A DOCKER-USER -s 100.96.0.0/11 -j RETURN -A DOCKER-USER -m conntrack --ctstate NEW -j DROP -A DOCKER-USER -j RETURN COMMIT EOFReload firewall rules:
sudo ufw reload sudo iptables -S DOCKER-USERVerify rules (optional) by displaying active Docker firewall rules:
sudo iptables -S DOCKER-USER
Step 12: Access OpenClaw remotely
Connect to CloudConnexa.
In a web browser, navigate to:
https://openclaw.local/#token=<your-token>Replace
openclaw.localwith your Host domain if different.Replace
<your-token>with the gateway token from Step 1: Install OpenClaw.
Accept the certificate warning. (Caddy uses a self-signed internal certificate.)
Step 13: Approve the device
On first connection from a new browser, the Control UI will show "pairing required". This is expected; the gateway requires device approval as an additional layer of security on top of token authentication.
Connect to your server.
List pending device requests:
openclaw devices listNote the device ID you'll need to approve.
Approve the device:
openclaw devices approve <requestId>Replace
<requestId>with the ID from the device list.
Refresh the browser.
The Control UI should now load.