Secrets that never expire are one of the quietest risks in a cloud environment. A database connection string issued three years ago, a service principal secret nobody remembers creating, an API key copied into five different app settings — each one is a standing credential that, if leaked, gives an attacker a durable foothold. Following Azure Key Vault secret rotation best practices is how you close that window without breaking production every time a secret changes.
This guide walks through a practical, repeatable process: inventorying what you have, configuring a Key Vault rotation policy, wiring up automation so rotation doesn't depend on a human remembering, setting up expiration notifications, and validating that consuming applications actually pick up new versions without downtime. By the end, you'll have a rotation workflow you can apply across subscriptions, not just a one-off fix for a single vault.
Step 1: Inventory Every Secret and Its Consumers
Before you rotate anything, you need to know what exists and who depends on it. Pull a full list of secrets across every vault in your tenant:
az keyvault list --query "[].name" -o tsv | while read vault; do
az keyvault secret list --vault-name "$vault" \
--query "[].{name:name, updated:attributes.updated, expires:attributes.expires}" -o table
done
For each secret, document:
- Type — database credential, storage account key, API token, certificate password, service principal secret.
- Consumers — which apps, functions, pipelines, or scripts read it, and how (app setting, environment variable, direct SDK call).
- Current age and expiration — secrets with no
expiresattribute set are the highest-priority fix; they can live forever by default. - Rotation difficulty — some secrets (like a service principal password) can be rotated with zero downtime; others (a hardcoded key baked into a config file at build time) require a code change.
This inventory is what turns rotation from a scramble into a schedule. Skip it and you'll inevitably rotate something a forgotten script still depends on.
Step 2: Configure a Key Vault Rotation Policy
Azure Key Vault supports native rotation policies for secrets, which define how often a secret should rotate and when to fire a near-expiry notification. This is the mechanism that turns rotation into a managed lifecycle instead of a manual chore.
Set a rotation policy on a secret using a JSON policy file:
{
"lifetimeActions": [
{
"trigger": { "timeAfterCreate": "P60D" },
"action": { "type": "Rotate" }
},
{
"trigger": { "timeBeforeExpiry": "P10D" },
"action": { "type": "Notify" }
}
],
"attributes": { "expiryTime": "P90D" }
}
Apply it with:
az keyvault secret rotation-policy update \
--vault-name my-prod-vault \
--name db-connection-string \
--value rotation-policy.json
This particular Key Vault rotation policy sets a 90-day expiry, triggers rotation at 60 days, and fires a notification event 10 days before expiry so you have a buffer even if automation fails. Standardize this policy across secret types where possible — a consistent 60/90-day cadence is far easier to reason about and audit than ad hoc expiration dates scattered across vaults.
Step 3: Automate Secret Rotation with Event Grid and Azure Functions
A rotation policy only defines when a secret should rotate — it doesn't rotate anything on its own for most secret types (this native auto-rotation currently applies to specific integrations like storage account keys). For everything else, you need to automate secret rotation in Azure using Event Grid to trigger a rotation function.
Subscribe an Azure Function to the SecretNearExpiry and SecretExpired events:
az eventgrid event-subscription create \
--name kv-rotation-trigger \
--source-resource-id /subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.KeyVault/vaults/my-prod-vault \
--endpoint-type azurefunction \
--endpoint /subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.Web/sites/kv-rotator/functions/RotateSecret \
--included-event-types Microsoft.KeyVault.SecretNearExpiry
Your function logic typically does three things: generate or request a new credential from the source system (SQL Server, a storage account, an external SaaS API), write the new value as a fresh secret version with az keyvault secret set, and — critically — verify the new version works before the old one is invalidated. For service principal secrets, this pattern is well suited to Managed Identity plus the Microsoft Graph API to create a new client secret and remove the expired one in the same run.
This is also the right layer to enforce guardrails: reject a rotation if the new value fails a basic connectivity check, and alert your on-call channel on any rotation failure rather than letting a function fail silently.
Step 4: Set Up Key Vault Expiration Notifications
Automation will occasionally fail — a downstream API is down, a function throws, a permission gets revoked. Key Vault expiration notification events are your safety net, and they should reach a human, not just a log.
Wire Event Grid to a Logic App or webhook that posts to your alerting channel:
az eventgrid event-subscription create \
--name kv-expiry-alerts \
--source-resource-id /subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.KeyVault/vaults/my-prod-vault \
--endpoint-type webhook \
--endpoint https://your-logic-app-trigger-url \
--included-event-types Microsoft.KeyVault.SecretNearExpiry Microsoft.KeyVault.SecretExpired
Route SecretNearExpiry to a low-urgency Slack or Teams channel and SecretExpired to a paging tool like PagerDuty or Opsgenie — an expired secret is an active incident, not a backlog item. Keep the notification payload informative: include the vault name, secret name, and expiration timestamp so whoever picks it up doesn't have to go hunting.
Step 5: Version Secrets Without Breaking Consumers
The most common rotation failure isn't the rotation itself — it's an application that cached the old secret value or was pointed at a specific version instead of the latest one. Two practices prevent this:
- Reference secrets without pinning a version wherever your platform supports it. In App Configuration or App Service Key Vault references, omit the version suffix so the app always resolves to the current value:
@Microsoft.KeyVault(SecretUri=https://my-prod-vault.vault.azure.net/secrets/db-connection-string/)
- Overlap old and new versions briefly. Never disable the previous version immediately after creating a new one. Key Vault keeps prior versions accessible by default — use that. Give consumers a short overlap window (minutes to a few hours, depending on caching behavior) before you disable the old version:
az keyvault secret set-attributes \
--vault-name my-prod-vault \
--name db-connection-string \
--version <old-version-id> \
--enabled false
For services with in-memory caching of secret values (common with connection pools and SDK clients), make sure the app either restarts to pick up the new version or actively polls Key Vault on a TTL rather than caching indefinitely.
Step 6: Enforce Least Privilege and Audit Trails — Azure Key Vault Secret Rotation Best Practices in Action
Rotation only reduces risk if the rotation process itself is tightly scoped. Use Azure RBAC (or access policies on older vaults) so that only the automation identity and designated operators can write secret values:
az role assignment create \
--role "Key Vault Secrets Officer" \
--assignee <rotation-function-managed-identity-id> \
--scope /subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.KeyVault/vaults/my-prod-vault
Enable diagnostic logging on every vault and route it to Log Analytics or a SIEM so every SecretSet, SecretGet, and SecretDelete is auditable:
az monitor diagnostic-settings create \
--name kv-audit-logs \
--resource /subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.KeyVault/vaults/my-prod-vault \
--workspace <log-analytics-workspace-id> \
--logs '[{"category": "AuditEvent", "enabled": true}]'
This gives you an evidence trail for compliance audits and lets you quickly spot anomalies — like a secret being read from an unfamiliar identity or region right after rotation.
Verification and Troubleshooting
After setting up rotation, confirm it actually works end-to-end before trusting it in production:
- Check the active version resolves correctly:
az keyvault secret show --vault-name my-prod-vault --name db-connection-string --query "value"should return the new value immediately after rotation completes. - Confirm consumers are healthy post-rotation: watch application logs and error rates for a spike in authentication failures in the minutes following a rotation — this usually means a consumer pinned an old version or cached the secret longer than the overlap window.
- Rotation policy not triggering: verify the policy is actually attached to the secret (
az keyvault secret rotation-policy show) and that the vault's access policy or RBAC role grants the automation identityrotateandsetpermissions — a silent permission failure is the most common cause of "nothing happened." - Missing expiration notifications: check that the Event Grid subscription's
includedEventTypesmatches the events you expect, and that the endpoint (Logic App, webhook, Function) is returning a 200 — Event Grid will retry and eventually dead-letter events that consistently fail delivery. - Duplicate or conflicting versions: if two rotation jobs run concurrently (a manual rotation plus a scheduled one), you can end up with a newer secret being immediately superseded. Use a distributed lock or a status tag on the secret to prevent overlapping rotation runs.
- Test in staging first: run the full rotation cycle against a non-production vault with a synthetic consumer before enabling it for a vault backing customer-facing services.
How Safeguard Helps
Rotation policies and Event Grid automation solve the Key Vault side of the problem, but secrets don't stay inside Key Vault — they end up hardcoded in repos, leaked into CI/CD logs, embedded in container images, or copied into a teammate's local .env file. Safeguard is built to close that gap across the software supply chain.
Safeguard continuously scans source repositories, build pipelines, and container images for hardcoded credentials and stale references to secrets that should have been rotated out of use, flagging them before they ship. It correlates findings against your actual Key Vault inventory, so you can see at a glance which secrets are enforced by a rotation policy and which have drifted outside of it — a service principal secret three teams still reference directly instead of through Key Vault, for example. Combined with SBOM-level visibility into dependencies and build artifacts, Safeguard gives security teams a single view of secret exposure that spans code, pipeline, and cloud configuration, so Azure Key Vault secret rotation best practices translate into fewer live, exploitable credentials anywhere in your environment — not just fewer expired ones in the vault.