Skip to main content

Tutorial: Secure OpenClaw with CloudConnexa (Docker — Advanced)

Abstract

Deploy OpenClaw securely with Docker and CloudConnexa using a private tunnel, HTTPS reverse proxy, and hardened configuration without exposing public ports.

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

  1. Install Docker Engine using the official documentation: Docker Install.

  2. Verify the installation:

    docker --version
    docker compose version
    • Expected 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.

  1. Navigate to Hosts → Hosts.

  2. Click Add Host.

  3. Configure:

    • Name: OpenClaw

    • Domain Name: openclaw.local (CloudConnexa will automatically resolve this domain to the Connector's tunnel IP for users connected to the WPC.)

    • Connector name

    • Region

  4. Click Finish.

    Important

    The domain name you choose here (e.g., openclaw.local) will be used throughout this guide. If you use a different domain, replace openclaw.local in all subsequent steps.

Step 3: Deploy the host connector

  1. Open the new Host, and click the Connectors tab.

  2. Click Deploy → Deploy Connector.

  3. Select your Linux distribution running on your VM.

  4. Run the provided script on your VM's terminal.

  5. Paste the token shown under "Generate Token" (located below the script).

  6. Click Next.

  7. Verify that the Connector shows "Connected."

  8. 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.

  1. Open the new Host, and click the Applications tab.

  2. Click Add Application.

  3. Add an application for HTTPS:

    • Name: OpenClaw HTTPS

    • Protocol: TCP

    • Port: 443

  4. (Optional) Add an application for HTTP (useful for curl testing from the tunnel):

    • Name: OpenClaw HTTP

    • Protocol: TCP

    • Port: 80

  5. Save and ensure the appropriate Access Groups have permission to reach these Applications.

Step 6: Prepare the project directory

  1. Prepare the project directory on Docker:

    mkdir -p ~/openclaw-docker
    cd ~/openclaw-docker
    mkdir -p config workspace
  2. Set ownership to uid 1000 (the node user inside the container) and lock down permissions:

    sudo chown -R 1000:1000 config workspace
    chmod 700 config workspace

    Keep ~/openclaw-docker/config/ and ~/openclaw-docker/workspace/ at 700 to 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 curl testing from the host.

  • Port 443 (HTTPS): For browser access through CloudConnexa. Uses tls internal to auto-generate a self-signed certificate for the hostname.

    Note

    The OpenClaw Control UI requires a secure context (HTTPS or localhost) for device identity, so this isn't optional for remote access.

  • Create the Caddyfile in the project directory:

    tee Caddyfile > /dev/null << 'EOF'
    :80 {
        reverse_proxy localhost:18789
    }
    
    openclaw.local1 {
        tls internal
        reverse_proxy localhost:18789
    }
    EOF

    1

    Replace openclaw.local with 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.

  1. 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"
  2. 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'
  3. Create the docker-compose.yml file 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

    What's happening here and why:

    • Security: Uses official GHCR non-root images (node user) and CLI hardening (cap_drop, no-new-privileges).

    • Networking: Caddy and CLI share the Gateway's network namespace via network_mode. This allows localhost communication without exposing internal ports to the host or public internet.

    • Restricted Access: Ports are bound strictly to 127.0.0.1 (local testing) and $TUNNEL_IP (CloudConnexa). No public 0.0.0.0 binding.

    • Persistence: caddy_data volume preserves SSL certificates and CA state.

    • Health: Standardized /healthz probes ensure automated container recovery.

Step 9: Run onboarding and start services

9.1 Run onboarding

  1. Run the onboarding wizard:

    docker compose run --rm openclaw-cli onboard
  2. Follow 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).

      Note

      You don't need to set the gateway bind to the CloudConnexa tunnel IP. The gateway stays on loopback inside the container. Caddy shares the same network namespace and can reach it directly. Caddy then serves HTTPS on port 443, which is bound to the tunnel IP on the host.

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:

  1. Run:

    openclaw dashboard --no-open
  2. Copy 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.

  1. Start all services defined in the Docker Compose file and run them in the background:

    docker compose up -d
  2. Verify the services are running:

    docker compose ps
    • Expected 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>).

    Note

    The #token=... part of the dashboard URL is a browser-side fragment. It's not sent to the server, so you don't need it for a curl test. You'll use the full URL with the token when accessing the dashboard from a browser.

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).

  1. 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.

  2. 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.local1\"]" --strict-json

    1

    Replace openclaw.local with 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

    Note

    Caddy and the CLI share the gateway's network namespace, so restarting just the gateway would take them down too.

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.

  1. 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
    EOF
  2. Reload firewall rules:

    sudo ufw reload
    sudo iptables -S DOCKER-USER

    Note

    If you also need local network access (e.g., for SSH management), add 10.0.0.0/8, 172.16.0.0/12, or 192.168.0.0/16 as needed.

  3. Verify rules (optional) by displaying active Docker firewall rules:

    sudo iptables -S DOCKER-USER

Step 12: Access OpenClaw remotely

  1. Connect to CloudConnexa.

  2. In a web browser, navigate to:

    https://openclaw.local1/#token=<your-token>2

    1

    Replace openclaw.local with your Host domain if different.

    2

    Replace <your-token> with the gateway token from Step 1: Install OpenClaw.

  3. 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.

  1. Connect to your server.

  2. List pending device requests:

    openclaw devices list
  3. Note the device ID you'll need to approve.

  4. Approve the device:

    openclaw devices approve <requestId>1

    1

    Replace <requestId> with the ID from the device list.

  5. Refresh the browser.

  6. The Control UI should now load.

    Note

    Each browser/device needs approval once. After approval, it's remembered across sessions.

Managing your deployment

docker compose up -d          # Start all services
docker compose stop           # Stop (keep data)
docker compose down           # Stop and remove containers

Important

Always use docker compose down && docker compose up -d instead of docker compose restart openclaw-gateway when restarting. Caddy and the CLI share the gateway's network namespace, so restarting just the gateway takes them all down.

docker compose logs openclaw-gateway       # Gateway logs
docker compose logs caddy                  # Caddy logs

Always back up before updating.

docker compose pull                        # Pull latest images
docker compose up -d --force-recreate      # Recreate containers
docker compose run --rm openclaw-cli security audit --deep

Troubleshooting

Cause

You're accessing OpenClaw over HTTP or using a hostname that doesn't match your Caddy configuration.

Resolution

  • Always access the dashboard using HTTPS:

    https://openclaw.local
  • Don't use http:// or the raw tunnel IP.

  • Verify the hostname in your Caddy file matches your CloudConnexa domain.

  • Verify your browser accepts the self-signed certificate.

Cause

The request origin isn't included in the allowed origins configuration.

Resolution

Update the allowed origins settings.

Cause

This is expected behavior when connecting from a new device or browser.

Resolution

Follow the steps to approve the device.

Cause

The Connector, routing, or access permissions may not be correctly configured.

Resolution

  1. Verify the Host Connector shows "Connected" in the CloudConnexa admin portal.

  2. Test connectivity from a device connected to the WPC:

    curl -sk https://openclaw.local/
  3. Confirm that your user/device is assigned to an Access Group that has permission to reach the OpenClaw application.

Cause

Caddy may not be correctly configured or running.

Resolution

  1. Verify the Caddyfile exists and is correct:

    cat Caddyfile
  2. Text connectivity from inside the container:

    docker compose exec caddy curl http://localhost:18789/healthz

    Expected output:

    {"ok":true,"status":"live"}
  3. Restart all services:

    docker compose down && docker compose up -d

Cause

Incorrect file ownership or permissions for mounted volumes.

Resolution

  • Reset permissions:

    sudo chown -R 1000:1000 config/ workspace/
    chmod 700 config/ workspace/