Microsoft Defender for Cloud · Azure Cosmos DB
Cosmos DB accounts should use private link
Written and reviewed by Emnode · Last reviewed
What does the recommendation "Cosmos DB accounts should use private link" check?
Audits each Azure Cosmos DB account for at least one approved private endpoint connection, so the account is reachable over a private IP inside your virtual network rather than only through its public endpoint. The check reads the account's 'privateEndpointConnections' and flags any account with none in an approved state. It does not verify that the public endpoint has been switched off ('publicNetworkAccess' is a separate setting and a separate recommendation), and it does not check IP firewall rules, virtual network service endpoints, or whether the private endpoint covers every API in use (a Cosmos account exposes a distinct sub-resource per API, such as Sql, MongoDB, Cassandra, Gremlin or Table, and each needs its own endpoint).
Why does "Cosmos DB accounts should use private link" matter?
Without a private endpoint, a Cosmos DB account answers on a public hostname, so the only thing standing between your data and the internet is a key or token plus any IP firewall you remembered to configure. A leaked connection string, an over-broad firewall range, or a server-side request forgery flaw in a front-end app then becomes a direct, internet-routable path to every collection in the account. A private endpoint moves the account onto a private IP in your subnet so traffic stays on the Azure backbone and the database is simply not addressable from the public internet, which shrinks the exfiltration surface and is often a hard requirement for handling regulated or customer data.
How do I fix "Cosmos DB accounts should use private link"?
- Create a private endpoint that targets the account's API sub-resource, for example: az network private-endpoint create --resource-group <rg> --name pe-cosmos --vnet-name <vnet> --subnet <subnet> --private-connection-resource-id <cosmos-account-resource-id> --group-id Sql --connection-name cosmos-conn. Repeat with the matching --group-id (MongoDB, Cassandra, Gremlin or Table) for each API the account serves.
- Wire up private DNS so clients resolve the account name to the private IP: link the privatelink.documents.azure.com zone (or the API-specific zone) to the virtual network and add the A record, otherwise applications keep resolving and using the public endpoint.
- Once the private path is verified, set the account's 'publicNetworkAccess' to Disabled to close the public endpoint, and assign the built-in audit policy 'CosmosDB accounts should use private link' so new accounts are caught: pid=$(az policy definition list --query "[?displayName=='CosmosDB accounts should use private link'].name" -o tsv); az policy assignment create --name cosmos-private-link --policy "$pid" --scope <scope>.
Remediation script · bash
# 1. Give the account a private path IN before shutting the public one.
# --group-ids is the API sub-resource: Sql, MongoDB, Cassandra, Gremlin or Table.
RG=data-prod
ACCT=custprofiles-prod
VNET=spoke-data-vnet
SUBNET=db-subnet
SUB=$(az account show --query id -o tsv)
az network private-endpoint create \
--name "${ACCT}-pe" \
--resource-group "$RG" \
--vnet-name "$VNET" \
--subnet "$SUBNET" \
--private-connection-resource-id \
"/subscriptions/$SUB/resourceGroups/$RG/providers/Microsoft.DocumentDB/databaseAccounts/$ACCT" \
--group-ids Sql \
--connection-name "${ACCT}-conn"
# 2. Once the endpoint is approved and private DNS resolves, shut the public route.
# --public-network-access Disabled takes precedence over any IP/VNet firewall rule.
az cosmosdb update \
--name "$ACCT" \
--resource-group "$RG" \
--public-network-access Disabled
# 3. Backstop: audit any future account that lacks private link or stays public.
# Look the built-in policies up by display name; never hardcode a definition GUID.
for disp in \
"CosmosDB accounts should use private link" \
"Azure Cosmos DB should disable public network access"; do
pid=$(az policy definition list \
--query "[?displayName=='$disp'].name | [0]" -o tsv)
az policy assignment create \
--name "audit-$(echo "$disp" | tr ' A-Z' '-a-z' | cut -c1-40)" \
--policy "$pid" \
--scope "/subscriptions/$SUB"
done Full walkthrough (console steps, edge cases and verification) in the lesson Harden Azure Cosmos DB.
Is "Cosmos DB accounts should use private link" a false positive?
A Cosmos DB account that backs a purely public, read-only sample dataset, where the data is non-sensitive by design and a private endpoint would add cost and DNS complexity for no security gain, will still flag. This is a defensible exception: document it, keep the IP firewall and keys tight, and exclude that single account from the assignment rather than disabling the policy, so every other account stays held to the private-link standard.