Microsoft Defender for Cloud · Azure Database for MySQL
Private endpoint should be enabled for MySQL servers
Written and reviewed by Emnode · Last reviewed
What does the recommendation "Private endpoint should be enabled for MySQL servers" check?
Flags any Azure Database for MySQL single server (resource type Microsoft.DBforMySQL/servers) that has no approved private endpoint connection, so the server is still reachable over its public endpoint and a regional IP allow list rather than through a private IP inside your virtual network. The underlying policy (AuditIfNotExists) inspects each server's privateLinkServiceConnectionState and reports any server with no connection in the Approved state. It does not check the firewall rules themselves, does not confirm that public network access has been switched off once the private endpoint exists, and does not cover the newer MySQL flexible server (Microsoft.DBforMySQL/flexibleServers), which uses virtual network integration or its own private link surface and is governed by separate recommendations.
Why does "Private endpoint should be enabled for MySQL servers" matter?
Without a private endpoint, connections to the database traverse a public IP, and the only thing standing between the internet and your data is a set of firewall rules that are easy to widen by accident, for example a 0.0.0.0 to 255.255.255.255 rule added during a rushed migration. A private endpoint pulls the server onto a private IP in your virtual network, so traffic from application subnets, peered networks and on-premises over ExpressRoute never leaves the Microsoft backbone and the public path can be closed entirely. The business consequence of skipping it is a customer database that is internet-addressable: a leaked connection string or a brute-forced credential becomes a direct route to regulated data, and the exposure is visible to anyone scanning the MySQL port. Note that single server is on Azure's retirement path, so prioritise migrating to flexible server, where private connectivity is part of the networking model.
How do I fix "Private endpoint should be enabled for MySQL servers"?
- Create the private endpoint against the server, targeting the mysqlServer sub-resource: az network private-endpoint create --name pe-mysql --resource-group <rg> --vnet-name <vnet> --subnet <subnet> --private-connection-resource-id $(az mysql server show -g <rg> -n <server> --query id -o tsv) --group-id mysqlServer --connection-name mysql-conn.
- Add a private DNS zone (privatelink.mysql.database.azure.com) linked to the virtual network and a record for the endpoint, then set Public network access to Disabled on the server so clients can only reach it through the private IP.
- Enforce the baseline at scale by assigning the built-in policy. Look the definition up by its exact display name rather than pasting a GUID: pid=$(az policy definition list --query "[?displayName=='Private endpoint should be enabled for MySQL servers'].name" -o tsv); az policy assignment create --name require-mysql-pe --policy "$pid" --scope <subscription-or-mg-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 "Private endpoint should be enabled for MySQL servers" a false positive?
A single server kept deliberately on the public endpoint behind tightly scoped firewall rules, for example a short-lived staging instance reached only from a fixed corporate egress IP, will still flag because the policy looks for an approved private endpoint connection and not for whether your public-path controls are adequate. If that exposure is a conscious, time-boxed decision, exempt the resource through an Azure Policy exemption with a documented expiry rather than weakening the policy itself.
More Azure Database for MySQL controls
- (Enable if required) MySQL servers should use customer-managed keys to encrypt data at rest
- Azure Database for MySQL should have a Microsoft Entra administrator provisioned
- Enforce SSL connection should be enabled for MySQL database servers
- Geo-redundant backup should be enabled for Azure Database for MySQL
- Public network access should be disabled for MySQL servers