Azure DevOps Azure Front Door Staging Infrastructure 8 min read

Your Staging Environment Is Probably Talking to Prod Right Now

How I found 4 cross-environment resource leaks in an Azure Front Door staging setup — and the systematic audit that caught them.

B
Mohammed Bilal Meccai
TL;DR
Dev slots on Azure App Service will inherit production app settings unless you explicitly override every resource connection — DB, Redis, storage, and cert check. Running az webapp config appsettings list across all slots in one pass will reveal which ones are silently hitting prod resources. We found 4 violations in a single audit pass.

The Setup

We were standing up Azure Front Door (AFD) as the CDN and routing layer for a payment platform’s staging environment. The goal: route traffic through a custom staging domain to the correct Azure App Service deployment slots, without touching the production setup.

Simple enough on paper. In practice: three months of incremental work had produced three separate documentation files, all slightly out of date, none fully consistent with what was actually deployed. Classic.

The first task was to consolidate everything. Not just rewrite the docs — verify every value against live Azure, then write from truth.

The second task is what this post is about.


The Question That Exposed Everything

Once the masterbook was done, I asked a simple question: are the dev slots actually isolated from production resources?

Not “do they have the right URL?” Not “do they respond to requests?” — but are they talking to dev databases, dev Redis, dev storage? Or are they silently running queries against production?

This is a question most engineers skip because the slots work. Requests come back 200. Nobody complains. The problem only surfaces when a developer’s test run corrupts a prod record, or when you’re debugging a staging issue and realize the “test” data is actually live customer data.

Let me show you what we found.


Step 1: Query Live Slot Settings

The audit is simple. Run az webapp config appsettings list against every dev slot. For a payment platform with multiple services, that means something like:

# Query all app settings for a specific dev slot
az webapp config appsettings list \
  --resource-group your-resource-group \
  --name your-app-name \
  --slot dev \
  --output table

Do this for every service — API, OBA (online banking), POS, control panel, tickets, webhook handler. Every one of them.

The output shows every app setting key and value. What you’re looking for is resource connection strings:

Setting Key What it connects to
ConnectionStrings__DefaultConnection Database
Redis__ConnectionString Redis cache
StorageAccount__ConnectionString Blob storage
KeyVault__Vault Key Vault (prod or dev?)
IsMultiTenant Tenant routing mode
AllowSpecificOrigin CORS allowed origin

Now compare: does the value look like your dev/staging resource? Or your production one?


What We Found: 4 Violations

After running the audit across 8 dev slots, we had a clean map. Here’s a sanitised version of what the violations looked like:

Service Resource Issue
Tickets API dev slot Redis Connection string pointed to production Redis instance
API dev slot Blob storage Storage account URL was the production storage account
OBA dev slot CORS origin AllowSpecificOrigin still set to production azurewebsites URL
OBA dev slot Config IsMultiTenant and AllowSpecificOrigin not configured at all

The last one is the silent killer. When a setting is missing entirely, Azure App Service either errors on startup, falls back to a default, or — worst case — resolves it from a linked Key Vault that points to prod values.


The Azure Front Door Cert Check Problem

Before we even got to resource isolation, we hit a different wall: 502s on every request through AFD.

The error comes from a setting called enforceCertificateNameCheck on AFD origins. When it’s true (the default), AFD validates that the SSL certificate on the backend matches the origin’s hostname.

For production this is fine — your app is at api.yourdomain.com and the cert says api.yourdomain.com. Match.

For staging dev slots, you’re routing staging.yourdomain.com (or the AFD staging endpoint) through to your-app-dev.azurewebsites.net. The cert on that origin says *.azurewebsites.net, not your custom domain. AFD rejects the connection.

The fix is one command per origin:

az afd origin update \
  --resource-group your-rg \
  --profile-name your-afd-profile \
  --origin-group-name og-apidev \
  --origin-name your-api-dev-origin \
  --enforce-certificate-name-check false

Run this for every dev slot origin. Do not confuse this with disabling cert validation entirely — you’re only telling AFD not to do hostname matching on the backend connection. TLS is still in use.

We verified the fix worked by checking the origin config after:

az afd origin show \
  --resource-group your-rg \
  --profile-name your-afd-profile \
  --origin-group-name og-apidev \
  --origin-name your-api-dev-origin \
  --query enforceCertificateNameCheck

Expected output: false


Step 2: Map the Full Resource Picture

Once we had all slot settings, we built a resource isolation map. One row per service slot, columns for each resource type. Something like this:

Slot Database Redis Storage Cert Check Issues
api-dev dev DB ✅ dev Redis ✅ prod storage false ✅ Storage bleed
oba-dev dev DB ✅ dev Redis ✅ dev storage ✅ true Cert check 502
tickets-dev dev DB ✅ prod Redis dev storage ✅ false ✅ Redis bleed
pos-dev dev DB ✅ dev Redis ✅ dev storage ✅ false ✅ IsMultiTenant=false

Two things jump out of this map:

  1. No single slot has all violations. They’re scattered. You’d never find this without querying all slots.
  2. Some violations are subtle. IsMultiTenant=false on the POS dev slot means it won’t correctly resolve tenant-scoped database connections. The slot starts up fine, returns 200s — but certain operations silently use the wrong tenant context.

The Fix Protocol

For each violation, the remediation follows the same pattern:

1. Resource connection fixes — update the specific app setting to point at the dev resource:

az webapp config appsettings set \
  --resource-group your-rg \
  --name your-app \
  --slot dev \
  --settings "Redis__ConnectionString=your-dev-redis.redis.cache.windows.net:6380,password=..."

2. Mark settings as slot-sticky so they don’t get wiped on a slot swap:

az webapp config appsettings set \
  --resource-group your-rg \
  --name your-app \
  --slot dev \
  --slot-settings "Redis__ConnectionString" "ConnectionStrings__DefaultConnection"

The --slot-settings flag is the key one most teams miss. Without it, your carefully configured dev settings will be overwritten next time someone does a production slot swap.

3. Verify after every fix — don’t trust the command succeeding, query the live value:

az webapp config appsettings list \
  --resource-group your-rg \
  --name your-app \
  --slot dev \
  --query "[?name=='Redis__ConnectionString'].value" \
  --output tsv

The Documentation Trap

Here’s the meta-lesson from this whole exercise.

Before the audit, we had documentation. Three files, totalling probably 1,500 lines. They listed slot names, hostnames, connection strings — the works. Written during the initial setup, updated occasionally when someone remembered.

When we queried live Azure, the docs were wrong in at least four places:

  • Two slot hostnames were different from what was documented (named slots have different URL patterns than expected)
  • Two cert check settings showed the Phase 0 fix had already been applied (docs hadn’t caught up)
  • One slot had settings that had never been documented at all

This isn’t a documentation quality problem. It’s a fundamental limitation of documentation as a source of truth for infrastructure state. The only reliable source of truth is the live infrastructure itself.

The new rule: before any staging work, run a full settings audit. Script it if you’re doing this regularly. Make the query the first step, not the docs.


Quick Audit Script

Here’s the pattern for auditing multiple slots across multiple apps in one pass:

#!/bin/bash
RG="your-resource-group"
APPS=("app-api" "app-oba" "app-pos" "app-tickets")
SLOTS=("dev" "staging")

for APP in "${APPS[@]}"; do
  for SLOT in "${SLOTS[@]}"; do
    echo "=== $APP / $SLOT ==="
    az webapp config appsettings list \
      --resource-group "$RG" \
      --name "$APP" \
      --slot "$SLOT" \
      --query "[?contains(name, 'Connection') || contains(name, 'Redis') || contains(name, 'Storage') || name=='IsMultiTenant'].{Key:name, Value:value}" \
      --output table 2>/dev/null || echo "(slot not found)"
    echo ""
  done
done

Run this before and after any staging environment changes. The diff tells you exactly what changed and surfaces any accidental production connections.


Checklist

Before calling a staging environment “ready”:

  • [ ] All dev slots have enforceCertificateNameCheck=false on their AFD origins
  • [ ] All connection strings point at dev/staging resources (not prod)
  • [ ] Redis connection strings verified — dev Redis, not prod
  • [ ] Storage account URLs verified — dev storage, not prod
  • [ ] Key Vault references point at a staging vault with staging secrets
  • [ ] CORS AllowSpecificOrigin updated to the staging domain
  • [ ] IsMultiTenant configured correctly for multi-tenant services
  • [ ] All environment-specific settings marked as slot-sticky
  • [ ] OAuth redirect URIs registered for staging domains
  • [ ] Full smoke test run: auth flow, data write, external service call
FAQ

Why would a dev slot point to a production database? Azure App Service slots don’t isolate app settings by default. Create a new slot and it inherits or falls back to the parent app’s values unless you explicitly override each one. The slot starts up and responds to requests — there’s no warning that it’s writing to prod.

What does enforceCertificateNameCheck do in Azure Front Door? It validates that the SSL cert on the backend matches the origin hostname. For staging dev slots, the cert is *.azurewebsites.net but AFD is routing a custom domain — that mismatch causes a 502. Setting it to false for staging origins fixes this without disabling TLS.

How do I find all dev slot hostnames accurately? Don’t trust documentation. Run az webapp deployment slot list --resource-group <rg> --name <app> to get the actual slot names, then az webapp show on each slot for the confirmed defaultHostName. Slot naming varies depending on how the slot was created.

What are slot-sticky settings and why do they matter? Slot settings (marked with --slot-settings) stay with the slot when you do a slot swap. Without this, doing a swap to promote staging to production would overwrite your production database credentials with staging ones — or vice versa. Always mark environment-specific connection strings as slot-sticky.

B
Mohammed Bilal Meccai
Senior DevOps Engineer · Systems Thinker

I turn complex infrastructure chaos into clean, working systems — production ELT pipelines, cloud cost optimisation, AI-native DevOps tooling. I write about the real problems I solve and the patterns I find in systems others miss.

bilalmeccai.com · bilalmeccai@gmail.com

Got an infrastructure problem?
I solve these kinds of problems fast.
Let's talk about yours.
Work With Me →