This check flags Azure SQL Server firewall rules that allow connections from public IP ranges, which exposes your database to the open internet. Remove broad rules like 0.0.0.0 to 255.255.255.255, lock access down to known IPs or private endpoints, and enforce the policy in IaC so it cannot creep back in.
Azure SQL Server sits behind a server-level firewall that decides which IP addresses are allowed to open a connection. It is one of the few protections standing between your data and the rest of the internet. When a firewall rule permits public IP ranges, anyone who can guess or steal a set of credentials can reach the login surface from anywhere. This check catches exactly that: a SQL Server with a firewall rule that opens the door to public address space.
What this check detects
The sql_publicfirewall check inspects the server-level firewall rules on each Azure SQL logical server and looks for rules whose start and end IP range covers public address space. The most obvious offender is a rule spanning the entire IPv4 range:
{
"name": "AllowAll",
"startIpAddress": "0.0.0.0",
"endIpAddress": "255.255.255.255"
}
Two patterns commonly trigger this finding:
- A wide-open custom rule with a broad start and end range that includes public IPs.
- The "Allow Azure services" toggle, which creates a rule with start and end IP both set to
0.0.0.0. This special rule permits traffic from any Azure resource, including resources in other tenants, not just your own subscription.
Note: The 0.0.0.0 to 0.0.0.0 rule does not mean "allow everything" in the literal sense. Azure treats it as "allow connections from Azure services and resources." The catch is that this includes the entire Azure backbone, so a workload running in someone else's subscription can still attempt to connect.
Database-level firewall rules exist too, but server-level rules are the ones that govern who can even reach the server, so they carry the most weight here.
Why it matters
A public firewall rule turns your SQL Server into an internet-facing service. That changes the threat model entirely.
Credential attacks become viable
SQL authentication uses a username and password. With the firewall wide open, attackers can run password spraying and brute force attempts against your server endpoint, which follows a predictable naming pattern: yourserver.database.windows.net. These names are easy to enumerate. Once an attacker can reach the login surface, weak or reused credentials are the only thing left to stop them.
Exposed servers get found fast
Internet scanners catalog Azure SQL endpoints continuously. A server reachable from public ranges will show up in those scans within hours, not weeks. There is no obscurity to hide behind.
Warning: Many breaches involving cloud databases come down to two combined mistakes: a public firewall rule plus a credential that was committed to a repo, leaked in a log, or reused from another service. Either one alone is recoverable. Together they are an open door.
Compliance impact
Frameworks like PCI DSS, HIPAA, SOC 2, and the CIS Azure Foundations Benchmark all expect databases holding sensitive data to be isolated from public networks. A public firewall rule is a direct finding in most audits and often blocks certification.
How to fix it
The goal is to remove broad public rules and replace them with tightly scoped access. Start by listing the existing rules so you know what you are working with.
az sql server firewall-rule list \
--resource-group myResourceGroup \
--server myserver \
--output table
Step 1: Remove the offending rule
Danger: Deleting a firewall rule cuts off any client currently relying on that rule. Before you remove an allow-all rule, confirm which application servers, jump hosts, or CI runners need access and have replacement rules ready. Dropping access in production can break live traffic.
Delete a named allow-all rule:
az sql server firewall-rule delete \
--resource-group myResourceGroup \
--server myserver \
--name AllowAll
If the "Allow Azure services" setting is enabled, turn it off by setting public network access policy and removing that rule. In the Azure Portal, go to your SQL Server, open Networking, and under Exceptions uncheck Allow Azure services and resources to access this server.
Step 2: Add narrowly scoped rules for the addresses that need access
az sql server firewall-rule create \
--resource-group myResourceGroup \
--server myserver \
--name AllowAppServer \
--start-ip-address 203.0.113.25 \
--end-ip-address 203.0.113.25
Use a single IP or the smallest range your client requires. Avoid CIDR-sized ranges unless you genuinely control every address in them.
Step 3: Prefer private connectivity where possible
For workloads running inside Azure, the strongest fix is to stop relying on the public endpoint entirely. Disable public network access and use a Private Endpoint so traffic stays on the Microsoft backbone and your VNet.
# Disable public network access on the server
az sql server update \
--resource-group myResourceGroup \
--name myserver \
--enable-public-network false
Then create a Private Endpoint that maps the server into your virtual network:
az network private-endpoint create \
--resource-group myResourceGroup \
--name sql-pe \
--vnet-name myVnet \
--subnet mySubnet \
--private-connection-resource-id "/subscriptions/<sub-id>/resourceGroups/myResourceGroup/providers/Microsoft.Sql/servers/myserver" \
--group-id sqlServer \
--connection-name sql-pe-conn
Tip: Private Endpoints need a Private DNS Zone (privatelink.database.windows.net) linked to your VNet so that the server hostname resolves to the private IP. Without it, clients will keep resolving to the public endpoint and the connection will fail or fall back to public routing.
How to prevent it from happening again
Manual cleanup fixes today's problem. Policy and IaC stop it from coming back next sprint.
Define firewall rules in infrastructure as code
Keep the server's access model in version control so every change goes through review. Here is a Terraform example that disables public access and defines a single allowed range:
resource "azurerm_mssql_server" "main" {
name = "myserver"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
version = "12.0"
administrator_login = "sqladmin"
administrator_login_password = var.sql_admin_password
public_network_access_enabled = false
}
resource "azurerm_mssql_firewall_rule" "app" {
name = "AllowAppServer"
server_id = azurerm_mssql_server.main.id
start_ip_address = "203.0.113.25"
end_ip_address = "203.0.113.25"
}
Enforce it with Azure Policy
Azure Policy can deny or audit servers that expose public access. Assign the built-in definition Public network access on Azure SQL Database should be disabled, or use a custom policy with a deny effect so the bad configuration never gets created in the first place.
az policy assignment create \
--name deny-public-sql \
--display-name "Deny public network access on Azure SQL" \
--policy "/providers/Microsoft.Authorization/policyDefinitions/<definition-id>" \
--scope "/subscriptions/<sub-id>"
Add a CI/CD gate
Run a scan against your Terraform or Bicep before it merges. Tools like Checkov, tfsec, or a Lensix scan in your pipeline can fail the build when a firewall rule covers public ranges or when public network access is left enabled.
Tip: Pair a deny policy at the subscription scope with a continuous Lensix check. The policy blocks new mistakes at deploy time, and the check catches drift from changes made outside your IaC, like someone clicking a box in the portal during an incident and forgetting to revert it.
Best practices
- Default to private. Treat public network access as something you justify, not something you remove later. Disable it unless a specific client genuinely needs the public endpoint.
- Use Private Endpoints for Azure-hosted workloads. They remove the public endpoint from the equation entirely and keep traffic off the internet.
- Scope rules tightly. A single IP beats a range. A range beats an allow-all. Never use
0.0.0.0to255.255.255.255. - Prefer Microsoft Entra authentication over SQL auth. It gives you MFA, conditional access, and centralized identity management, which limits the damage of a leaked password.
- Enable auditing and threat detection. Turn on Microsoft Defender for SQL so you get alerts on suspicious login attempts and potential SQL injection.
- Review firewall rules on a schedule. Temporary rules added during debugging have a way of becoming permanent. Audit them regularly and remove anything no one can explain.
A public SQL firewall rule is one of those misconfigurations that looks harmless until the day it isn't. Closing it down, moving to private connectivity, and gating the configuration in your pipeline turns a recurring risk into a settled decision.

