In my case, I needed to reach Azure Database for PostgreSQL from my laptop without punching temporary holes in the firewall every time my IP changed.
This is the exact Terraform setup I used: private endpoint + private DNS + point-to-site VPN, with Azure VPN Client.
1. Add dedicated private networking Terraform
I kept this in its own file (private-networking.tf) so the core deployment files stay clean.
1 | locals { |
The key bits here are:
- A subnet for private endpoints.
- A
GatewaySubnetfor VPN gateway. - AZ-compatible VPN SKU (
VpnGw1AZ) to avoid deprecated SKU errors.
Docs:
2. Add PostgreSQL private endpoint and private DNS
This is what let my resolv-psql-uat.postgres.database.azure.com resolve and route privately once connected over VPN.
1 | resource "azurerm_private_dns_zone" "pgsql-privatelink" { |
Without the private DNS zone and VNet link, name resolution is usually where things break.
Docs: Private Endpoint for Azure Database for PostgreSQL flexible server • Azure Private DNS
3. Configure P2S VPN for Azure VPN Client
I used Azure AD (Entra ID) auth with OpenVPN so sign-in is with the same tenant account used in the Azure portal.
1 | resource "azurerm_public_ip" "resolv-vpngw-pip" { |
Two important gotchas here:
- AZ gateway SKUs require zoned Standard public IPs.
domain_name_labelis required on the public IP. The Azure VPN Client only accepts an FQDN as the VPN Server address — a bare IP will fail with “Azure VPN Client can only connect to Azure VPN Servers”. Settingdomain_name_labelgives the IP a stable DNS name likeresolv-uat-vpngw.<region>.cloudapp.azure.com.
Docs: About Point-to-Site VPN • Configure Microsoft Entra authentication for P2S
4. Get the VPN gateway FQDN after deployment
Once applied, retrieve the exact FQDN to use in the Azure VPN Client:
1 | az network public-ip show \ |
This returns something like resolv-uat-vpngw.australiaeast.cloudapp.azure.com. That value goes in the VPN Server field when importing the VPN profile in Azure VPN Client.
5. Run Terraform plan and apply
This is the sequence I used in UAT.
1 | cd ./terraform/web/uat |
What to expect while applying:
- Private DNS zone, VNet, and private endpoint create relatively quickly.
- VPN gateway creation is slow and can take 30 to 60 minutes depending on region load.
- Seeing repeated
Still creating...for the VPN gateway is normal, so do not cancel as long as status updates keep appearing. - If apply fails midway, fix the issue and run
terraform planthenterraform applyagain. Terraform will continue from current state.
Docs: Terraform plan • Terraform apply • Terraform init
6. Test end-to-end
These were my exact verification steps:
- Install Azure VPN Client.
- Download and import the VPN profile:
- In Azure Portal, open your virtual network gateway (
resolv-uat-vpngw). - Go to
Settings->Point-to-site configurationand click Download VPN client. - Extract the downloaded zip.
- Open Azure VPN Client, choose + then Import, and select the
azurevpnconfig.xmlfile.
- Connect and sign in with the same Microsoft account in the target tenant.
- Confirm DNS resolution and connectivity:
1 | nslookup <DEPLOYMENT-NAME>.postgres.database.azure.com |
If DNS resolves to a private RFC1918 address and psql connects, the path is working as intended.
Docs:
- Configure P2S and download VPN client profile
- Azure VPN Client import/connect
- Connect to Azure Database for PostgreSQL
7. Troubleshooting I hit during rollout
I ran into both of these when applying in UAT:
NonAzSkusNotAllowedForVPNGateway.
UseVpnGw1AZ(or another*AZSKU), notVpnGw1.VmssVpnGatewayPublicIpsMustHaveZonesConfigured.
Setzoneson the Standard public IP used by the VPN gateway.- “Azure VPN Client can only connect to Azure VPN Servers”.
The VPN Server field in the client must be an FQDN, not a raw IP. Adddomain_name_labelto theazurerm_public_ipresource (see section 3), redeploy, then use the FQDN from section 4 in the client.
Also, if terraform plan suddenly says backend init is required, run:
1 | terraform init -reconfigure |
That rebinds the working directory to backend config; it does not redeploy resources by itself.
Docs: Terraform init command • Azure VPN gateway SKU changes