Connecting Securely to Azure Databases

An unsecured database connection is one of the most common security failures in cloud architectures. This page shows you the full picture: how to keep database traffic off the public internet using private endpoints, how to authenticate without passwords using managed identities, and how to verify SSL is working correctly.

The two parts of database connection security

Secure database connections have two independent concerns:

  1. Network path — which network route does the connection take? Over the public internet, or through a private network?
  2. Authentication — what credentials does the connecting application use? A username and password, a managed identity token, or a certificate?

You need both. A private endpoint without proper authentication still lets any VM in the VNet connect with a leaked password. Strong credentials over the public internet still expose the connection to eavesdropping (even with TLS, you’ve reduced exposure without eliminating it). The secure pattern is private networking AND managed identity authentication together.

Private endpoints for databases

A private endpoint creates a network interface in your VNet with a private IP address pointing to your database server. All traffic goes through the VNet — the database’s public endpoint can be disabled entirely.

# Get the resource IDs
POSTGRES_ID=$(az postgres flexible-server show \
  --resource-group my-storage-rg \
  --name my-postgres-demo \
  --query "id" --output tsv)

# Create a private endpoint for PostgreSQL
az network private-endpoint create \
  --name postgres-private-endpoint \
  --resource-group my-storage-rg \
  --vnet-name my-app-vnet \
  --subnet my-private-subnet \
  --private-connection-resource-id $POSTGRES_ID \
  --group-id postgresqlServer \
  --connection-name postgres-private-connection

# Disable public access on the PostgreSQL server
az postgres flexible-server update \
  --resource-group my-storage-rg \
  --name my-postgres-demo \
  --public-access Disabled

# For Azure SQL Database — create private endpoint
SQL_SERVER_ID=$(az sql server show \
  --resource-group my-storage-rg \
  --name my-sql-server-demo \
  --query "id" --output tsv)

az network private-endpoint create \
  --name sql-private-endpoint \
  --resource-group my-storage-rg \
  --vnet-name my-app-vnet \
  --subnet my-private-subnet \
  --private-connection-resource-id $SQL_SERVER_ID \
  --group-id sqlServer \
  --connection-name sql-private-connection

After creating the private endpoint, you need a DNS record so the database hostname resolves to the private IP rather than the public IP. Use Azure Private DNS Zones:

# Create a private DNS zone for PostgreSQL
az network private-dns zone create \
  --resource-group my-storage-rg \
  --name "privatelink.postgres.database.azure.com"

# Link the DNS zone to the VNet
az network private-dns link vnet create \
  --resource-group my-storage-rg \
  --zone-name "privatelink.postgres.database.azure.com" \
  --name my-app-vnet-link \
  --virtual-network my-app-vnet \
  --registration-enabled false

# Create a DNS record for the private endpoint
az network private-endpoint dns-zone-group create \
  --resource-group my-storage-rg \
  --endpoint-name postgres-private-endpoint \
  --name postgres-dns-group \
  --private-dns-zone "privatelink.postgres.database.azure.com" \
  --zone-name "privatelink.postgres.database.azure.com"

Connecting with Managed Identity to Azure SQL Database

Managed identity authentication eliminates database passwords from your application entirely. The application’s Azure identity (assigned to a VM, App Service, or Function) acquires a token from Azure AD, and presents it to the database instead of a username/password pair.

# Step 1: Create a user-assigned managed identity
az identity create \
  --name my-app-identity \
  --resource-group my-storage-rg

# Step 2: Assign the identity to your app service or VM
# (example: App Service)
az webapp identity assign \
  --resource-group my-app-rg \
  --name my-app-service \
  --identities my-app-identity

# Step 3: Set an Azure AD admin on the SQL server
az sql server ad-admin create \
  --resource-group my-storage-rg \
  --server-name my-sql-server-demo \
  --display-name "SQL AD Admin" \
  --object-id $(az ad user show --id admin@example.com --query id --output tsv)

Then in SQL Server Management Studio or sqlcmd (connected as the AD admin), create a contained database user for the managed identity:

-- Connect as Azure AD admin, then run:
CREATE USER [my-app-identity] FROM EXTERNAL PROVIDER;
ALTER ROLE db_datareader ADD MEMBER [my-app-identity];
ALTER ROLE db_datawriter ADD MEMBER [my-app-identity];

In your application code (C# example), connect with managed identity:

// Using Microsoft.Data.SqlClient with DefaultAzureCredential
var credential = new DefaultAzureCredential();
var token = await credential.GetTokenAsync(
    new TokenRequestContext(new[] { "https://database.windows.net/.default" }));

var connection = new SqlConnection(
    "Server=my-sql-server-demo.database.windows.net;Database=myappdb;");
connection.AccessToken = token.Token;
await connection.OpenAsync();

Connecting with Managed Identity to PostgreSQL

# Enable Azure AD authentication on PostgreSQL Flexible Server
az postgres flexible-server update \
  --resource-group my-storage-rg \
  --name my-postgres-demo \
  --active-directory-auth Enabled

# Set an Azure AD admin
IDENTITY_ID=$(az identity show \
  --name my-app-identity \
  --resource-group my-storage-rg \
  --query "principalId" --output tsv)

az postgres flexible-server ad-admin create \
  --resource-group my-storage-rg \
  --server-name my-postgres-demo \
  --display-name my-app-identity \
  --object-id $IDENTITY_ID

Connecting from Python using managed identity:

import psycopg2
from azure.identity import ManagedIdentityCredential

credential = ManagedIdentityCredential()
token = credential.get_token("https://ossrdbms-aad.database.windows.net/.default")

conn = psycopg2.connect(
    host="my-postgres-demo.postgres.database.azure.com",
    port=5432,
    database="myappdb",
    user="my-app-identity",
    password=token.token,
    sslmode="require"
)

Verifying SSL/TLS connections

All Azure database services require encrypted connections. Verify your connection is using TLS:

# Verify SSL for PostgreSQL (from psql)
# After connecting:
# \conninfo
# Should show "SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384)"

# For MySQL: check SSL status
# After connecting with mysql client:
# SHOW STATUS LIKE 'Ssl_cipher';
# Should return a cipher name, not empty

# Check TLS version from CLI
openssl s_client -connect my-postgres-demo.postgres.database.azure.com:5432 \
  -starttls postgres 2>&1 | grep -E "Protocol|Cipher"

For strict verification (verifying the server certificate rather than just encrypting), download the DigiCert Global Root G2 CA certificate and configure your connection:

# PostgreSQL with certificate verification
psql "postgresql://pgadmin@my-postgres-demo.postgres.database.azure.com:5432/myappdb\
?sslmode=verify-full&sslrootcert=/path/to/DigiCertGlobalRootG2.crt.pem"

Common mistakes

  1. Using a service endpoint instead of a private endpoint and thinking the database is now private. A VNet service endpoint routes traffic through Azure’s backbone but the database’s public IP still exists and is reachable from the internet. Only a private endpoint with the public access disabled truly removes public reachability.
  2. Storing managed identity client IDs as environment variables alongside secrets. A managed identity client ID is not a secret — it’s an identity, not a credential. But developers often store database passwords and client IDs together in the same secrets file, implying the client ID is sensitive. The credential is the Azure platform token, which your app acquires automatically. No secret is needed.
  3. Not configuring the private DNS zone after creating a private endpoint. Without a private DNS zone record, the database hostname still resolves to the public IP even from inside the VNet. VMs inside the VNet will connect over the public internet, bypassing the private endpoint entirely. Always set up the DNS zone group after creating a private endpoint.
  4. Using sslmode=disable for “testing” in production. Disabling SSL is sometimes done to troubleshoot connection issues. Developers forget to re-enable it before deploying. Use sslmode=require or sslmode=verify-full in all environments, and never use disable except as a last-resort local debugging step.

Frequently asked questions

What is the difference between a VNet service endpoint and a private endpoint for databases?

A VNet service endpoint routes traffic from a subnet to the database service using the Azure backbone, but the database still has a public IP. Traffic from outside Azure can still reach the public endpoint. A private endpoint gives the database a private IP inside your VNet and the public endpoint can be disabled entirely — it is more secure and is the recommended approach for production.

Can I use managed identity to connect to MySQL and PostgreSQL, not just SQL Database?

Yes. Azure Database for MySQL Flexible Server and PostgreSQL Flexible Server both support Microsoft Entra ID (Azure AD) authentication, which enables managed identity connections. For MySQL, you authenticate with an Azure AD token over the standard MySQL protocol. For PostgreSQL, similar token-based authentication is supported. The setup requires enabling Azure AD admin on the server and creating database users mapped to Azure AD identities.

Is SSL/TLS required for all Azure database connections?

SSL is enforced by default for Azure SQL Database, MySQL Flexible Server, and PostgreSQL Flexible Server. You can technically disable it on MySQL and PostgreSQL (but should never do so in production). Azure SQL Database always requires encrypted connections and the setting cannot be turned off.

Last verified: 19 March 2026 Cloud services change frequently. Verify details against official documentation before making infrastructure decisions.