Cloud

Azure App Service: diagnose TLS renewal before rebinding production

A production runbook to separate certificate issuance, Key Vault version, App Service import and hostname binding before syncing, rebinding or rolling back TLS.

01 Sept 2026 azureapp-servicetlscertificatekey-vaultcustom-domainsecurityobservabilityazure-cliautomationrunbookrollbackproduction

An App Service certificate has been renewed, yet clients still receive the old leaf certificate. The portal shows a later expiration date, the deployment pipeline is green, and the application itself is healthy. The tempting response is to delete the TLS binding, upload the new PFX again, or restart every instance. Each action changes production before proving which certificate state is stale.

The use case is a web app with a custom hostname and SNI binding. Its certificate may be an App Service managed certificate, a purchased App Service Certificate backed by Key Vault, or a certificate imported from a customer-managed vault. The runbook separates issuance, secret version, App Service certificate inventory, hostname binding and the certificate served at the edge. It ends with a bounded decision: wait, sync, import, rebind, fix validation or access, or restore the previous thumbprint.

Freeze the hostname and the certificate chain

Do not start from the certificate display name. Record the exact hostname, application, resource group, observed thumbprint, expected thumbprint, expiration dates and first UTC observation. Preserve the current binding before changing it.

yaml app-service-tls-incident.yml
incident:
observed_at_utc: 2026-09-01T07:20:00Z
hostname: api.example.net
app: api-prod
resource_group: rg-app-prod

served_certificate:
sha256_fingerprint: <observed-fingerprint>
not_after: <observed-expiration>

expected_certificate:
source: managed-or-app-service-certificate-or-key-vault
thumbprint: <expected-thumbprint>
secret_version: <expected-version-or-not-applicable>
not_after: <expected-expiration>

preserve:
- hostname binding and SSL type
- certificate resource state
- Key Vault secret versions when applicable
- activity log around renewal and binding changes
- TLS probe output from at least two networks

stop_conditions:
- expected certificate does not cover the hostname
- private key or chain cannot be validated
- another hostname depends on the binding being changed
- rollback thumbprint is no longer available

A certificate resource marked renewed is not end-to-end proof. The served certificate is the one returned for the real hostname and SNI, so collect it independently from Azure control-plane state.

Read what clients actually receive

Probe the production hostname with SNI. Avoid testing only the default azurewebsites.net address or only a backend IP, because either test can select a different binding.

bash 01-read-served-certificate.sh
HOST="api.example.net"

echo | openssl s_client \
-connect "$HOST:443" \
-servername "$HOST" \
-showcerts 2>/dev/null \
| openssl x509 \
    -noout \
    -subject \
    -issuer \
    -serial \
    -fingerprint \
    -sha256 \
    -dates \
    -ext subjectAltName

curl --silent --show-error --head \
--connect-timeout 5 \
--max-time 15 \
"https://$HOST/health"

Repeat from a controlled external probe and, when the hostname is internal, from the real caller network. Different certificates across probes can indicate DNS divergence, an upstream gateway or CDN, or a partial path that never reaches App Service. App Service cannot correct a certificate served by another TLS terminator.

Separate the four certificate states

Read the chain in order. First, issuance: does the candidate certificate exist, remain valid and cover the exact hostname in its SANs? Second, storage: when Key Vault is involved, does the expected secret version contain the current certificate and private key? Third, import: does App Service list the expected thumbprint? Fourth, binding: does the custom hostname point to that thumbprint with the intended SSL type?

bash 02-read-app-service-tls-state.sh
RG="rg-app-prod"
APP="api-prod"
HOST="api.example.net"

az webapp show \
--resource-group "$RG" \
--name "$APP" \
--query "{id:id,state:state,hostNames:hostNames,httpsOnly:httpsOnly}" \
--output json

az webapp config hostname list \
--resource-group "$RG" \
--webapp-name "$APP" \
--query "[?name=='$HOST'].{hostname:name,sslState:sslState,thumbprint:thumbprint}" \
--output json

az webapp config ssl list \
--resource-group "$RG" \
--query "[].{name:name,thumbprint:thumbprint,expiration:expirationDate,hostNames:hostNames,keyVaultId:keyVaultId,keyVaultSecretName:keyVaultSecretName}" \
--output table

Interpret the first mismatch instead of treating every layer as broken:

  • no valid candidate means issuance or domain validation is still incomplete;
  • a current Key Vault version with no matching App Service thumbprint means import or synchronization is stale;
  • a matching App Service certificate with an old binding thumbprint means the hostname binding is stale;
  • a current binding with an old certificate on the wire means the request terminates elsewhere, the probe uses a different hostname, or platform convergence still needs to be qualified.

Qualify the certificate source before fixing it

The correction depends on who owns renewal. An App Service managed certificate is platform-managed and should not be treated like an uploaded PFX. A purchased App Service Certificate has issuance, domain verification, Key Vault storage, import and sync stages. A customer-managed Key Vault certificate also has its own issuer, version lifecycle and App Service import path.

For an App Service Certificate, verify renewal status, automatic renewal, domain verification and the backing vault permissions before forcing sync. Domain ownership may need to be reconfirmed after the applicable validation period. Do not rekey merely to repair a delayed sync: rekeying changes the key material and creates a different security event.

For a certificate imported from Key Vault, compare secret versions and their activation dates. A new version existing in the vault does not prove that the App Service certificate resource has imported it. Confirm the intended version is enabled and contains a usable PFX before changing the binding.

Correlate renewal, import and binding changes

Use the Azure Activity Log to rebuild the control-plane sequence. The useful evidence is the operation, resource ID, caller, result and correlation ID around the renewal window.

kusto 03-certificate-control-plane-timeline.kql
let StartTime = datetime(2026-09-01T06:00:00Z);
let EndTime = datetime(2026-09-01T09:00:00Z);
AzureActivity
| where TimeGenerated between (StartTime .. EndTime)
| where ResourceProviderValue in~ ("MICROSOFT.WEB", "MICROSOFT.CERTIFICATEREGISTRATION", "MICROSOFT.KEYVAULT")
| where OperationNameValue has_any (
  "certificates",
  "hostnameBindings",
  "certificateOrders",
  "vaults/secrets"
)
| project TimeGenerated,
        OperationNameValue,
        ActivityStatusValue,
        Caller,
        ResourceId,
        CorrelationId,
        Properties
| order by TimeGenerated asc

Table names and operation strings can vary with diagnostic routing. The objective is a sequence that explains whether issuance completed, synchronization ran, import failed or a later deployment restored an older binding. A green application deployment does not validate TLS state unless the pipeline explicitly probes the served certificate.

Choose the smallest correction

text app-service-tls-decision-matrix.txt
Wait and observe
Renewal or import is still inside the documented convergence window
Old certificate remains valid beyond the observation window
No hostname or chain defect is present

Complete validation or access repair
Candidate is pending, denied or missing from storage
Domain ownership or Key Vault access is the first failed layer
The repair does not broaden application runtime permissions

Sync or import
New certificate is valid and stored
App Service inventory does not contain its thumbprint
Existing binding remains available for rollback

Rebind
New thumbprint is already present in App Service
SAN, chain and private key are validated
Hostname binding still references the old thumbprint

Roll back
New binding serves the wrong name or an incomplete chain
Representative clients fail after the change
Previous certificate remains valid and available

Stop
Another gateway terminates TLS
Certificate ownership is unclear
Expected thumbprint cannot be tied to an approved issuance

When rebinding is justified, change only the affected hostname and keep the old certificate until validation is complete. Deleting the old certificate first removes the fastest return path.

Validate and keep rollback open

After sync, import or rebind, repeat the same SNI probe from every relevant path. Require the expected SHA-256 fingerprint, SAN, issuer and expiration. Then verify HTTPS status, application health and any gateway or synthetic monitor that checks the hostname.

Add a pipeline gate that compares the expected thumbprint with the certificate served on the wire. Alert before expiration, but also alert when the binding thumbprint and served fingerprint diverge. Expiration monitoring alone misses a renewed certificate that never reached production.

Keep the previous binding record and certificate until the new chain has survived a representative observation window. Roll back the binding if clients reject the chain, the wrong hostname is served, or availability degrades. Do not roll back issuance simply because one binding was wrong.

Conclusion

An App Service TLS renewal is a chain: issuance, validation, Key Vault version, App Service import, hostname binding and the certificate actually served with SNI. A later expiration date in the portal proves only one part of that chain.

The production decision becomes reliable when the first mismatch is explicit. Repair validation when issuance is blocked, repair access when storage cannot synchronize, import or sync when App Service lacks the new thumbprint, rebind only when the candidate is already validated, and keep the previous thumbprint until external probes prove the new certificate end to end.