Skip to main content
emnode
Compliance Medium severity MCSB NS-2

Microsoft Defender for Cloud · Azure Database for PostgreSQL

Public network access should be disabled for PostgreSQL servers

Written and reviewed by Emnode · Last reviewed

What does the recommendation "Public network access should be disabled for PostgreSQL servers" check?

Flags any Azure Database for PostgreSQL Single Server whose 'publicNetworkAccess' property is set to Enabled, meaning the server is reachable over a public endpoint and connections are governed only by IP and virtual-network firewall rules. The check is scoped to the Single Server deployment model (Microsoft.DBforPostgreSQL/servers); the equivalent Flexible Server recommendation is a separate control. It looks only at whether the public endpoint exists, not at how permissive your firewall rules are, and it does not verify that a private endpoint or VNet integration has actually been configured as the replacement path.

Why does "Public network access should be disabled for PostgreSQL servers" matter?

While public access is on, your database listens on an internet-facing endpoint and the only barrier is a firewall rule list that is easy to widen by accident, for example an 'allow Azure services' entry (a special rule whose start and end IP are both 0.0.0.0) that quietly opens the server to every tenant in the cloud, or a 0.0.0.0 to 255.255.255.255 'allow all internet' rule that exposes it to the entire public internet. That exposes credential-stuffing, brute-force and exploitation attempts to anyone on the internet, and automated scanners find newly exposed database ports within minutes. A single weak password or unpatched flaw can then turn into exfiltration of customer data, breach-notification duties and regulatory penalties under regimes such as GDPR. Setting publicNetworkAccess to Disabled removes the public endpoint entirely, so the server can only be reached through a private endpoint inside your virtual network and every IP-based firewall login is denied, which shrinks the attack surface to traffic you explicitly route.

How do I fix "Public network access should be disabled for PostgreSQL servers"?

  1. On the server in the Azure portal, open Networking (or Connection security on older blades) and set 'Deny Public Network Access' to Yes, after confirming a private endpoint or VNet path is in place for legitimate clients.
  2. Via CLI, run 'az postgres server update --resource-group <rg> --name <server> --public-network-access Disabled' (Single Server uses 'az postgres server', not 'az postgres flexible-server'); the allowed values are Enabled and Disabled.
  3. To enforce this at scale, assign the built-in policy by its display name rather than a hardcoded GUID: pid=$(az policy definition list --query "[?displayName=='Public network access should be disabled for PostgreSQL servers'].name" -o tsv); az policy assignment create --name deny-pg-public --policy "$pid" --scope <scope>.

Remediation script · bash

# 1. Lock the wire and the network on a production PostgreSQL flexible server.
RG=db-prod-rg
SRV=ordersprod-pg

# Enforce encrypted connections (dynamic, no restart needed).
az postgres flexible-server parameter set \
  --resource-group "$RG" --server-name "$SRV" \
  --source user-override \
  --name require_secure_transport --value ON

# Pin the minimum TLS version.
az postgres flexible-server parameter set \
  --resource-group "$RG" --server-name "$SRV" \
  --source user-override \
  --name ssl_min_protocol_version --value TLSv1.2

# Disable public network access.
az postgres flexible-server update \
  --resource-group "$RG" --name "$SRV" \
  --public-access Disabled

# 2. Turn on the pgaudit trail and connection throttling.
for p in "pgaudit.log=role,ddl,misc" \
         "pgaudit.log_client=ON" \
         "pgaudit.log_level=log" \
         "pgaudit.log_statement=on" \
         "pgaudit.log_statement_once=on" \
         "connection_throttle.enable=on"; do
  az postgres flexible-server parameter set \
    --resource-group "$RG" --server-name "$SRV" \
    --source user-override \
    --name "${p%%=*}" --value "${p#*=}"
done

# 3. Provision a Microsoft Entra administrator and disable password sign-in.
az postgres flexible-server ad-admin create \
  --resource-group "$RG" --server-name "$SRV" \
  --display-name "dba-team" \
  --object-id "$(az ad group show --group dba-team --query id -o tsv)" \
  --type Group
az postgres flexible-server update \
  --resource-group "$RG" --name "$SRV" \
  --microsoft-entra-auth Enabled --password-auth Disabled

# 4. Ratchet it shut: deny any future PostgreSQL flexible server with public access.
# Look the built-in policy up by its EXACT display name, never paste a guessed GUID.
PID=$(az policy definition list \
  --query "[?displayName=='Public network access should be disabled for PostgreSQL flexible servers'].name" \
  -o tsv)
az policy assignment create \
  --name deny-public-pg-flex \
  --policy "$PID" \
  --params '{"effect":{"value":"Deny"}}' \
  --scope "/subscriptions/00000000-0000-0000-0000-000000000000"

Full walkthrough (console steps, edge cases and verification) in the lesson Harden Azure MySQL and PostgreSQL.

Is "Public network access should be disabled for PostgreSQL servers" a false positive?

A Single Server still flagged here may be a deliberate, time-boxed exception during a migration to Flexible Server, where Single Server is on Microsoft's retirement path and the safer long-term fix is to move the workload rather than re-plumb private networking on the legacy server. If you must keep the public endpoint open temporarily, narrow the firewall rules to specific named client IPs, document the exception with an expiry date, and treat the alert as a reminder to finish the migration before retirement forces it. Note too that disabling public access takes effect even when no private endpoint exists yet, so verify your private path works before flipping the setting to avoid locking out every client.