Hosting

Azure

Set up an Azure Linux VM with the Azure CLI, apply Network Security Group (NSG) hardening, configure Azure Bastion for SSH access, and install OpenClaw.

What you will do

  • Create Azure networking (VNet, subnets, NSG) and compute resources with the Azure CLI
  • Apply NSG rules so VM SSH is allowed only from Azure Bastion
  • Use Azure Bastion for SSH access (no public IP on the VM)
  • Install OpenClaw with the installer script
  • Verify the gateway

What you need

  • An Azure subscription with permission to create compute and network resources
  • Azure CLI installed (see Azure CLI install steps)
  • An SSH key pair (this guide covers generating one if needed)
  • About 20-30 minutes

Configure deployment

  • Sign in to Azure CLI

    bash
    az loginaz extension add -n ssh

    The ssh extension is required for Azure Bastion native SSH tunneling.

  • Register required resource providers (one time)

    bash
    az provider register --namespace Microsoft.Computeaz provider register --namespace Microsoft.Network

    Verify registration; wait until both show Registered.

    bash
    az provider show --namespace Microsoft.Compute --query registrationState -o tsvaz provider show --namespace Microsoft.Network --query registrationState -o tsv
  • Set deployment variables

    bash
    RG="rg-openclaw"LOCATION="westus2"VNET_NAME="vnet-openclaw"VNET_PREFIX="10.40.0.0/16"VM_SUBNET_NAME="snet-openclaw-vm"VM_SUBNET_PREFIX="10.40.2.0/24"BASTION_SUBNET_PREFIX="10.40.1.0/26"NSG_NAME="nsg-openclaw-vm"VM_NAME="vm-openclaw"ADMIN_USERNAME="openclaw"BASTION_NAME="bas-openclaw"BASTION_PIP_NAME="pip-openclaw-bastion"

    Adjust names and CIDR ranges to fit your environment. The Bastion subnet must be at least /26.

  • Select an SSH key

    Use your existing public key if you have one:

    bash
    SSH_PUB_KEY="$(cat ~/.ssh/id_ed25519.pub)"

    Otherwise, generate one:

    bash
    ssh-keygen -t ed25519 -a 100 -f ~/.ssh/id_ed25519 -C "you@example.com"SSH_PUB_KEY="$(cat ~/.ssh/id_ed25519.pub)"
  • Select VM size and OS disk size

    bash
    VM_SIZE="Standard_B2as_v2"OS_DISK_SIZE_GB=64
    • Start smaller for light usage and scale up later.
    • Use more vCPU/RAM/disk for heavier automation, more channels, or larger model/tool workloads.
    • If a size is unavailable in your region or subscription quota, pick the closest available SKU.

    List VM sizes available in your target region:

    bash
    az vm list-skus --location "${LOCATION}" --resource-type virtualMachines -o table

    Check your current vCPU and disk usage/quota:

    bash
    az vm list-usage --location "${LOCATION}" -o table
  • Deploy Azure resources

  • Create the resource group

    bash
    az group create -n "${RG}" -l "${LOCATION}"
  • Create the network security group

    Create the NSG and add rules so only the Bastion subnet can SSH into the VM.

    bash
    az network nsg create \  -g "${RG}" -n "${NSG_NAME}" -l "${LOCATION}" # Allow SSH from the Bastion subnet onlyaz network nsg rule create \  -g "${RG}" --nsg-name "${NSG_NAME}" \  -n AllowSshFromBastionSubnet --priority 100 \  --access Allow --direction Inbound --protocol Tcp \  --source-address-prefixes "${BASTION_SUBNET_PREFIX}" \  --destination-port-ranges 22 # Deny SSH from the public internetaz network nsg rule create \  -g "${RG}" --nsg-name "${NSG_NAME}" \  -n DenyInternetSsh --priority 110 \  --access Deny --direction Inbound --protocol Tcp \  --source-address-prefixes Internet \  --destination-port-ranges 22 # Deny SSH from other VNet sourcesaz network nsg rule create \  -g "${RG}" --nsg-name "${NSG_NAME}" \  -n DenyVnetSsh --priority 120 \  --access Deny --direction Inbound --protocol Tcp \  --source-address-prefixes VirtualNetwork \  --destination-port-ranges 22

    Rules evaluate by priority, lowest number first: Bastion traffic is allowed at 100, then all other SSH is blocked at 110 and 120.

  • Create the virtual network and subnets

    Create the VNet with the VM subnet (NSG attached), then add the Bastion subnet.

    bash
    az network vnet create \  -g "${RG}" -n "${VNET_NAME}" -l "${LOCATION}" \  --address-prefixes "${VNET_PREFIX}" \  --subnet-name "${VM_SUBNET_NAME}" \  --subnet-prefixes "${VM_SUBNET_PREFIX}" # Attach the NSG to the VM subnetaz network vnet subnet update \  -g "${RG}" --vnet-name "${VNET_NAME}" \  -n "${VM_SUBNET_NAME}" --nsg "${NSG_NAME}" # AzureBastionSubnet: this exact name is required by Azureaz network vnet subnet create \  -g "${RG}" --vnet-name "${VNET_NAME}" \  -n AzureBastionSubnet \  --address-prefixes "${BASTION_SUBNET_PREFIX}"
  • Create the VM

    The VM gets no public IP. SSH access goes exclusively through Azure Bastion.

    bash
    az vm create \  -g "${RG}" -n "${VM_NAME}" -l "${LOCATION}" \  --image "Canonical:ubuntu-24_04-lts:server:latest" \  --size "${VM_SIZE}" \  --os-disk-size-gb "${OS_DISK_SIZE_GB}" \  --storage-sku StandardSSD_LRS \  --admin-username "${ADMIN_USERNAME}" \  --ssh-key-values "${SSH_PUB_KEY}" \  --vnet-name "${VNET_NAME}" \  --subnet "${VM_SUBNET_NAME}" \  --public-ip-address "" \  --nsg ""

    --public-ip-address "" prevents a public IP from being assigned. --nsg "" skips a per-NIC NSG since the subnet-level NSG already handles security.

    To pin a specific Ubuntu image version instead of latest, list available versions first:

    bash
    az vm image list \  --publisher Canonical --offer ubuntu-24_04-lts \  --sku server --all -o table
  • Create Azure Bastion

    Azure Bastion gives managed SSH access without exposing a public IP on the VM. The Standard SKU with tunneling enabled is required for CLI-based az network bastion ssh.

    bash
    az network public-ip create \  -g "${RG}" -n "${BASTION_PIP_NAME}" -l "${LOCATION}" \  --sku Standard --allocation-method Static az network bastion create \  -g "${RG}" -n "${BASTION_NAME}" -l "${LOCATION}" \  --vnet-name "${VNET_NAME}" \  --public-ip-address "${BASTION_PIP_NAME}" \  --sku Standard --enable-tunneling true

    Bastion provisioning typically takes 5-10 minutes, but can take up to 15-30 minutes in some regions.

  • Install OpenClaw

  • SSH into the VM through Azure Bastion

    bash
    VM_ID="$(az vm show -g "${RG}" -n "${VM_NAME}" --query id -o tsv)" az network bastion ssh \  --name "${BASTION_NAME}" \  --resource-group "${RG}" \  --target-resource-id "${VM_ID}" \  --auth-type ssh-key \  --username "${ADMIN_USERNAME}" \  --ssh-key ~/.ssh/id_ed25519
  • Install OpenClaw (in the VM shell)

    bash
    curl -fsSL https://openclaw.ai/install.sh -o /tmp/install.shbash /tmp/install.shrm -f /tmp/install.sh

    The installer installs Node and dependencies if not already present, installs OpenClaw, and launches onboarding. See Install for details.

  • Verify the gateway

    After onboarding completes:

    bash
    openclaw gateway status

    If your organization already has GitHub Copilot licenses, you can choose the GitHub Copilot provider during onboarding instead of a separate model API key. See GitHub Copilot provider.

  • Cost considerations

    Approximate monthly costs (verify current pricing in the Azure Pricing Calculator, since rates vary by region and change over time):

    • Azure Bastion Standard SKU: roughly $140/month
    • VM (Standard_B2as_v2): roughly $55/month

    To reduce costs:

    • Deallocate the VM when not in use. This stops compute billing (disk charges remain). The gateway is unreachable while deallocated.

      bash
      az vm deallocate -g "${RG}" -n "${VM_NAME}"az vm start -g "${RG}" -n "${VM_NAME}"   # restart later
    • Delete Bastion when not needed and recreate it when you need SSH access again; it is the largest cost component and provisions in a few minutes.

    • Use the Basic Bastion SKU (roughly $38/month) if you only need Portal-based SSH and do not need CLI tunneling (az network bastion ssh).

    Cleanup

    Delete all resources created by this guide:

    bash
    az group delete -n "${RG}" --yes --no-wait

    This removes the resource group and everything inside it (VM, VNet, NSG, Bastion, public IP).

    Next steps

    Was this useful?
    On this page

    On this page