Deploy Azure Bastion Hosts

The azure-native:network:BastionHost resource, part of the Pulumi Azure Native provider, provisions an Azure Bastion service that provides secure RDP and SSH connectivity to virtual machines through the Azure portal. This guide focuses on three capabilities: standard deployment with public access, private-only mode for isolated networks, and Developer SKU with IP-based access control.

Bastion hosts require a virtual network with a subnet named ‘BastionHostSubnet’ and, for standard deployments, a public IP address. The examples are intentionally small. Combine them with your own virtual networks, public IPs, and access policies.

Deploy a standard Bastion Host with public access

Most deployments provide browser-based VM access without exposing public IPs on individual VMs.

import * as pulumi from "@pulumi/pulumi";
import * as azure_native from "@pulumi/azure-native";

const bastionHost = new azure_native.network.BastionHost("bastionHost", {
    bastionHostName: "bastionhosttenant",
    ipConfigurations: [{
        name: "bastionHostIpConfiguration",
        publicIPAddress: {
            id: "/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Network/publicIPAddresses/pipName",
        },
        subnet: {
            id: "/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet2/subnets/BastionHostSubnet",
        },
    }],
    resourceGroupName: "rg1",
});
import pulumi
import pulumi_azure_native as azure_native

bastion_host = azure_native.network.BastionHost("bastionHost",
    bastion_host_name="bastionhosttenant",
    ip_configurations=[{
        "name": "bastionHostIpConfiguration",
        "public_ip_address": {
            "id": "/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Network/publicIPAddresses/pipName",
        },
        "subnet": {
            "id": "/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet2/subnets/BastionHostSubnet",
        },
    }],
    resource_group_name="rg1")
package main

import (
	network "github.com/pulumi/pulumi-azure-native-sdk/network/v3"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := network.NewBastionHost(ctx, "bastionHost", &network.BastionHostArgs{
			BastionHostName: pulumi.String("bastionhosttenant"),
			IpConfigurations: network.BastionHostIPConfigurationArray{
				&network.BastionHostIPConfigurationArgs{
					Name: pulumi.String("bastionHostIpConfiguration"),
					PublicIPAddress: &network.SubResourceArgs{
						Id: pulumi.String("/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Network/publicIPAddresses/pipName"),
					},
					Subnet: &network.SubResourceArgs{
						Id: pulumi.String("/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet2/subnets/BastionHostSubnet"),
					},
				},
			},
			ResourceGroupName: pulumi.String("rg1"),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using AzureNative = Pulumi.AzureNative;

return await Deployment.RunAsync(() => 
{
    var bastionHost = new AzureNative.Network.BastionHost("bastionHost", new()
    {
        BastionHostName = "bastionhosttenant",
        IpConfigurations = new[]
        {
            new AzureNative.Network.Inputs.BastionHostIPConfigurationArgs
            {
                Name = "bastionHostIpConfiguration",
                PublicIPAddress = new AzureNative.Network.Inputs.SubResourceArgs
                {
                    Id = "/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Network/publicIPAddresses/pipName",
                },
                Subnet = new AzureNative.Network.Inputs.SubResourceArgs
                {
                    Id = "/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet2/subnets/BastionHostSubnet",
                },
            },
        },
        ResourceGroupName = "rg1",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.azurenative.network.BastionHost;
import com.pulumi.azurenative.network.BastionHostArgs;
import com.pulumi.azurenative.network.inputs.BastionHostIPConfigurationArgs;
import com.pulumi.azurenative.network.inputs.SubResourceArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var bastionHost = new BastionHost("bastionHost", BastionHostArgs.builder()
            .bastionHostName("bastionhosttenant")
            .ipConfigurations(BastionHostIPConfigurationArgs.builder()
                .name("bastionHostIpConfiguration")
                .publicIPAddress(SubResourceArgs.builder()
                    .id("/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Network/publicIPAddresses/pipName")
                    .build())
                .subnet(SubResourceArgs.builder()
                    .id("/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet2/subnets/BastionHostSubnet")
                    .build())
                .build())
            .resourceGroupName("rg1")
            .build());

    }
}
resources:
  bastionHost:
    type: azure-native:network:BastionHost
    properties:
      bastionHostName: bastionhosttenant
      ipConfigurations:
        - name: bastionHostIpConfiguration
          publicIPAddress:
            id: /subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Network/publicIPAddresses/pipName
          subnet:
            id: /subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet2/subnets/BastionHostSubnet
      resourceGroupName: rg1

The ipConfigurations array connects Bastion to your network. The publicIPAddress property assigns a public IP for inbound connections, while the subnet property must reference a subnet named exactly “BastionHostSubnet”. Azure uses this dedicated subnet to deploy the Bastion service infrastructure.

Deploy a private-only Bastion Host without public IP

Organizations with strict isolation requirements can restrict Bastion access to users connected via ExpressRoute or VPN.

import * as pulumi from "@pulumi/pulumi";
import * as azure_native from "@pulumi/azure-native";

const bastionHost = new azure_native.network.BastionHost("bastionHost", {
    bastionHostName: "bastionhosttenant",
    enablePrivateOnlyBastion: true,
    ipConfigurations: [{
        name: "bastionHostIpConfiguration",
        subnet: {
            id: "/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet2/subnets/BastionHostSubnet",
        },
    }],
    resourceGroupName: "rg1",
});
import pulumi
import pulumi_azure_native as azure_native

bastion_host = azure_native.network.BastionHost("bastionHost",
    bastion_host_name="bastionhosttenant",
    enable_private_only_bastion=True,
    ip_configurations=[{
        "name": "bastionHostIpConfiguration",
        "subnet": {
            "id": "/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet2/subnets/BastionHostSubnet",
        },
    }],
    resource_group_name="rg1")
package main

import (
	network "github.com/pulumi/pulumi-azure-native-sdk/network/v3"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := network.NewBastionHost(ctx, "bastionHost", &network.BastionHostArgs{
			BastionHostName:          pulumi.String("bastionhosttenant"),
			EnablePrivateOnlyBastion: pulumi.Bool(true),
			IpConfigurations: network.BastionHostIPConfigurationArray{
				&network.BastionHostIPConfigurationArgs{
					Name: pulumi.String("bastionHostIpConfiguration"),
					Subnet: &network.SubResourceArgs{
						Id: pulumi.String("/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet2/subnets/BastionHostSubnet"),
					},
				},
			},
			ResourceGroupName: pulumi.String("rg1"),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using AzureNative = Pulumi.AzureNative;

return await Deployment.RunAsync(() => 
{
    var bastionHost = new AzureNative.Network.BastionHost("bastionHost", new()
    {
        BastionHostName = "bastionhosttenant",
        EnablePrivateOnlyBastion = true,
        IpConfigurations = new[]
        {
            new AzureNative.Network.Inputs.BastionHostIPConfigurationArgs
            {
                Name = "bastionHostIpConfiguration",
                Subnet = new AzureNative.Network.Inputs.SubResourceArgs
                {
                    Id = "/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet2/subnets/BastionHostSubnet",
                },
            },
        },
        ResourceGroupName = "rg1",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.azurenative.network.BastionHost;
import com.pulumi.azurenative.network.BastionHostArgs;
import com.pulumi.azurenative.network.inputs.BastionHostIPConfigurationArgs;
import com.pulumi.azurenative.network.inputs.SubResourceArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var bastionHost = new BastionHost("bastionHost", BastionHostArgs.builder()
            .bastionHostName("bastionhosttenant")
            .enablePrivateOnlyBastion(true)
            .ipConfigurations(BastionHostIPConfigurationArgs.builder()
                .name("bastionHostIpConfiguration")
                .subnet(SubResourceArgs.builder()
                    .id("/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet2/subnets/BastionHostSubnet")
                    .build())
                .build())
            .resourceGroupName("rg1")
            .build());

    }
}
resources:
  bastionHost:
    type: azure-native:network:BastionHost
    properties:
      bastionHostName: bastionhosttenant
      enablePrivateOnlyBastion: true
      ipConfigurations:
        - name: bastionHostIpConfiguration
          subnet:
            id: /subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet2/subnets/BastionHostSubnet
      resourceGroupName: rg1

Setting enablePrivateOnlyBastion to true removes the public IP requirement. The ipConfigurations array still references the BastionHostSubnet, but users must connect through private network paths. This mode prevents internet-based access entirely.

Deploy a Developer SKU Bastion with network ACLs

The Developer SKU reduces costs in non-production environments while adding IP-based access control.

import * as pulumi from "@pulumi/pulumi";
import * as azure_native from "@pulumi/azure-native";

const bastionHost = new azure_native.network.BastionHost("bastionHost", {
    bastionHostName: "bastionhostdeveloper",
    ipConfigurations: [],
    networkAcls: {
        ipRules: [{
            addressPrefix: "1.1.1.1/16",
        }],
    },
    resourceGroupName: "rg2",
    virtualNetwork: {
        id: "/subscriptions/subid/resourceGroups/rg2/providers/Microsoft.Network/virtualNetworks/vnet2",
    },
});
import pulumi
import pulumi_azure_native as azure_native

bastion_host = azure_native.network.BastionHost("bastionHost",
    bastion_host_name="bastionhostdeveloper",
    ip_configurations=[],
    network_acls={
        "ip_rules": [{
            "address_prefix": "1.1.1.1/16",
        }],
    },
    resource_group_name="rg2",
    virtual_network={
        "id": "/subscriptions/subid/resourceGroups/rg2/providers/Microsoft.Network/virtualNetworks/vnet2",
    })
package main

import (
	network "github.com/pulumi/pulumi-azure-native-sdk/network/v3"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := network.NewBastionHost(ctx, "bastionHost", &network.BastionHostArgs{
			BastionHostName:  pulumi.String("bastionhostdeveloper"),
			IpConfigurations: network.BastionHostIPConfigurationArray{},
			NetworkAcls: &network.BastionHostPropertiesFormatNetworkAclsArgs{
				IpRules: network.IPRuleArray{
					&network.IPRuleArgs{
						AddressPrefix: pulumi.String("1.1.1.1/16"),
					},
				},
			},
			ResourceGroupName: pulumi.String("rg2"),
			VirtualNetwork: &network.SubResourceArgs{
				Id: pulumi.String("/subscriptions/subid/resourceGroups/rg2/providers/Microsoft.Network/virtualNetworks/vnet2"),
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using AzureNative = Pulumi.AzureNative;

return await Deployment.RunAsync(() => 
{
    var bastionHost = new AzureNative.Network.BastionHost("bastionHost", new()
    {
        BastionHostName = "bastionhostdeveloper",
        IpConfigurations = new[] {},
        NetworkAcls = new AzureNative.Network.Inputs.BastionHostPropertiesFormatNetworkAclsArgs
        {
            IpRules = new[]
            {
                new AzureNative.Network.Inputs.IPRuleArgs
                {
                    AddressPrefix = "1.1.1.1/16",
                },
            },
        },
        ResourceGroupName = "rg2",
        VirtualNetwork = new AzureNative.Network.Inputs.SubResourceArgs
        {
            Id = "/subscriptions/subid/resourceGroups/rg2/providers/Microsoft.Network/virtualNetworks/vnet2",
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.azurenative.network.BastionHost;
import com.pulumi.azurenative.network.BastionHostArgs;
import com.pulumi.azurenative.network.inputs.BastionHostPropertiesFormatNetworkAclsArgs;
import com.pulumi.azurenative.network.inputs.SubResourceArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var bastionHost = new BastionHost("bastionHost", BastionHostArgs.builder()
            .bastionHostName("bastionhostdeveloper")
            .ipConfigurations()
            .networkAcls(BastionHostPropertiesFormatNetworkAclsArgs.builder()
                .ipRules(IPRuleArgs.builder()
                    .addressPrefix("1.1.1.1/16")
                    .build())
                .build())
            .resourceGroupName("rg2")
            .virtualNetwork(SubResourceArgs.builder()
                .id("/subscriptions/subid/resourceGroups/rg2/providers/Microsoft.Network/virtualNetworks/vnet2")
                .build())
            .build());

    }
}
resources:
  bastionHost:
    type: azure-native:network:BastionHost
    properties:
      bastionHostName: bastionhostdeveloper
      ipConfigurations: []
      networkAcls:
        ipRules:
          - addressPrefix: 1.1.1.1/16
      resourceGroupName: rg2
      virtualNetwork:
        id: /subscriptions/subid/resourceGroups/rg2/providers/Microsoft.Network/virtualNetworks/vnet2

The virtualNetwork property references an existing VNet by ID. The networkAcls property defines IP allowlists; only source IPs matching the addressPrefix can access the Bastion service. The empty ipConfigurations array indicates this uses the Developer SKU, which doesn’t require explicit IP configuration.

Beyond these examples

These snippets focus on specific Bastion Host features: standard and private-only deployment modes, and network ACLs for IP-based access control. They’re intentionally minimal rather than full remote access solutions.

The examples reference pre-existing infrastructure such as virtual networks with BastionHostSubnet, and public IP addresses for standard mode. They focus on configuring the Bastion Host rather than provisioning the surrounding network.

To keep things focused, common Bastion patterns are omitted, including:

  • Feature flags (enableFileCopy, enableTunneling, enableShareableLink, enableKerberos)
  • SKU selection and scale units (sku, scaleUnits)
  • Availability zones (zones)
  • Session recording and copy/paste controls

These omissions are intentional: the goal is to illustrate how each Bastion feature is wired, not provide drop-in remote access modules. See the BastionHost resource reference for all available configuration options.

Let's deploy Azure Bastion Hosts

Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.

Try Pulumi Cloud for FREE

Frequently Asked Questions

Configuration & Deployment
What's the difference between standard and developer Bastion Host deployments?
Standard Bastion uses ipConfigurations with a subnet and public IP address, while Developer Bastion uses an empty ipConfigurations array and requires a virtualNetwork reference instead.
How do I create a standard Bastion Host?
Configure ipConfigurations with a subnet (named BastionHostSubnet) and a publicIPAddress reference pointing to an existing public IP resource.
How do I create a Developer Bastion Host?
Set ipConfigurations to an empty array, configure virtualNetwork to reference an existing VNet, and optionally add networkAcls for IP-based access control.
When should I use private-only mode for Bastion Host?
Enable private-only mode by setting enablePrivateOnlyBastion to true when you don’t need a public IP address. In this mode, configure ipConfigurations with only a subnet reference.
Networking & Subnet Requirements
What subnet name is required for Bastion Host?
The subnet must be named BastionHostSubnet, as shown in all deployment examples.
Can I restrict access to my Bastion Host by IP address?
Yes, configure networkAcls with ipRules containing addressPrefix values to restrict access by IP range (shown in Developer Bastion example).
Features & Capabilities
What optional features can I enable on a Bastion Host?
You can enable File Copy (enableFileCopy), IP Connect (enableIpConnect), Kerberos (enableKerberos), Session Recording (enableSessionRecording), Shareable Link (enableShareableLink), and Tunneling (enableTunneling). All features default to false. You can also disable Copy/Paste with disableCopyPaste.
Resource Management
Can I rename a Bastion Host after creation?
No, bastionHostName is immutable and cannot be changed after resource creation. Changing it will force resource replacement.
How do I configure availability zones for my Bastion Host?
Use the zones property to specify a list of availability zones where the resource should be deployed.

Using a different cloud?

Explore networking guides for other cloud providers: