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

Microsoft Defender for Cloud · Azure Database for PostgreSQL flexible server

Private endpoint should be configured for Azure Database for PostgreSQL Servers

Written and reviewed by Emnode · Last reviewed

What does the recommendation "Private endpoint should be configured for Azure Database for PostgreSQL Servers" check?

Flags any Azure Database for PostgreSQL flexible server that has no approved Private Link private endpoint attached, so the only way to reach it is over its public endpoint governed by firewall rules. It looks for the presence of a private endpoint connection on the server, not for whether public network access is also switched off and not for whether your firewall allow list is tight. A server can satisfy this control yet still accept public traffic if you leave public access enabled, so treat it as one half of the network isolation story rather than the whole of it. It does not cover the older single-server deployment model, encryption in transit, or who holds database credentials.

Why does "Private endpoint should be configured for Azure Database for PostgreSQL Servers" matter?

A PostgreSQL server reached over its public endpoint is one weak firewall rule away from the internet: an overly broad CIDR, a stray 0.0.0.0 to 255.255.255.255 'allow Azure services' entry, or a forgotten temporary rule turns the database into an internet-facing target for credential stuffing and exploit scanning. Databases hold exactly the data that makes a breach reportable, so an exposed instance is both the most likely entry point and the most expensive thing to lose. A private endpoint pins the server to a private IP inside your virtual network and routes traffic over the Microsoft backbone, so even a misconfigured firewall rule cannot expose it, and an attacker without a foothold in your network has nothing to connect to.

How do I fix "Private endpoint should be configured for Azure Database for PostgreSQL Servers"?

  1. Create a private endpoint against the server's 'postgresqlServer' sub-resource: az network private-endpoint create --name <pe> --resource-group <rg> --vnet-name <vnet> --subnet <subnet> --group-id postgresqlServer --private-connection-resource-id $(az postgres flexible-server show -g <rg> -n <server> --query id -o tsv) --connection-name <conn>. If you lack rights to auto-approve, approve the pending connection with az network private-endpoint-connection approve --resource-name <server> --type Microsoft.DBforPostgreSQL/flexibleServers --name <conn>.
  2. Wire up name resolution by linking the private endpoint to the privatelink.postgres.database.azure.com private DNS zone (az network private-endpoint dns-zone-group create), otherwise clients still resolve the public hostname and the private path is never used.
  3. Once private connectivity is proven, turn off the public path so the firewall can never re-expose the server, and bake the private endpoint plus DNS zone group into your Bicep or Terraform module so every new flexible server ships isolated by default.

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 "Private endpoint should be configured for Azure Database for PostgreSQL Servers" a false positive?

A non-production sandbox flexible server holding only synthetic data, sitting in an isolated subscription and used for short-lived load testing from a CI runner, can legitimately stay on its public endpoint with a narrow firewall allow list rather than carrying a private endpoint. The control still flags it because it only checks for a private endpoint connection; document the exception so reviewers know the exposure is bounded and the data is not real.