Platform setup

How to Deploy OpenClaw on Azure

5 min read

Browse more in Platform setup.

All platform setup guides →

This guide walks you through deploying OpenClaw on an Azure Linux VM using the Azure CLI, with a locked-down network and Bastion-only SSH access. You create the VNet, NSG, VM, and Bastion from the terminal, then run the OpenClaw installer script on the VM.

04 in your Azure subscription.

Setup flow

Prerequisites

  • An Azure subscription with permission to create compute and network resources.
  • Azure CLI installed on your machine (see Microsoft's Azure CLI install steps if you don't have it yet).
  • An SSH key pair available locally; this guide assumes `~/.ssh/id_ed25519` and shows how to generate it if needed.
  • 20–30 minutes of uninterrupted time to create Azure resources and run the OpenClaw installer.

Steps

  1. 1

    Sign in to Azure CLI and enable SSH extension

    Authenticate the Azure CLI so all subsequent commands run against the right subscription, then add the `ssh` extension that Bastion tunneling depends on. Without this extension, the `az network bastion ssh` command you use later will fail.

    bash
    az login
    az extension add -n ssh
  2. 2

    Register Azure resource providers for compute and networking

    Azure needs the Compute and Network resource providers registered before you can create VMs, VNets, NSGs, and Bastion. Run these once per subscription and wait until both show `Registered` before moving on, otherwise later `az` commands will error out.

    bash
    az provider register --namespace Microsoft.Compute
    az provider register --namespace Microsoft.Network
    
    az provider show --namespace Microsoft.Compute --query registrationState -o tsv
    az provider show --namespace Microsoft.Network --query registrationState -o tsv
  3. 3

    Set deployment variables for your Azure environment

    Define all the names, locations, and CIDR ranges up front so you can reuse them across commands and keep your deployment consistent. You can tweak these values to match your conventions, but keep the Bastion subnet at least `/26` or Azure will reject it.

    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"
  4. 4

    Select or generate an SSH key for VM access

    OpenClaw runs on a VM that you access over SSH via Bastion, so you need a public key to inject into the VM at creation time. Use your existing `id_ed25519` key if you have one, or generate a new ed25519 key and capture its public half into `SSH_PUB_KEY`.

    bash
    SSH_PUB_KEY="$(cat ~/.ssh/id_ed25519.pub)"
    
    ssh-keygen -t ed25519 -a 100 -f ~/.ssh/id_ed25519 -C "you@example.com"
    SSH_PUB_KEY="$(cat ~/.ssh/id_ed25519.pub)"
  5. 5

    Choose VM size and OS disk size for OpenClaw

    Pick a VM SKU and disk size that exist in your region and fit your workload, then verify quotas. The docs default to `Standard_B2as_v2` with a 64 GB disk, and you can list available SKUs and current usage to avoid quota or availability errors during creation.

    bash
    VM_SIZE="Standard_B2as_v2"
    OS_DISK_SIZE_GB=64
    
    az vm list-skus --location "${LOCATION}" --resource-type virtualMachines -o table
    
    az vm list-usage --location "${LOCATION}" -o table
  6. 6

    Create the resource group for all OpenClaw Azure resources

    Group everything (VNet, NSG, VM, Bastion, public IP) into a single resource group so you can manage or delete it in one shot later. This also keeps your OpenClaw deployment isolated from other infrastructure.

    bash
    az group create -n "${RG}" -l "${LOCATION}"
  7. 7

    Create and harden the Network Security Group

    Set up an NSG that only allows SSH from the Bastion subnet and explicitly denies SSH from the internet and other VNet sources. The rule priorities matter: the allow rule at 100 must come before the deny rules at 110 and 120 so Bastion traffic is permitted.

    bash
    az network nsg create \
      -g "${RG}" -n "${NSG_NAME}" -l "${LOCATION}"
    
    # Allow SSH from the Bastion subnet only
    az 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 internet
    az 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 sources
    az 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
  8. 8

    Create the virtual network and subnets for VM and Bastion

    Provision a VNet with a VM subnet that has the NSG attached, then add the special `AzureBastionSubnet` that Bastion requires. Attaching the NSG at the subnet level keeps the VM NIC clean and centralizes your SSH rules.

    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 subnet
    az network vnet subnet update \
      -g "${RG}" --vnet-name "${VNET_NAME}" \
      -n "${VM_SUBNET_NAME}" --nsg "${NSG_NAME}"
    
    # AzureBastionSubnet — name is required by Azure
    az network vnet subnet create \
      -g "${RG}" --vnet-name "${VNET_NAME}" \
      -n AzureBastionSubnet \
      --address-prefixes "${BASTION_SUBNET_PREFIX}"
  9. 9

    Create the Ubuntu VM without a public IP

    04 LTS VM that will host OpenClaw, using your chosen size and disk. The command explicitly disables a public IP and per-NIC NSG so all SSH goes through Bastion and the subnet-level NSG, and you can later pin the image version instead of `latest` if you want reproducibility.

    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 ""
    
    az vm image list \
      --publisher Canonical --offer ubuntu-24_04-lts \
      --sku server --all -o table
  10. 10

    Create Azure Bastion for managed SSH access

    Provision a Standard SKU Bastion host with tunneling enabled so you can SSH into the VM from your terminal without exposing a public IP. Bastion takes several minutes to come up, so expect a short wait before SSH works.

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

    SSH into the VM through Azure Bastion

    Once Bastion is ready, use the CLI-based SSH tunneling to open a shell on the VM using your SSH key and admin username. This command looks up the VM resource ID and connects without ever assigning a public IP to the VM.

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

    Install OpenClaw on the Azure VM

    From the VM shell, download the official installer script, run it, and then clean up the temporary file. The installer handles Node LTS and dependencies, installs OpenClaw itself, and launches the onboarding wizard where you pick providers like GitHub Copilot.

    bash
    curl -fsSL https://openclaw.ai/install.sh -o /tmp/install.sh
    bash /tmp/install.sh
    rm -f /tmp/install.sh
  13. 13

    Verify the OpenClaw Gateway is running

    After the onboarding wizard finishes, confirm that the Gateway service is up and healthy. This command gives you a quick status check so you know the deployment on Azure completed successfully.

    bash
    openclaw gateway status
  14. 14

    Manage costs and clean up Azure resources when done

    Azure Bastion and the VM both incur ongoing charges, so deallocate or delete resources when you are not actively using OpenClaw. You can stop compute billing by deallocating the VM, or nuke everything by deleting the resource group.

    bash
    az vm deallocate -g "${RG}" -n "${VM_NAME}"
    az vm start -g "${RG}" -n "${VM_NAME}"   # restart later
    
    az group delete -n "${RG}" --yes --no-wait

Troubleshooting

Azure Bastion creation is stuck in 'Creating' for a long time and SSH via `az network bastion ssh` fails.

Bastion provisioning often takes 5–10 minutes and can take up to 15–30 minutes in some regions. Wait until the Bastion resource shows as fully provisioned in the Azure Portal or via `az` before retrying the SSH command.

VM creation fails with an error about the Bastion subnet size or SSH access not working from Bastion.

Azure requires the Bastion subnet (`AzureBastionSubnet`) to be at least `/26`, and the NSG must allow SSH from that subnet CIDR. Ensure `BASTION_SUBNET_PREFIX` is `/26` or larger and that the NSG rule `AllowSshFromBastionSubnet` uses the same prefix.

bash
BASTION_SUBNET_PREFIX="10.40.1.0/26"

az 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

Frequently asked questions

Powered by Mem0

Add persistent memory to OpenClaw

Official Mem0 plugin for OpenClaw keeps context across chats and tools. Smaller prompts, lower cost, better continuity for your agents.

More in Platform setup