Deploy OpenClaw Gateway on an Azure Linux VM with Bastion
This guide covers creating an Azure Linux VM with no public IP, hardening NSG rules, and installing OpenClaw using Azure Bastion for SSH. It is intended for users with an Azure subscription and Azure CLI installed.
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 using 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.
- Configure NSG rules so VM SSH is only allowed from Azure Bastion.
- Use Azure Bastion for SSH access (the VM has no public IP).
- Install OpenClaw using the installer script.
- Verify the gateway.
What you need
- An Azure subscription with permissions 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 to 30 minutes.
Configure deployment
Sign in to Azure CLI
az login
az extension add -n ssh
The ssh extension is required for Azure Bastion native SSH tunneling.
Register required resource providers (one time)
az provider register --namespace Microsoft.Compute
az provider register --namespace Microsoft.Network
Verify registration; wait until both show 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"
Adjust names and CIDR ranges to match your environment. The Bastion subnet must be at least /26.
Select an SSH key
Use your existing public key if you have one:
SSH_PUB_KEY="$(cat ~/.ssh/id_ed25519.pub)"
Otherwise, generate one:
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
- Start with a smaller size for light usage and scale up later.
- Use more vCPU, RAM, or disk for heavier automation, more channels, or larger model or 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:
az vm list-skus --location "${LOCATION}" --resource-type virtualMachines -o table
Check 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
Create the NSG and add rules so only the Bastion subnet can SSH into the VM.
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
Rules are evaluated by priority, with the 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.
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 gets no public IP. SSH access goes exclusively through 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 ""
--public-ip-address "" prevents a public IP from being assigned. --nsg "" skips a per-NIC NSG because the subnet-level NSG already handles security.
To pin a specific Ubuntu image version instead of latest, list available versions first:
az vm image list \
--publisher Canonical --offer ubuntu-24_04-lts \
--sku server --all -o table
Create Azure Bastion
Azure Bastion provides 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.
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 to 10 minutes, but can take up to 15 to 30 minutes in some regions.
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 installs Node and dependencies if not already present, installs OpenClaw, and launches onboarding. See Install for details.
Verify the gateway
After onboarding completes:
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, as rates vary by region and change over time):
- Azure Bastion Standard SKU: roughly $140 per month.
- VM (
Standard_B2as_v2): roughly $55 per month.
To reduce costs:
-
Deallocate the VM when not in use. This stops compute billing (disk charges remain). The gateway is unreachable while deallocated.
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 per 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:
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
- Set up messaging channels: Channels.
- Pair local devices as nodes: Nodes.
- Configure the gateway: Gateway configuration.
- More detail on Azure deployment with the GitHub Copilot model provider: OpenClaw on Azure with GitHub Copilot.