This check flags Azure NSG rules that expose PostgreSQL (port 5432) to 0.0.0.0/0 or any other public source. An open database port invites brute-force attacks and data theft. Lock the rule down to known IPs or a private subnet, or replace public access with a private endpoint.
Databases are not meant to be reachable from the open internet. Yet it happens constantly, usually because someone needed quick remote access during setup and never tightened the rule afterward. The NSG Allows Public PostgreSQL check looks for exactly this: a Network Security Group rule that permits inbound traffic on port 5432 from a public source range.
If you run PostgreSQL on an Azure VM, or you front a managed Postgres instance with subnet-level NSGs, this is a finding you want to clear fast.
What this check detects
Azure Network Security Groups control inbound and outbound traffic at the subnet or network interface level using a prioritized list of allow/deny rules. This check inspects those rules and flags any that meet all of the following conditions:
- Direction: Inbound
- Access: Allow
- Protocol: TCP (or
*) - Destination port: 5432, or a range that includes it (for example
5000-6000or*) - Source: A public range such as
0.0.0.0/0,Internet, or any address space outside your private networks
Note: Azure uses the Internet service tag and the wildcard * as common ways to express "any source." A rule with source * on port 5432 is functionally identical to opening it to the entire internet, even though it does not literally say 0.0.0.0/0.
The check does not care whether anything is actually listening on 5432 right now. The exposure exists the moment the rule is in place, and attackers scan for it whether or not you have a service running.
Why it matters
PostgreSQL on a public IP is one of the most heavily scanned services on the internet. Automated bots sweep entire IP ranges looking for open 5432 ports, then immediately start credential-stuffing against the default postgres superuser and other common usernames.
Here is how a typical compromise plays out:
- A bot finds your open port during a routine internet-wide scan.
- It attempts to connect using default or weak credentials, or exploits a known CVE in your Postgres version.
- Once authenticated, the attacker dumps tables, drops databases for ransom, or pivots deeper into your network using whatever the database can reach.
The fallout is rarely just "lost data." With PostgreSQL specifically, an attacker who gains superuser access can use features like COPY ... FROM PROGRAM to execute arbitrary shell commands on the host, turning a database compromise into full server takeover.
Danger: A superuser connection to PostgreSQL can run COPY ... TO/FROM PROGRAM, which executes OS-level commands as the Postgres service account. A leaked password on a public port is not just a data breach, it is potential remote code execution on the host.
There is also a compliance angle. Frameworks like PCI DSS, SOC 2, HIPAA, and ISO 27001 all expect database tiers to be isolated from public networks. An open 5432 rule is the kind of finding that turns up in an audit and forces an awkward conversation.
How to fix it
The right fix depends on why the port was opened. In almost every case the answer is to stop accepting connections from the internet and instead scope access to a known network. Start by finding the offending rule.
1. Identify the open rule
az network nsg rule list \
--resource-group my-rg \
--nsg-name my-nsg \
--query "[?direction=='Inbound' && access=='Allow' && (destinationPortRange=='5432' || destinationPortRange=='*')].{Name:name, Port:destinationPortRange, Source:sourceAddressPrefix, Priority:priority}" \
--output table
This lists inbound allow rules touching port 5432 along with their source. Anything showing *, 0.0.0.0/0, or Internet is the problem.
2. Restrict the source to known IPs
If you genuinely need remote access for a small set of operators or app servers, narrow the source prefix to those specific addresses instead of the whole internet.
Warning: Changing an NSG rule takes effect immediately. If an application currently connects through this rule from an IP you forget to whitelist, it will lose database connectivity the moment you apply the change. Confirm the full list of legitimate source IPs first.
az network nsg rule update \
--resource-group my-rg \
--nsg-name my-nsg \
--name Allow-Postgres \
--source-address-prefixes 203.0.113.10 198.51.100.0/24 \
--destination-port-ranges 5432
3. Better: remove public access entirely
If the database only needs to be reached by application servers inside Azure, delete the public rule and rely on the default behavior that allows traffic within the same virtual network.
Danger: Deleting an NSG rule is immediate and can cut off live connections. Verify nothing legitimate depends on the public path before running this command.
az network nsg rule delete \
--resource-group my-rg \
--nsg-name my-nsg \
--name Allow-Postgres
4. For Azure Database for PostgreSQL, use a private endpoint
If you are running the managed service rather than self-hosted Postgres, disable public network access and connect through a private endpoint instead. This keeps traffic on the Azure backbone and off the public internet entirely.
# Disable public access on a Flexible Server
az postgres flexible-server update \
--resource-group my-rg \
--name my-pg-server \
--public-network-access Disabled
# Create a private endpoint into your app subnet
az network private-endpoint create \
--resource-group my-rg \
--name pg-private-endpoint \
--vnet-name my-vnet \
--subnet app-subnet \
--private-connection-resource-id "$(az postgres flexible-server show -g my-rg -n my-pg-server --query id -o tsv)" \
--group-id postgresqlServer \
--connection-name pg-connection
Tip: For operator access without exposing the port at all, use Azure Bastion or a jump host inside the VNet. Your laptop never touches 5432 directly, and you keep a clean audit trail of who connected.
How to prevent it from happening again
Manual cleanup fixes today's finding. To stop the rule from reappearing, push the control left into your provisioning pipeline.
Define NSG rules in Terraform with explicit sources
Codifying the rule makes the source range reviewable in a pull request. A reviewer who sees 0.0.0.0/0 on port 5432 can block the merge before it ever reaches Azure.
resource "azurerm_network_security_rule" "postgres" {
name = "Allow-Postgres-Internal"
priority = 200
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "5432"
source_address_prefix = "10.0.1.0/24" # app subnet only
destination_address_prefix = "*"
resource_group_name = azurerm_resource_group.main.name
network_security_group_name = azurerm_network_security_group.db.name
}
Gate it with Azure Policy
Azure Policy can deny the creation of any NSG rule that opens 5432 from the internet, so the guardrail holds even when someone clicks around in the portal.
{
"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": "5432" },
{
"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" }
}
Tip: Run a scan in your CI pipeline with a tool like tfsec or checkov against your Terraform plan. Catching an open 5432 rule in a pull request costs minutes. Catching it after a breach costs a lot more.
Best practices
- Treat the database tier as private by default. Application servers talk to the database; the internet never should. Design subnets so the database sits behind the app tier with no public route.
- Prefer private endpoints over IP allowlists. Allowlisting works, but office IPs change and VPN ranges drift. A private endpoint removes the public attack surface entirely.
- Never use the default
postgressuperuser for applications. Create scoped roles with only the permissions each app needs, so a leaked credential cannot run arbitrary commands. - Require TLS for all connections. Set
require_secure_transporton Azure Database for PostgreSQL so credentials and data are never sent in plaintext. - Audit NSG rules continuously. A one-time cleanup is not enough. Drift happens, so scan on a schedule and alert on any new public database exposure.
- Use named, documented rules. A rule called
Allow-Postgres-AppSubnettells the next engineer what it is for. A rule calledtempinvites them to leave it forever.
Closing port 5432 to the public internet is one of the cheapest, highest-impact changes you can make to your Azure security posture. It takes one command to fix and one policy to keep fixed.

