---
cuppy_hub: azure
cuppy_path: reference/postgresql/flexible-server.md
cuppy_section: docs
cuppy_doc: F35G-1NEW
cuppy_doc_uuid: 7befdc0c-1bc1-465a-a80a-cad394bebace
cuppy_permalink: https://cuppy.io/azure/d/F35G-1NEW
cuppy_version: 1
cuppy_updated: 2026-10-23T16:16:52.575Z
cuppy_canonical: https://cuppy.io/azure/docs/reference/postgresql/flexible-server
cuppy_mcp: https://cuppy.io/mcp
cuppy_body_sha256: 952b842b8daef3432565f71731c28e26af8857d2c05bf1bf41bf7f3ad8cd6deb
# cuppy_body_sha256 is over the document body ALONE, excluding both generated blocks.
---


# PostgreSQL flexible server

## Entra authentication

- `authConfig`: `activeDirectoryAuth: 'Enabled'` and `passwordAuth: 'Disabled'` for Entra only (CLI `--microsoft-entra-auth Enabled --password-auth Disabled`; the CLI defaults are the reverse). Enable it at creation or later.
- **Entra admins** (users, groups, service principals or managed identities): one at provisioning, more afterwards; the resource is `flexibleServers/administrators/<objectId>` (`az postgres flexible-server microsoft-entra-admin create -g <rg> -s <server> --display-name <name> --object-id <id> --type Group`). Don't run the app as the admin: it has `CREATEDB` and elevated rights.
- **App roles:** connected as an Entra admin to the `postgres` database:

```sql
SELECT * FROM pgaadauth_create_principal('<managed identity display name>', false, false);
-- or, when display names collide:
SELECT * FROM pgaadauth_create_principal_with_oid('<role name>', '<object id>', 'service', false, false);
GRANT CONNECT ON DATABASE appdb TO "<managed identity display name>";
```

  Arguments: role name (UPN for users, display name for groups and service principals), `isAdmin`, `isMfa`. A role linked to a group lets every member sign in **with the group's role name**; group membership syncs every 30 minutes.
- **Tokens are the password**: scope `https://ossrdbms-aad.database.windows.net/.default` (CLI: `az account get-access-token` `--resource-type oss-rdbms`), valid **5–60 minutes**, checked at sign-in. Microsoft says that from January 2027 tokens for audiences other than `ossrdbms` won't be accepted.
- With private access, allow outbound to the `AzureActiveDirectory` service tag, and keep `login.microsoftonline.com` and `graph.microsoft.com` resolving publicly, or admin assignment and sign-in fail.

## Network: pick one mode at creation

| | Private access (VNet integration) | Public access (+ private endpoints) |
|---|---|---|
| Placement | A subnet **delegated to `Microsoft.DBforPostgreSQL/flexibleServers`**, only for these servers, **/28 or larger** (an HA server uses 4 addresses) | No subnet; firewall rules (none = no access) and/or private endpoints (`groupId` `postgresqlServer`, zone `privatelink.postgres.database.azure.com`) |
| DNS | A private zone whose name **ends in `.postgres.database.azure.com`** and isn't the server's name | Private endpoint zone group |
| Constraints | Can't move to another VNet or subnet; subnet can't grow once used; **no private endpoints**; don't force all Storage traffic through an NVA | CLI `--public-access 0.0.0.0` admits all Azure services |

## Versions

18 down to 11 are offered. **11, 12 and 13 have been in paid extended support since August 1, 2026** (billing from September 1, 2026; ends March 31, 2027, November 13, 2027 and November 12, 2028). **14's standard support ends January 31, 2027.** Enrolment is automatic and charged only while a server runs; upgrade the major version to leave it.

## Bicep (Entra only, private access)

```bicep
// PostgreSQL flexible server: Microsoft Entra only, private (VNet-integrated), with an Entra admin group
param location string = resourceGroup().location
param serverName string
param delegatedSubnetId string // subnet delegated to Microsoft.DBforPostgreSQL/flexibleServers, /28 or larger
param privateDnsZoneId string // a zone ending in .postgres.database.azure.com, linked to the VNet
param adminGroupName string
param adminGroupObjectId string

resource pg 'Microsoft.DBforPostgreSQL/flexibleServers@2025-08-01' = {
  name: serverName
  location: location
  sku: {
    name: 'Standard_D4ds_v5'
    tier: 'GeneralPurpose'
  }
  properties: {
    version: '17'
    authConfig: {
      activeDirectoryAuth: 'Enabled'
      passwordAuth: 'Disabled' // Entra only; the hub's reading is that no administratorLogin is then needed
      tenantId: tenant().tenantId
    }
    network: {
      delegatedSubnetResourceId: delegatedSubnetId
      privateDnsZoneArmResourceId: privateDnsZoneId
    }
    storage: {
      storageSizeGB: 64
      autoGrow: 'Enabled'
    }
    highAvailability: {
      mode: 'ZoneRedundant'
    }
  }
}

resource entraAdmin 'Microsoft.DBforPostgreSQL/flexibleServers/administrators@2025-08-01' = {
  parent: pg
  name: adminGroupObjectId
  properties: {
    principalType: 'Group'
    principalName: adminGroupName
    tenantId: tenant().tenantId
  }
}
```

## Node.js

```ts
import pg from "pg";
import { DefaultAzureCredential } from "@azure/identity";

const credential = new DefaultAzureCredential();
const SCOPE = "https://ossrdbms-aad.database.windows.net/.default"; // PostgreSQL and MySQL flexible servers

// The role must exist: SELECT * FROM pgaadauth_create_principal('<identity display name>', false, false);
export const pool = new pg.Pool({
  host: `${process.env.PG_SERVER}.postgres.database.azure.com`,
  database: "appdb",
  user: process.env.PG_USER ?? "", // the role name: UPN for users, display name for groups and identities
  // A function, so every new pooled connection gets a fresh token (tokens last 5–60 minutes)
  password: async () => (await credential.getToken(SCOPE)).token,
  ssl: { rejectUnauthorized: true },
  max: 10,
  idleTimeoutMillis: 30_000,
});

export async function main(): Promise<void> {
  const { rows } = await pool.query<{ now: Date }>("SELECT now() AS now");
  console.log(rows[0]?.now);
  await pool.end();
}
```

Read replicas, major version upgrades and elastic clusters: `reference/postgresql/replicas-upgrades-and-clusters.md`. Retired database services: `reference/databases/retired-and-replacements.md`.

## Errors

| Message | Cause → fix |
|---|---|
| `FATAL: password` `authentication failed for user "<user-name>"` | Wrong password; for Entra: expired or wrong-audience token, a user name that doesn't match the role exactly (case-sensitive), or no `pgaadauth_create_principal` role |
| `FATAL: no pg_hba.conf entry for host "y.y.y.y", user "<user-name>", database "postgres", no encryption` (right after the first error) | libpq retried without TLS after the TLS attempt failed: set `PGSSLMODE=require` (or `ssl` in the client) so you see the real error |
| Deployment fails naming the private DNS zone | The zone name equals the server name, or the zone's subscription lacks the `Microsoft.DBforPostgreSQL` provider registration |
| Timeouts from inside the VNet | The hub's reading: an NSG or UDR on the delegated subnet, or DNS not resolving through the linked zone (`guides/networking/diagnose-connectivity.md`) |


---
<!-- cuppy -->
You are reading one file. The same content is free over MCP at https://cuppy.io/mcp with no signup: ranked search across all 107 documents in this hub, the merged view when you work in a layer, known workarounds matched to the error you hit, and depth control so you spend tokens on what you need. Start with the `_hello` tool. How to connect: https://cuppy.io/azure/ Something wrong in this document? Report it with the `session feedback` tool on the same endpoint.
<!-- /cuppy -->
