Back to blog
AzureBest PracticesCloud SecurityDatabasesNetworking

NSG Allows Public SQL Server: Closing Port 1433 on Azure

Learn why an Azure NSG rule exposing SQL Server port 1433 to the internet is dangerous, and how to fix and prevent it with CLI, Terraform, and policy.

TL;DR

This check flags Network Security Group rules that expose SQL Server port 1433 to the public internet. An open database port is a prime target for brute-force and exploitation attacks, so lock the rule down to specific private sources or move access behind a Private Endpoint.

Exposing a SQL Server to the internet is one of those mistakes that looks harmless in a config file and catastrophic in a breach report. The NSG Allows Public SQL Server check (nsg_opensqlserver) inspects your Azure Network Security Groups for inbound rules that permit traffic to TCP port 1433 from any public source. If it finds one, your database engine is reachable by anyone with an internet connection and a port scanner.

This post explains what the check looks for, why an open 1433 is such a juicy target, and exactly how to close the gap and keep it closed.


What this check detects

The check evaluates the inbound security rules attached to your NSGs and raises a finding when a rule meets all of these conditions:

  • Direction: Inbound
  • Access: Allow
  • Protocol: TCP (or *)
  • Destination port: 1433, or a range that includes 1433
  • Source: a public range such as 0.0.0.0/0, *, Internet, or any CIDR that resolves to public internet space

Port 1433 is the default TCP listener for Microsoft SQL Server, used by SQL Server running on VMs, in containers, and by some self-managed deployments. When an NSG rule lets the whole internet reach that port, the check fails.

Note: Azure NSGs use the special service tag Internet to represent all public IP space. A rule with source Internet is just as exposed as one using 0.0.0.0/0, even though it does not look like an obvious wildcard.


Why it matters

A database port open to the internet is not a theoretical risk. Automated scanners sweep the entire IPv4 range looking for listening services on well-known ports, and 1433 is at the top of that list. Once a scanner finds your endpoint, the attack progression is predictable:

  1. Discovery. The open port is logged and added to a target list within minutes of becoming reachable.
  2. Credential attacks. Bots launch brute-force and password-spraying runs against the sa account and common usernames. SQL Server logins are a frequent weak point when strong password policies are not enforced.
  3. Exploitation. Unpatched SQL Server instances are vulnerable to known CVEs. Attackers can use xp_cmdshell or similar features to pivot from a database login to remote code execution on the host.
  4. Impact. Data exfiltration, ransomware deployment, and lateral movement into the rest of your VNet.

The business consequences are equally concrete: regulated data exposure under GDPR, HIPAA, or PCI DSS, plus the cost of incident response and customer notification. Many compliance frameworks explicitly forbid direct internet exposure of database tiers, so a single open rule can also fail an audit.

Danger: Ransomware groups actively scan for exposed SQL Server instances and have used them as initial access for full-environment encryption. If this rule is live in production, treat it as an active incident, not a backlog item.


How to fix it

The goal is simple: nobody on the open internet should be able to reach port 1433. There are three good ways to get there, depending on how the database is consumed.

1. Restrict the source to known networks

If clients connect from a fixed set of locations, replace the public source with the specific CIDR ranges that actually need access. First, find the offending rule.

az network nsg rule list \
  --resource-group myResourceGroup \
  --nsg-name myNsg \
  --query "[?destinationPortRange=='1433' || contains(destinationPortRanges, '1433')]" \
  --output table

Once you know the rule name, update its source to the ranges you trust:

az network nsg rule update \
  --resource-group myResourceGroup \
  --nsg-name myNsg \
  --name Allow-SQL-1433 \
  --source-address-prefixes 203.0.113.0/24 198.51.100.10/32 \
  --priority 200

Warning: Tightening the source will drop any active connections from addresses that are no longer permitted. Confirm the full list of legitimate client IPs before applying, or you may lock out an application tier and cause an outage.

2. Delete the rule entirely

If the rule was added for a one-off task or is no longer needed, remove it. NSGs deny inbound traffic by default, so deleting the allow rule restores a secure posture.

Danger: Deleting an NSG rule is immediate and affects live traffic. Make sure no production workload depends on this path before running the command below.

az network nsg rule delete \
  --resource-group myResourceGroup \
  --nsg-name myNsg \
  --name Allow-SQL-1433

3. Move access behind a Private Endpoint

The most robust fix is to take port 1433 off the public network entirely. For Azure SQL Database, use a Private Endpoint so traffic stays on the Microsoft backbone and your private VNet.

az network private-endpoint create \
  --name sql-private-endpoint \
  --resource-group myResourceGroup \
  --vnet-name myVnet \
  --subnet data-subnet \
  --private-connection-resource-id "/subscriptions/<sub-id>/resourceGroups/myResourceGroup/providers/Microsoft.Sql/servers/mySqlServer" \
  --group-id sqlServer \
  --connection-name sql-connection

After the endpoint is in place, disable public network access on the SQL server:

az sql server update \
  --name mySqlServer \
  --resource-group myResourceGroup \
  --enable-public-network false

Tip: Need temporary access for an engineer or admin task? Use Azure Bastion or a jump host inside the VNet instead of opening 1433. You get an auditable, time-limited path without ever exposing the database to the internet.

Fix it in IaC

If your NSGs are managed with Terraform, scope the rule to a private source rather than the internet:

resource "azurerm_network_security_rule" "sql" {
  name                        = "Allow-SQL-1433"
  priority                    = 200
  direction                   = "Inbound"
  access                      = "Allow"
  protocol                    = "Tcp"
  source_port_range           = "*"
  destination_port_range      = "1433"
  source_address_prefix       = "10.0.1.0/24"   # private app subnet, never "*" or "Internet"
  destination_address_prefix  = "VirtualNetwork"
  resource_group_name         = azurerm_resource_group.main.name
  network_security_group_name = azurerm_network_security_group.main.name
}

How to prevent it from happening again

Fixing one rule is easy. Stopping the next one from slipping in is where the real value is. Push the guardrails as far left as you can.

Block it with Azure Policy

Azure Policy can deny the creation of any NSG rule that opens 1433 to the internet. Use the built-in policy for inbound rules over the internet, or a custom definition like this:

{
  "if": {
    "allOf": [
      { "field": "type", "equals": "Microsoft.Network/networkSecurityGroups/securityRules" },
      { "field": "Microsoft.Network/networkSecurityGroups/securityRules/direction", "equals": "Inbound" },
      { "field": "Microsoft.Network/networkSecurityGroups/securityRules/access", "equals": "Allow" },
      { "field": "Microsoft.Network/networkSecurityGroups/securityRules/destinationPortRange", "equals": "1433" },
      {
        "anyOf": [
          { "field": "Microsoft.Network/networkSecurityGroups/securityRules/sourceAddressPrefix", "equals": "*" },
          { "field": "Microsoft.Network/networkSecurityGroups/securityRules/sourceAddressPrefix", "equals": "0.0.0.0/0" },
          { "field": "Microsoft.Network/networkSecurityGroups/securityRules/sourceAddressPrefix", "equals": "Internet" }
        ]
      }
    ]
  },
  "then": { "effect": "deny" }
}

Assign it at the subscription or management group scope so it applies everywhere, including resources created outside your IaC pipeline.

Gate it in CI/CD

Catch the misconfiguration before it ever reaches Azure by scanning Terraform plans in your pipeline. Tools like Checkov flag public database ports out of the box:

checkov -d ./infra --framework terraform \
  --check CKV_AZURE_10

Tip: Run the scan as a required pull request check. A failing scan blocks the merge, which turns "remember to review the NSG" into something the pipeline enforces automatically.

Monitor continuously

Policy and CI gates cover new changes, but drift happens through manual portal edits and emergency fixes. Lensix continuously evaluates nsg_opensqlserver across your subscriptions so an exposed rule surfaces within minutes rather than at the next audit.


Best practices

  • Treat database tiers as private by default. No data store should accept connections directly from the internet. Front it with a Private Endpoint or keep it on an isolated subnet.
  • Avoid wildcard sources. A source of * or Internet on any sensitive port is almost always a mistake. Be explicit about who can connect.
  • Change the default port where practical. Moving off 1433 is not a security control on its own, but it does cut down the noise from automated scanners that only probe defaults.
  • Enforce strong authentication. Use Microsoft Entra ID authentication for SQL, disable or rename the sa account, and require strong passwords on any remaining SQL logins.
  • Enable logging. Turn on NSG flow logs and SQL audit logs so you can detect probing and failed login attempts early.
  • Apply least privilege at the network layer. Scope every inbound rule to the narrowest source and port range that the workload actually needs, then review them regularly.

An open SQL Server port is one of the highest-signal findings in any cloud environment because the path from exposure to compromise is short and well-trodden. Close the rule, move the database behind a private path, and let policy and pipeline checks keep it that way.