Deploy OpenClaw on an Azure Linux VM with Bastion
Learn to provision an Azure Linux VM, secure it with NSG rules, and deploy OpenClaw via Bastion. Ideal for users seeking a 24/7 gateway with durable state.
Read this when
- You want OpenClaw running 24/7 on Azure with Network Security Group hardening
- You want a production-grade, always-on OpenClaw Gateway on your own Azure Linux VM
- You want secure administration with Azure Bastion SSH
Set up an Azure Linux VM through the Azure CLI, harden it with Network Security Group (NSG) rules, enable SSH access via Azure Bastion, and deploy OpenClaw.
What you will do
- Use the Azure CLI to provision networking (VNet, subnets, NSG) and compute resources
- Configure NSG rules so only Azure Bastion can SSH into the VM
- Connect over SSH using Azure Bastion, keeping the VM free of a public IP
- Run the installer script to deploy OpenClaw
- Confirm the gateway is operational
What you need
- An Azure subscription that permits creating compute and network resources
- Azure CLI installed, following the installation guide
- An SSH key pair, with instructions below for generating one if you lack it
- Around 20 to 30 minutes of time
Configure deployment
Sign in to Azure CLI
az login
az extension add -n ssh
Azure Bastion's native SSH tunneling depends on the ssh extension.
Register required resource providers (one time)
az provider register --namespace Microsoft.Compute
az provider register --namespace Microsoft.Network
Check registration status and wait until both entries display Registered.
az provider show --namespace Microsoft.Compute --query registrationState -o tsv
az provider show --namespace Microsoft.Network --query registrationState -o tsv
Set deployment variables
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"
Modify the names and CIDR ranges to match your setup. The Bastion subnet requires a minimum size of /26.
Select an SSH key
If you already have a public key, provide it here:
SSH_PUB_KEY="$(cat ~/.ssh/id_ed25519.pub)"
If not, create one with:
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
VM_SIZE="Standard_B2as_v2"
OS_DISK_SIZE_GB=64
- Choose a smaller size for modest workloads and expand later.
- Opt for more vCPU/RAM/disk when handling heavier automation, additional channels, or larger model/tool demands.
- When a size is unavailable in your region or due to quota limits, select the nearest available SKU.
See which VM sizes exist in your target region:
az vm list-skus --location "${LOCATION}" --resource-type virtualMachines -o table
Review your current vCPU and disk usage or quota:
az vm list-usage --location "${LOCATION}" -o table
Deploy Azure resources
Create the resource group
az group create -n "${RG}" -l "${LOCATION}"
Create the network security group
Set up the NSG with rules that restrict SSH access to the Bastion subnet only.
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
Priority dictates rule evaluation, with lower numbers taking precedence: Bastion traffic is permitted at 100, while all other SSH attempts are denied at 110 and 120.
Create the virtual network and subnets
Build the VNet containing the VM subnet with the NSG attached, then add the Bastion subnet.
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: this exact name is required by Azure
az network vnet subnet create \
-g "${RG}" --vnet-name "${VNET_NAME}" \
-n AzureBastionSubnet \
--address-prefixes "${BASTION_SUBNET_PREFIX}"
Create the VM
The VM is assigned no public IP, so SSH access relies entirely on Azure Bastion.
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 ""
The --public-ip-address "" flag stops a public IP from being created. Since the subnet-level NSG already enforces security, --nsg "" omits a per-NIC NSG.
To lock in a specific Ubuntu image version rather than latest, first enumerate the available versions:
az vm image list \
--publisher Canonical --offer ubuntu-24_04-lts \
--sku server --all -o table
Create Azure Bastion
Azure Bastion delivers managed SSH access without exposing a public IP on the VM. The Standard SKU with tunneling enabled is mandatory for CLI-based az network bastion ssh.
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
Provisioning Bastion usually completes in 5 to 10 minutes, though some regions may take 15 to 30 minutes.
Install OpenClaw
SSH into the VM through Azure Bastion
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)
curl -fsSL https://openclaw.ai/install.sh -o /tmp/install.sh
bash /tmp/install.sh
rm -f /tmp/install.sh
The installer checks for Node and dependencies, installing them if missing, then installs OpenClaw and starts onboarding. Refer to the Install page for more information.
Verify the gateway
Once onboarding finishes:
openclaw doctor --json
openclaw gateway status
If your organization holds GitHub Copilot licenses, you can pick the GitHub Copilot provider during onboarding instead of entering a separate model API key. See the GitHub Copilot provider documentation.
Cost considerations
Estimated monthly expenses, which you should verify against the Azure Pricing Calculator since regional rates shift over time:
- Azure Bastion Standard SKU: about $140 per month
- VM (
Standard_B2as_v2): about $55 per month
Ways to cut costs:
-
Stop the VM when idle. This halts compute billing while disk charges persist. The gateway remains offline during this period.
az vm deallocate -g "${RG}" -n "${VM_NAME}" az vm start -g "${RG}" -n "${VM_NAME}" # restart later -
Remove Bastion when it is not required and recreate it for future SSH sessions; it represents the largest expense and takes only minutes to provision.
-
Go with the Basic Bastion SKU (around $38 per month) if Portal-based SSH suffices and CLI tunneling (
az network bastion ssh) is unnecessary.
Cleanup
Remove every resource this guide creates:
az group delete -n "${RG}" --yes --no-wait
This action deletes the resource group along with all its contents, including the VM, VNet, NSG, Bastion, and public IP.
Next steps
- Add messaging channels: Channels
- Connect local devices as nodes: Nodes
- Adjust gateway settings: Gateway configuration
- For deeper Azure deployment details using the GitHub Copilot model provider: OpenClaw on Azure with GitHub Copilot