This blog post will show you how to set the required NSG Inbound security rules on a target VM subnet for Azure Bastion connectivity with an Azure PowerShell script.
Azure Bastion is an Azure PaaS service that you provision inside a virtual network (VNet), preferably the HUB VNet. It allows you to securely connect to your Azure virtual machines (VMs) via RDP or SSH, and this is done directly from the Azure portal over SSL. When you use Azure Bastion, your VMs do not need a Public IP Address (PIP), agent, or special client software to be able to connect to them.
If you want to read some more about Azure Bastion, you can do so via the following Microsoft Docs link: Azure Bastion documentation
If you want, you can also look at my previous blog posts, which can help you securely deploy and configure Azure Bastion and all associated resources: Azure Bastion: Azure PowerShell deployment script and Azure Bastion: Set the minimum required roles to access a virtual machine
When using Azure Bastion in a production environment, you should always apply a Network Security Group (NSG) on the AzureBastionSubnet with all the required inbound and outbound security rules, as shown in the screenshots below.


Next to that, and especially when you want to implement a good defense-in-depth strategy, you should also apply an NSG to all the subnets on which a virtual machine (VM) needs to be accessed by Azure Bastion.
On that NSG, the following inbound security rule(s) should be provided:
- Inbound (ingress) traffic from Azure Bastion on ports 3389 (RDP) or 22 (SSH). This is because Azure Bastion will reach the targeted VM over its private IP, and depending on the type of VM (Windows or Linux), port 3389 or 22 will be used. Therefore, as a best practice, you should only add the Azure Bastion subnet (AzureBastionSubnet CIDR IP address in IPv4) as the source in this rule.
- Block all other inbound traffic with a lower priority number than the default inbound rules (for example, with a priority of 900).
Because configuring this NSG rule manually can be quite a long and error-prone task, I wrote the below Azure PowerShell script, which does all of the following:
- Check if the PowerShell window is running as Administrator (when not running from Cloud Shell), otherwise the Azure PowerShell script will be exited.
- Suppress breaking change warning messages.
- Create the Target VM Subnet NSG if it does not exist.
- Store the Target VM Subnet NSG in a variable.
- Add inbound rule 1 to allow ingress RDP traffic from AzureBastionSubnet to the Target VM Subnet NSG, if it not already exists.
- Add inbound rule 2 to allow ingress SSH traffic from AzureBastionSubnet to the Target VM Subnet NSG, if it not already exists.
- Add inbound rule 3 to deny all other inbound virtual network traffic to the Target VM Subnet NSG, if it not already exists.
- Update the NSG with the new inbound rules.
To use the script, copy and save it as Set-AzureBastion-NSG-Inbound-security-rules-on-Target-VM-Subnet or download it from GitHub. Then, before using the script, adjust all variables to your needs. Afterwards, you can run the customized script from Windows Terminal, Visual Studio Code, or Windows PowerShell. Or you can simply run it from Cloud Shell.
Prerequisites
- An Azure subscription.
- An Azure Administrator account with the necessary RBAC roles.
- An existing HUB VNet with the AzureBastionSubnet
- An existing spoke VNet with some subnets.
- An existing Azure Bastion host.
- At least Azure Az PowerShell module version 5.9.0 and Az.Network module version 4.7.0
- Change all the variables in the script where needed to fit your needs (you can find an adjusted example in one of the screenshots below).




Azure PowerShell script
If you are not running the script from Cloud Shell, don’t forget to sign in with the Connect-AzAccount cmdlet to connect your Azure account. If you have multiple Azure subscriptions, make sure to select the appropriate subscription using the Set-AzContext cmdlet before executing the script.

When downloaded or copied, run the script with the following parameters:
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Connect-AzAccount
Get-AzSubscription -SubscriptionId <"your Azure Subscirption ID here"> -TenantId <"your Tenant ID here"> | Set-AzContext
.\Set-AzureBastion-NSG-Inbound-security-rules-on-Target-VM-Subnet <"your NSG name here"> <"your NSG resource group name here">
Example -> .\Set-AzureBastion-NSG-Inbound-security-rules-on-Target-VM-Subnet nsg-tst-myh-app-01 rg-tst-myh-networking-01
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
<#
.SYNOPSIS
A script used to set the required NSG Inbound security rules on a Target VM subnet for Azure Bastion connectivity.
.DESCRIPTION
A script used to set the required NSG Inbound security rules on a Target VM subnet for Azure Bastion connectivity.
The script will do all of the following:
Check if the PowerShell window is running as Administrator (when not running from Cloud Shell), otherwise the Azure PowerShell script will be exited.
Suppress breaking change warning messages.
Create the Target VM Subnet NSG if it does not exist.
Store the Target VM Subnet NSG in a variable.
Add inbound rule 1 to allow ingress RDP traffic from AzureBastionSubnet to the Target VM Subnet NSG, if it not already exists.
Add inbound rule 2 to allow ingress SSH traffic from AzureBastionSubnet to the Target VM Subnet NSG, if it not already exists.
Add inbound rule 3 to deny all other inbound virtual network traffic to the Target VM Subnet NSG, if it not already exists.
Update the NSG with the new inbound rules.
.NOTES
Filename: Set-AzureBastion-NSG-Inbound-security-rules-on-Target-VM-Subnet.ps1
Created: 10/08/2022
Last modified: 10/08/2022
Author: Wim Matthyssen
Version: 1.0
PowerShell: Azure Cloud Shell or Azure PowerShell
Requires: PowerShell Az (v5.9.0) and Az.Network (v4.7.0)
Action: Change variables were needed to fit your needs.
Disclaimer: This script is provided "As Is" with no warranties.
.EXAMPLE
Connect-AzAccount
Get-AzTenant (if not using the default tenant)
Set-AzContext -tenantID "<xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxx>" (if not using the default tenant)
Set-AzContext -Subscription "<SubscriptionName>" (if not using the default subscription)
.\Set-AzureBastion-NSG-Inbound-security-rules-on-Target-VM-Subnet <"your NSG name here"> <"your NSG resource group name here">
-> .\Set-AzureBastion-NSG-Inbound-security-rules-on-Target-VM-Subnet nsg-tst-myh-app-01 rg-tst-myh-networking-01
.LINK
https://wmatthyssen.com/2022/08/11/azure-bastion-set-azure-bastion-nsg-inbound-security-rules-on-the-target-vm-subnet-with-azure-powershell/
#>
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Parameters
param(
# $nsgNameTargetVMSubnet -> Name of the NSG associated to the Target VM Subnet
[parameter(Mandatory =$true)][ValidateNotNullOrEmpty()] [string] $nsgNameTargetVMSubnet,
# $rgNameNetworking -> Name of the resource group holding the NSG
[parameter(Mandatory =$true)][ValidateNotNullOrEmpty()] [string] $rgNameNetworking
)
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Variables
$inboundRule1Name = #<your RDP inbound rule here> The name of the RDP inbound rule. Example: "Allow_RDP_3389_AzureBastionSubnet_Inbound"
$inboundRule2Name = #<your SSH inbound rule here> The name of the SSH inbound rule. Example: "Allow_SSH_22_AzureBastionSubnet_Inbound"
$inboundRule3Name = #<your deny all other traffic inbound rule here> The name of the deny all other traffic inbound rule. Example: "Deny_Any_Other_Inbound_Traffic_Inbound"
$inboundRule1Priority = #<your RDP inbound rule priority here> The priority of the RDP inbound rule. Example: "100"
$inboundRule2Priority = #<your SSH inbound rule priority here> The priority of the SSH inbound rule. Example: "110"
$inboundRule3Priority = #<your deny all other traffic inbound rule priority here> The priority of the deny all other traffic inbound rule. Example: "900"
$bastionSubnetAddressRange = "10.1.1.128/26"
$global:currenttime= Set-PSBreakpoint -Variable currenttime -Mode Read -Action {$global:currenttime= Get-Date -UFormat "%A %m/%d/%Y %R"}
$foregroundColor1 = "Red"
$foregroundColor2 = "Yellow"
$writeEmptyLine = "`n"
$writeSeperatorSpaces = " - "
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Check if PowerShell runs as Administrator (when not running from Cloud Shell), otherwise exit the script
if ($PSVersionTable.Platform -eq "Unix") {
Write-Host ($writeEmptyLine + "# Running in Cloud Shell" + $writeSeperatorSpaces + $currentTime)`
-foregroundcolor $foregroundColor1 $writeEmptyLine
## Start script execution
Write-Host ($writeEmptyLine + "# Script started. Without any errors, it will need around 1 minute to complete" + $writeSeperatorSpaces + $currentTime)`
-foregroundcolor $foregroundColor1 $writeEmptyLine
} else {
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
$isAdministrator = $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
## Check if running as Administrator, otherwise exit the script
if ($isAdministrator -eq $false) {
Write-Host ($writeEmptyLine + "# Please run PowerShell as Administrator" + $writeSeperatorSpaces + $currentTime)`
-foregroundcolor $foregroundColor1 $writeEmptyLine
Start-Sleep -s 3
exit
}
else {
## If running as Administrator, start script execution
Write-Host ($writeEmptyLine + "# Script started. Without any errors, it will need around 1 minute to complete" + $writeSeperatorSpaces + $currentTime)`
-foregroundcolor $foregroundColor1 $writeEmptyLine
}
}
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Suppress breaking change warning messages
Set-Item Env:\SuppressAzurePowerShellBreakingChangeWarnings "true"
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
# Create the Target VM Subnet NSG if it does not exist
try {
Get-AzNetworkSecurityGroup -Name $nsgNameTargetVMSubnet -ResourceGroupName $rgNameNetworking -ErrorAction Stop | Out-Null
} catch {
New-AzNetworkSecurityGroup -Name $nsgNameTargetVMSubnet -ResourceGroupName $rgNameNetworking -Location $region -Force | Out-Null
}
Write-Host ($writeEmptyLine + "# NSG $nsgNameTargetVMSubnet available" + $writeSeperatorSpaces + $currentTime)`
-foregroundcolor $foregroundColor2 $writeEmptyLine
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Store the Target VM Subnet NSG in a variable
$nsg = Get-AzNetworkSecurityGroup -Name $nsgNameTargetVMSubnet -ResourceGroupName $rgNameNetworking
Write-Host ($writeEmptyLine + "# NSG $nsgNameTargetVMSubnet stored in a variable" + $writeSeperatorSpaces + $currentTime)`
-foregroundcolor $foregroundColor2 $writeEmptyLine
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Add inbound rule 1 to allow Ingress RDP traffic from AzureBastionSubnet to the Target VM Subnet NSG, if it not already exists
$inboundRule1Exists = $nsg | Get-AzNetworkSecurityRuleConfig -Name $inboundRule1Name -ErrorAction SilentlyContinue
if ($inboundRule1Exists) {
Write-Host ($writeEmptyLine + "# Inbound security rule $inboundRule1Name already exists" + $writeSeperatorSpaces + $currentTime) `
-foregroundcolor $foregroundColor2 $writeEmptyLine
} else {
$nsg | Add-AzNetworkSecurityRuleConfig -Name $inboundRule1Name -Description $inboundRule1Name -Access Allow -Protocol TCP -Direction Inbound -Priority $inboundRule1Priority `
-SourceAddressPrefix $bastionSubnetAddressRange -SourcePortRange * -DestinationAddressPrefix * -DestinationPortRange 3389 | Out-Null
Write-Host ($writeEmptyLine + "# Inbound security rule added to allow RDP from AzureBastionSubnet to NSG $nsgNameTargetVMSubnet" + $writeSeperatorSpaces + $currentTime) `
-foregroundcolor $foregroundColor2 $writeEmptyLine
}
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Add inbound rule 2 to allow Ingress SSH traffic from AzureBastionSubnet to the Target VM Subnet NSG, if it not already exists
$inboundRule2Exists = $nsg | Get-AzNetworkSecurityRuleConfig -Name $inboundRule2Name -ErrorAction SilentlyContinue
if ($inboundRule2Exists) {
Write-Host ($writeEmptyLine + "# Inbound security rule $inboundRule2Name already exists" + $writeSeperatorSpaces + $currentTime) `
-foregroundcolor $foregroundColor2 $writeEmptyLine
} else {
$nsg | Add-AzNetworkSecurityRuleConfig -Name $inboundRule2Name -Description $inboundRule2Name -Access Allow -Protocol TCP -Direction Inbound -Priority $inboundRule2Priority `
-SourceAddressPrefix $bastionSubnetAddressRange -SourcePortRange * -DestinationAddressPrefix * -DestinationPortRange 22 | Out-Null
Write-Host ($writeEmptyLine + "# Inbound security rule added to allow SSH from AzureBastionSubnet to NSG $nsgNameTargetVMSubnet" + $writeSeperatorSpaces + $currentTime) `
-foregroundcolor $foregroundColor2 $writeEmptyLine
}
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Add inbound rule 3 to deny all other inbound virtual network traffic to the Target VM Subnet NSG, if it not already exists
$inboundRule3Exists = $nsg | Get-AzNetworkSecurityRuleConfig -Name $inboundRule3Name -ErrorAction SilentlyContinue
if ($inboundRule3Exists) {
Write-Host ($writeEmptyLine + "# Inbound security rule $inboundRule3Name already exists" + $writeSeperatorSpaces + $currentTime) `
-foregroundcolor $foregroundColor2 $writeEmptyLine
} else {
$nsg | Add-AzNetworkSecurityRuleConfig -Name $inboundRule3Name -Description $inboundRule3Name -Access Deny -Protocol * -Direction Inbound -Priority $inboundRule3Priority `
-SourceAddressPrefix * -SourcePortRange * -DestinationAddressPrefix * -DestinationPortRange * | Out-Null
Write-Host ($writeEmptyLine + "# Inbound security rule added to deny any other traffic to NSG $nsgNameTargetVMSubnet" + $writeSeperatorSpaces + $currentTime) `
-foregroundcolor $foregroundColor2 $writeEmptyLine
}
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Update the NSG with the new inbound rules
$nsg | Set-AzNetworkSecurityGroup | Out-Null
Write-Host ($writeEmptyLine + "# NSG $nsgNameTargetVMSubnet updated" + $writeSeperatorSpaces + $currentTime) `
-foregroundcolor $foregroundColor2 $writeEmptyLine
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Write script completed
Write-Host ($writeEmptyLine + "# Script completed" + $writeSeperatorSpaces + $currentTime)`
-foregroundcolor $foregroundColor1 $writeEmptyLine
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Connect-AzAccount
Get-AzSubscription -SubscriptionId <"your Azure Subscirption ID here"> -TenantId <"your Tenant ID here"> | Set-AzContext
.\Set-AzureBastion-NSG-Inbound-security-rules-on-Target-VM-Subnet <"your NSG name here"> <"your NSG resource group name here">
Example -> .\Set-AzureBastion-NSG-Inbound-security-rules-on-Target-VM-Subnet nsg-tst-myh-app-01 rg-tst-myh-networking-01
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


I hope this Azure PowerShell script is useful for you and provides you with a good starting point to use Azure Bastion in your Azure environment.
If you have any questions or recommendations about it, feel free to contact me through my Twitter handle (@wmatthyssen) or to just leave a comment.
Pingback: Azure Bastion: Upgrade Basic SKU to Standard SKU with Azure PowerShell – Wim Matthyssen
Pingback: Azure Bastion: Switch Standard SKU to Basic SKU with Azure PowerShell – Wim Matthyssen