Azure security

How to find NSG rules open to the internet in Azure

Find NSG rules open to the internet with Azure PowerShell, review them in Azure Resource Graph Explorer, or run Altivane's Azure Network Rules Open to the Internet report.

By the Published Updated

To find network security group (NSG) rules open to the internet in Azure, query Azure Resource Graph for inbound Allow rules whose source is any address, the Internet service tag or 0.0.0.0/0, then look for rules that open SSH, RDP or other management ports. The list shows what rules allow, not what can be reached: a rule with a lower priority number that denies the same traffic wins.

Choose the method that fits the job
MethodBest fit
PowerShellA repeatable CSV across enabled subscriptions, with management ports flagged.
Admin centreA quick review in Azure Resource Graph Explorer, or 1 group's rules, without installing tools.
AltivaneRecommended for recurring reviews across readable subscriptions, with management ports marked in a workbook to keep.

Before you start

Use an Azure account with read access to the subscriptions you want to check. The built-in Reader role, assigned at the subscription or a parent scope, is enough for Resource Graph queries. The PowerShell example uses Az.Accounts and Az.ResourceGraph. Microsoft's Resource Graph prerequisites do not list a separate product licence.

The query expands each group's custom rules (securityRules) and keeps those that are Inbound, Allow and have a source of *, Internet, 0.0.0.0/0 or any, alone or in a list. Other sources, such as a very wide address range, are not counted. The script flags a rule when its ports include 22 (SSH), 3389 (RDP), 5985 or 5986 (WinRM), as a port, a range or *.

The examples only read Azure and export a CSV. Resource Graph is near real-time and eventually consistent, so a recent change may not show yet. The script paginates beyond 1,000 results, and its finally block disconnects the account and removes its local contexts, so you may need to sign in again.

Option 1

Find NSG rules open to the internet with PowerShell

Install the modules once, then run the script, which adds a management port flag to each rule.

PowerShell
Install-Module -Name Az.Accounts, Az.ResourceGraph -Repository PSGallery -Scope CurrentUser
PowerShell
$ErrorActionPreference = 'Stop'
Connect-AzAccount -ErrorAction Stop
try {
    $subscriptionIds = @(
        Get-AzSubscription |
        Where-Object State -eq 'Enabled' |
        Select-Object -ExpandProperty Id
    )
    if ($subscriptionIds.Count -eq 0) {
        Write-Output 'No enabled subscriptions are available to this account.'
        return
    }
    $query = @'
Resources
| where type =~ 'microsoft.network/networksecuritygroups'
| mv-expand rule = properties.securityRules limit 2000
| where rule.properties.direction =~ 'Inbound' and rule.properties.access =~ 'Allow'
| extend srcOne = tostring(rule.properties.sourceAddressPrefix),
    srcMany = tostring(rule.properties.sourceAddressPrefixes)
| where srcOne in~ ('*', 'Internet', '0.0.0.0/0', 'any')
    or srcMany contains '"*"' or srcMany contains '"Internet"'
    or srcMany contains '"0.0.0.0/0"' or srcMany contains '"any"'
| project id, name, subscriptionId, resourceGroup, location,
    ruleName = tostring(rule.name),
    priority = toint(rule.properties.priority),
    protocol = tostring(rule.properties.protocol),
    port = tostring(rule.properties.destinationPortRange),
    portList = rule.properties.destinationPortRanges,
    source = srcOne, sourceList = rule.properties.sourceAddressPrefixes
| order by subscriptionId asc, name asc, priority asc
'@
    function Get-AzGraphRows {
        param([string]$Query, [string[]]$SubscriptionIds)
        $results = [System.Collections.Generic.List[object]]::new()
        $skipToken = $null
        do {
            $arguments = @{
                Query = $Query
                Subscription = $SubscriptionIds
                First = 1000
            }
            if ($skipToken) { $arguments.SkipToken = $skipToken }
            $page = Search-AzGraph @arguments
            foreach ($record in $page.Data) { $results.Add($record) }
            $skipToken = $page.SkipToken
        } while ($skipToken)
        return $results.ToArray()
    }
    function Test-ManagementPort {
        param([string[]]$Ports)
        foreach ($port in $Ports) {
            if ($port -eq '*') { return $true }
            if ($port -match '^(\d+)(?:-(\d+))?$') {
                $low = [int]$Matches[1]
                $high = if ($Matches[2]) { [int]$Matches[2] } else { $low }
                foreach ($managed in 22, 3389, 5985, 5986) {
                    if ($managed -ge $low -and $managed -le $high) { return $true }
                }
            }
        }
        return $false
    }
    $rows = @(Get-AzGraphRows -Query $query -SubscriptionIds $subscriptionIds)
    if ($rows.Count -eq 0) {
        Write-Output 'No inbound rules open to the internet were found in the indexed groups.'
    } else {
        $report = foreach ($row in $rows) {
            $ports = @(@($row.port) + @($row.portList) | Where-Object { $_ })
            $sources = @(@($row.source) + @($row.sourceList) | Where-Object { $_ })
            [pscustomobject]@{
                Subscription = $row.subscriptionId
                ResourceGroup = $row.resourceGroup
                NetworkSecurityGroup = $row.name
                Location = $row.location
                Rule = $row.ruleName
                Priority = $row.priority
                Protocol = $row.protocol
                Ports = $ports -join ', '
                Source = $sources -join ', '
                ManagementPort = if (Test-ManagementPort $ports) { 'Yes' } else { 'No' }
                ResourceId = $row.id
            }
        }
        $report | Sort-Object @{ Expression = 'ManagementPort'; Descending = $true },
            Subscription, NetworkSecurityGroup, Priority |
            Export-Csv -Path './nsg-rules-open-to-internet.csv' -NoTypeInformation -Encoding utf8
        $management = @($report | Where-Object ManagementPort -eq 'Yes').Count
        Write-Output "$($report.Count) rules saved to nsg-rules-open-to-internet.csv."
        Write-Output "$management of them open a management port."
    }
} finally {
    Disconnect-AzAccount | Out-Null
}

The CSV has 1 row for each rule, with its group, subscription, location, priority, protocol, ports, sources, management port flag and resource ID. Rules with several ports or sources stay 1 row, and management port rows come first. An empty result produces a message, not an empty CSV.

To narrow the search, keep only the subscription IDs you want in $subscriptionIds. To widen it, remove the source test from the query to list every inbound Allow rule. The query keeps id because Azure's continuation token requires it for pagination.

Option 2

Review open NSG rules in Azure Resource Graph Explorer

  1. Sign in to the Azure portal with read access to the subscriptions.
  2. Search for resource graph > choose Resource Graph Explorer.
  3. To change its scope, choose Directory, then choose the directory, management group or subscription to query.
  4. Paste this query in Query 1 and choose Run query:
    KQL
    Resources
    | where type =~ 'microsoft.network/networksecuritygroups'
    | mv-expand rule = properties.securityRules limit 2000
    | where rule.properties.direction =~ 'Inbound' and rule.properties.access =~ 'Allow'
    | extend srcOne = tostring(rule.properties.sourceAddressPrefix),
        srcMany = tostring(rule.properties.sourceAddressPrefixes)
    | where srcOne in~ ('*', 'Internet', '0.0.0.0/0', 'any')
        or srcMany contains '"*"' or srcMany contains '"Internet"'
        or srcMany contains '"0.0.0.0/0"' or srcMany contains '"any"'
    | project id, name, subscriptionId, resourceGroup, location,
        ruleName = tostring(rule.name),
        priority = toint(rule.properties.priority),
        protocol = tostring(rule.properties.protocol),
        port = tostring(rule.properties.destinationPortRange),
        portList = rule.properties.destinationPortRanges,
        source = srcOne, sourceList = rule.properties.sourceAddressPrefixes
    | order by subscriptionId asc, name asc, priority asc
  5. Review the rules in Results, looking in port and portList for 22, 3389, 5985, 5986, a range that includes them, or *. Download as CSV keeps the rows, up to 55,000 records.

Check 1 group

Search for Network security group > choose Network security groups > select the group > choose Inbound security rules.

Explorer suits an occasional look; use PowerShell for the management port flag or a repeatable CSV. The group page suits 1 group you already suspect.

Common questions

Does a rule in the result mean the virtual machine is reachable from the internet?

Not always. Azure processes rules in priority order, lowest number first, and stops at the first match, so a deny rule with a lower number wins. Security admin rules from Azure Virtual Network Manager are evaluated before NSG rules. Treat each row as a rule to review, then check the effective rules for the machine.

What should replace an open RDP or SSH rule?

Microsoft advises never allowing RDP (3389) or SSH (22) inbound from 0.0.0.0/0, and offers Azure Bastion, a point-to-site VPN or just-in-time VM access instead. This guide only reads rules, so confirm who uses a rule before changing it.

Which public IP addresses are not attached to anything?

That is a separate check. See how to find unused public IP addresses in Azure.

Microsoft references

Technical references checked on 7 October 2026. The example scripts have been reviewed and syntax checked; they have not been run against a live tenant.

Talk to us about Altivane

Questions, or pricing? Get in touch.

Contact us