January 9, 2026

PostgreSQL Major Version Upgrades on Azure: A Terraform-based Approach

Introduction

PostgreSQL 11 has reached its end of life, and Azure recommends upgrading to PostgreSQL 13 or later for enhanced security, improved performance, and long-term support. Unlike minor upgrades, Azure Database for PostgreSQL (Flexible Server) does not support in-place major version upgrades. This makes the upgrade process slightly non-trivial—especially when the server is provisioned using Terraform, and some environments use VNet integration.

In this blog, we’ll walk through:
  • How Azure PostgreSQL upgrades work
  • Why does Terraform recreate the server
  • Multiple migration strategies
  • The exact steps I followed to upgrade PostgreSQL 11 → 13 safely


Existing Setup

My environment had the following characteristics:

  • Azure Database for PostgreSQL – Flexible Server
  • PostgreSQL version: 11
  • SKU: Burstable B1ms (1 vCore, 2 GiB RAM)
  • Storage: 32 GiB
  • Region: Central US
  • Provisioned using Terraform
  • Mixed environments: Some with public access, some with VNet integration
  • Firewall rules restricted to specific IPs

Terraform snippet (simplified):



Important Reality: No In-Place Major Version Upgrade

This is the most critical thing to understand: Azure PostgreSQL Flexible Server does NOT support in-place major version upgrades.

That means:
  • You cannot upgrade PostgreSQL 11 → 13 on the same server
  • Changing version = "13" in Terraform:
  • Deletes the existing PostgreSQL 11 server
  • Creates a brand-new PostgreSQL 13 server
  • All data is lost unless you migrate or restore it manually

 

Terraform makes this very clear: forces replacement. This is not really an upgrade — it’s a rebuild and a migration.

Why This Upgrade Looks Simple — and Why It Isn’t

At first glance, the upgrade appears trivial: version = "13" 

But behind this single line:
  • Azure treats PostgreSQL major versions as immutable
  • Terraform maps this to a ForceNew operation
  • Automated backups are tied to the old server lifecycle
  • Configuration and data do not carry over


What Actually Happens (Timeline)

Understanding the timeline helps avoid surprises:

T-0: PostgreSQL 11 running

  • Applications connected
  • Data live
  • Automated backups available


T-1: Terraform version updated

  • version = "11" → version = "13"
  • Plan shows forces replacement


T-2: Terraform apply

  • PostgreSQL 11 server is deleted
  • Databases and backups disappear


T-3: PostgreSQL 13 server created

  • Empty server
  • Default parameters
  • No firewall rules
  • No databases


T-4: Manual restore

  • Data restored
  • Configuration reapplied
  • Applications reconnect


Available Upgrade Approaches

1. Azure Database Migration Service (DMS)
2. Backup & Restore (pg_dump / pgAdmin)
3. Temporary Public Access

Here we focus on Option 3, which was simple, cost-effective, and acceptable for my downtime window.

Step 1: Take a Backup

I used pgAdmin 4 with a custom format backup.

Why Custom format?
  • Includes schema + data
  • Best compatibility across versions
  • Works cleanly with pg_restore
pg_dump `
  -h myserver.postgres.database.azure.com `
  -U pgsqladmin@myserver `
  -d master_data_service `
  -Fc `
  --sslmode=require `
  -f master_data_service_v11.dump

Step 2: Upgrade PostgreSQL Version via Terraform

In Terraform, change the code: version = "13"
Important: This destroys the PostgreSQL 11 server and creates a new PostgreSQL 13 server with the same name.
Run:
terraform plan
terraform apply

This immediately destroys the PostgreSQL 11 server and creates a new PostgreSQL 13 server with the same name.

Step 3: Restore the Database to PostgreSQL 13

pg_restore `
  -h myserver.postgres.database.azure.com `
  -U pgsqladmin@myserver `
  -d postgres `
  --create `
  -Fc `
  --sslmode=require `
  master_data_service_v11.dump

This:
  • Recreated the database
  • Restored schema and data
  • Worked cleanly from v11 → v13


Step 4: Server Parameters & Configuration
Azure applies default server parameters when a new PostgreSQL server is created.

Key learning:

  • Server parameters are NOT automatically migrated
  • If you changed parameters manually in the portal, you must reapply them


Step 5: VNet-Integrated Environments

For servers with VNet integration:
  • No public endpoint exists
  • Local pgAdmin / pg_dump won’t connect

Available options:
  • Use Azure DMS inside the VNet
  • Use a VM or jumpbox
  • Temporarily enable public access

We temporarily enabled public access with strict /32 firewall rules and disabled it immediately after migration.

Step 6: Validate & Cutover

After restoring:
  • Verified tables, row counts, and extensions
  • Tested application connectivity
  • Updated connection strings where required
  • Disabled public access again for private environments

Cost Considerations
  • PostgreSQL B1ms server: ~$25/month
  • Temporary overlap or migration time: a few dollars
  • Azure DMS (Standard): Often free for migration scenarios
  • Overall upgrade cost: minimal


Key Takeaways

  • Azure PostgreSQL major upgrades are not in-place
  • Terraform recreates the server when version changes
  • Always backup before upgrading
  • Server parameters must be reapplied
  • For VNet setups, plan connectivity carefully
  • PostgreSQL supports direct jump from 11 → 13


Final Thoughts

Upgrading PostgreSQL on Azure requires careful planning, but with the right approach, it can be a predictable and safe process.

If you’re using Terraform:

  • Treat major version upgrades as rebuild + restore
  • Automate as much as possible
  • Test in lower environments first

No comments:

Post a Comment