Provisioning, SSL configuration, connection strings, and connecting to Payload CMS on Azure Flexible Server.
March 31, 2026
Azure Database for PostgreSQL Flexible Server is the current recommended PostgreSQL offering on Azure. It replaced the older Single Server product and adds useful features: stop/start capability, flexible compute tiers, and better zone redundancy options. For a Payload CMS deployment, it's the most straightforward managed PostgreSQL option on Azure.
az postgres flexible-server create \
--resource-group blog-rg \
--name shared-postgres-2025 \
--location centralindia \
--admin-user blogadmin \
--admin-password "YourStr0ngP@ss!" \
--sku-name Standard_B1ms \
--tier Burstable \
--storage-size 32 \
--version 16 \
--public-access 0.0.0.0-255.255.255.255
Standard_B1ms (Burstable tier) is the cheapest option — 1 vCore, 2GB RAM. For a blog with moderate traffic, it's sufficient. Upgrade to Standard_D2s_v3 (General Purpose) if you see CPU throttling under load.
--public-access 0.0.0.0-255.255.255.255 allows connections from any IP. Restrict this to your Container Apps' outbound IPs in production for better security.
az postgres flexible-server db create \
--resource-group blog-rg \
--server-name shared-postgres-2025 \
--database-name blogdb
Azure PostgreSQL Flexible Server enforces SSL connections by default. Your connection string must include sslmode=require:
postgresql://blogadmin:YourStr0ngP@ss!@shared-postgres-2025.postgres.database.azure.com:5432/blogdb?sslmode=require
Without sslmode=require, most PostgreSQL clients will still try to connect without SSL, and Azure will reject the connection. This is the most common cause of "connection refused" errors when migrating from a local PostgreSQL instance.
Test the connection from your local machine:
psql "postgresql://blogadmin:YourStr0ngP@ss!@shared-postgres-2025.postgres.database.azure.com:5432/blogdb?sslmode=require"
If you get an error about the client certificate or SSL mode, add sslmode=require. If you get an access denied error, check your firewall rules.
Flexible Server uses firewall rules to control inbound connections:
# Allow your current IP (for local development)
az postgres flexible-server firewall-rule create \
--resource-group blog-rg \
--name shared-postgres-2025 \
--rule-name my-dev-machine \
--start-ip-address $(curl -s ifconfig.me) \
--end-ip-address $(curl -s ifconfig.me)
# Allow Azure services (Container Apps, etc.)
az postgres flexible-server firewall-rule create \
--resource-group blog-rg \
--name shared-postgres-2025 \
--rule-name AllowAzureServices \
--start-ip-address 0.0.0.0 \
--end-ip-address 0.0.0.0
The 0.0.0.0 to 0.0.0.0 rule is Azure's magic value for "allow connections from any Azure service." It's not a wildcard — it specifically allows traffic originating from within Azure.
Payload CMS uses Drizzle ORM under the hood. The @payloadcms/db-postgres adapter connects to PostgreSQL directly. For production, configure a reasonable pool size:
// apps/cms/src/payload.config.ts
import { postgresAdapter } from '@payloadcms/db-postgres'
export default buildConfig({
db: postgresAdapter({
pool: {
connectionString: process.env.DATABASE_URI,
max: 10, // maximum pool connections
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
},
}),
})
max: 10 is conservative for a Burstable B1ms tier. Azure's Flexible Server has a connection limit tied to the compute tier — B1ms allows roughly 50 concurrent connections. With a pool of 10 per Container Apps replica and up to 3 replicas, you're safely under the limit.
Flexible Server includes automatic backups with configurable retention (1–35 days):
# Check backup status
az postgres flexible-server show \
--name shared-postgres-2025 \
--resource-group blog-rg \
--query backup
# Point-in-time restore (creates a new server)
az postgres flexible-server restore \
--resource-group blog-rg \
--name restored-postgres-2025 \
--source-server shared-postgres-2025 \
--restore-time "2025-03-30T10:00:00Z"
Restores create a new server — your original server remains unchanged. Test your restore procedure before you need it in production.
Enable slow query logging to identify performance problems:
az postgres flexible-server parameter set \
--resource-group blog-rg \
--server-name shared-postgres-2025 \
--name log_min_duration_statement \
--value 1000 # log queries taking more than 1 second
View logs in the Azure portal under Monitoring > Logs, or stream them with:
az postgres flexible-server logs list \
--resource-group blog-rg \
--server-name shared-postgres-2025
?sslmode=require in your connection string — Azure enforces SSL and connections fail without it0.0.0.0 to 0.0.0.0 firewall rule allows Azure-internal traffic (Container Apps, etc.) — not all IPslog_min_duration_statement to surface slow queries before they become user-facing latency