Azure Key Vault is Azure's managed store for secrets (connection strings, API keys, passwords), cryptographic keys that never leave the service, and certificates with their lifecycle. Almost every Azure workload touches it, often indirectly: App Service settings, AKS pods, storage accounts with customer-managed keys and data pipelines all resolve credentials through a vault at startup. When a vault throttles or denies access, all of those fail together.

This article explains the vault as a system: its object model, the control and data planes, the 2026 change in default access model, network isolation, reading secrets and using keys from code, the throttling limits, soft delete and purge protection, rotation, monitoring, and the failure modes that show up in production. Limits and behaviours were checked against Microsoft Learn on 2026-10-03. For the vendor-neutral background, read Cloud KMS architecture and cloud secrets architecture.

The architecture in one picture

Azure Key Vault: identities, planes, network and eventsApp / AKS pod / VMmanaged identityMicrosoft Entra IDissues access tokentoken requestPrivate endpointprivatelink.vaultcore.azure.netHTTPS + tokenVault data planeSecrets (25 KB, versions)Keys (wrap, sign, decrypt)Certificates (key + secret)Azure RBAC or access policiesARM control planecreate vault, network, rolesEvent GridNearExpiry, NewVersionRotation functionnew credential, new versionLog AnalyticsAuditEvent logs, metricseventsset secretdiagnosticsSoft delete keeps deleted objects for 7 to 90 days; purge protection stops anyone purging early.
Workloads authenticate with managed identities, reach the vault over a private endpoint, and read secrets or use keys on the data plane; Event Grid drives rotation and diagnostics flow to Log Analytics.

Vaults, tiers and objects

Key Vault has two resource types. A vault is multi-tenant and comes in two tiers. Standard holds secrets, certificates and software-protected keys. Premium adds HSM-protected keys. Managed HSM is a separate, single-tenant resource with its own limits, for workloads that need dedicated HSMs or high key-operation throughput. This article is mostly about vaults.

A vault holds three kinds of objects:

  • Secrets are opaque values up to 25 KB. Key Vault adds no meaning to them. You can set a contentType hint of up to 255 characters, up to 15 tags, and the attributes nbf (not before), exp (expiry) and enabled.
  • Keys are RSA or elliptic-curve keys. You call operations on them (encrypt, decrypt, wrap, unwrap, sign, verify), and for non-exportable keys the private material never leaves the service.
  • Certificates combine a key, a secret holding the full certificate with its private key, and issuance and renewal policy. Renewal can be automated with integrated certificate authorities.

Every object is versioned and addressed by URI: https://<vault>.vault.azure.net/secrets/<name>/<version>. Leaving the version off means "current version". Setting a secret always creates a new version and never edits an old one, and that property is what makes safe rotation possible.

Two planes and two access models

Key Vault has two planes. The control plane is Azure Resource Manager: create the vault, set network rules, configure diagnostics, assign roles. The data plane is the vault endpoint itself: read a secret, sign with a key. Both authenticate with Microsoft Entra ID tokens, but they are authorized separately.

Data-plane authorization has two models. Access policies are the legacy per-vault list of principals and permissions. Azure RBAC uses role assignments that can be scoped down to a single secret or key. Built-in roles include Key Vault Administrator, Key Vault Secrets User (read secret values), Key Vault Secrets Officer (manage secrets), Key Vault Crypto User and Key Vault Reader (metadata only, no values). RBAC has a security advantage beyond granularity. Under access policies, anyone who can write the vault resource on the control plane can add themselves to the policy and read every secret. Under RBAC, granting data access requires permission to create role assignments, which is a separate, auditable right.

The default changed in 2026. Creating a vault with control-plane API version 2026-02-01 or later enables RBAC by default (enableRbacAuthorization = true). Existing vaults keep their model, and both models remain supported. Microsoft states that control-plane API versions older than 2026-02-01 retire on 27 February 2027. Infrastructure-as-code that pins an old API version, or that relies on access policies on new vaults, must be updated before then.

Network isolation

A vault has a public endpoint by default. For production, disable public network access and reach the vault through a private endpoint in your virtual network, with the privatelink.vaultcore.azure.net private DNS zone linked so the vault name resolves to a private IP. The vault firewall can alternatively allow selected networks, and there is an option to let trusted Microsoft services bypass it.

Watch the clients that run outside your network. CI agents, developer laptops and Azure services that are not VNet-integrated will start failing with 403 once public access is off. App Service documents a specific quirk: audit logs may show a failed 403 SecretGet from the app's public IP followed by a successful one from its private IP, and that is by design.

Reading secrets from code and platforms

Applications should authenticate with a managed identity, never with a client secret stored somewhere else, which only moves the problem. In Python, DefaultAzureCredential picks up the managed identity in Azure and your developer login locally:

# pip install azure-identity azure-keyvault-secrets
import time
from azure.identity import DefaultAzureCredential
from azure.keyvault.secrets import SecretClient

VAULT_URL = "https://payments-prod-weu.vault.azure.net"
client = SecretClient(vault_url=VAULT_URL, credential=DefaultAzureCredential())

_cache = {}   # name -> (value, fetched_at)
TTL = 300     # seconds; bounded staleness, and protects the vault from bursts

def get_secret(name):
    hit = _cache.get(name)
    if hit and time.time() - hit[1] < TTL:
        return hit[0]
    value = client.get_secret(name).value     # current version
    _cache[name] = (value, time.time())
    return value

db_password = get_secret("orders-db-password")

Create one client per process and reuse it, because the credential caches tokens. Cache secret values with a TTL, because a vault read on every request is the most common cause of throttling. The SDK retries 429 responses using the service's Retry-After header, but retries cannot fix a design that exceeds the limit.

Platform integrations avoid code entirely. App Service, Azure Functions and Logic Apps Standard accept settings such as @Microsoft.KeyVault(SecretUri=https://myvault.vault.azure.net/secrets/mysecret) or @Microsoft.KeyVault(VaultName=myvault;SecretName=mysecret). They resolve with the system-assigned identity unless keyVaultReferenceIdentity names a user-assigned one. Versionless references are cached and refetched every 24 hours, or immediately on any configuration change. On AKS, the Azure Key Vault provider for the Secrets Store CSI Driver mounts secrets into pods. Azure Functions in depth covers the Functions side.

Keys and envelope encryption

Keys follow the envelope-encryption pattern. You generate a random data encryption key (DEK) locally, encrypt data with it, then ask the vault to wrap the DEK with a key encryption key (KEK) that never leaves the vault. You store the wrapped DEK next to the ciphertext. To decrypt, you ask the vault to unwrap it.

# pip install azure-keyvault-keys cryptography
import os
from azure.identity import DefaultAzureCredential
from azure.keyvault.keys import KeyClient
from azure.keyvault.keys.crypto import CryptographyClient, KeyWrapAlgorithm
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

cred = DefaultAzureCredential()
kek = KeyClient("https://payments-prod-weu.vault.azure.net", cred).get_key("records-kek")
crypto = CryptographyClient(kek, credential=cred)

dek = AESGCM.generate_key(bit_length=256)
nonce = os.urandom(12)
ciphertext = AESGCM(dek).encrypt(nonce, b"card-token-batch", None)
wrapped = crypto.wrap_key(KeyWrapAlgorithm.rsa_oaep_256, dek)
# persist: ciphertext, nonce, wrapped.encrypted_key, wrapped.key_id  (key_id pins the KEK version)

dek_again = crypto.unwrap_key(KeyWrapAlgorithm.rsa_oaep_256, wrapped.encrypted_key).key
# after KEK rotation: CryptographyClient(stored_key_id, cred).unwrap_key(...) uses the old version

Store the key ID returned by the wrap, which includes the version. After the KEK rotates, old data still unwraps with the old version, and you re-wrap DEKs lazily. Azure services that use customer-managed keys, such as Storage, follow the same model and require purge protection on the vault, because purging the KEK makes the data unrecoverable.

Throttling limits and a worked example

Vault throughput is limited per vault per region in 10-second windows, and enforcement is on a weighted sum. These are the limits from Microsoft's service-limits page:

OperationLimit per 10 s
Secret, managed storage and vault transactions (get, list and so on)4,000
Create secret + import certificate + import key, combined300
RSA 2,048 software key, non-create operations4,000
RSA 2,048 HSM key, non-create operations2,000
RSA 4,096 HSM key, non-create operations250
Create key or secure key release, HSM key10

Weights mean an RSA 4,096 HSM operation costs eight times an RSA 2,048 HSM operation (2,000 / 250). A subscription-wide limit applies too, at five times the per-vault limit. Worked example: a deployment rolls 500 pods, and each pod reads 10 secrets at startup without caching. That is 5,000 reads in a few seconds, over the 4,000 limit, so the tail of the rollout gets 429s and slow restarts. Fixes, in order: cache in the process, load secrets once per pod rather than per request, roll out in waves, and split unrelated applications into separate vaults. Splitting is also the right security boundary.

Soft delete and purge protection

Soft delete is on for new vaults and cannot be turned off. Deleted vaults and objects stay recoverable for a retention period of 7 to 90 days (default 90). The period is fixed when the vault is created. While a vault is soft-deleted its name cannot be reused, which breaks naive tear-down-and-recreate test pipelines. Deleting a vault also deletes its RBAC role assignments and Event Grid subscriptions, and recovering the vault does not bring them back.

Purge protection is off by default. Once on, nobody, including an owner, can purge a deleted object or vault before the retention period ends, and it cannot be switched off again. Turn it on for any vault holding encryption keys. Without it, an attacker with purge rights can destroy keys and therefore data.

Rotation and monitoring

Keys support a rotation policy: the vault creates a new key version on a schedule or a set time before expiry, and consumers that reference the versionless key pick it up. Secrets cannot rotate themselves, because the vault cannot change a database password. Instead, Event Grid publishes Microsoft.KeyVault.SecretNearExpiry 30 days before exp, plus SecretExpired and SecretNewVersionCreated. Subscribe a function to the near-expiry event that creates a new credential in the target system, writes it as a new secret version with a fresh exp, and keeps the old credential valid until every consumer has refreshed. The dual-credential pattern is described in secrets rotation on AWS and applies unchanged here.

For monitoring, send the AuditEvent diagnostic category to Log Analytics, and alert on throttling, denied access and unusual callers:

AzureDiagnostics
| where ResourceProvider == "MICROSOFT.KEYVAULT" and TimeGenerated > ago(1h)
| summarize calls = count(),
            throttled = countif(httpStatusCode_d == 429),
            denied    = countif(httpStatusCode_d == 403)
          by Resource, OperationName, CallerIPAddress
| where throttled > 0 or denied > 0
| order by throttled desc, denied desc

Failure modes

  • 429 storms at deploy time: uncached reads multiplied by replicas. Cache, stagger rollouts, split vaults.
  • 403 after locking down the network: a client outside the VNet, or a private DNS zone not linked to the client's network so the name still resolves publicly.
  • 403 after an access-model change: switching a vault from access policies to RBAC without first creating equivalent role assignments.
  • Unresolved App Service references: the setting falls back to the literal @Microsoft.KeyVault(...) string, and the app fails with a confusing credential error.
  • Expired secrets nobody noticed: exp is informational and does not block reads. Without an Event Grid subscription, the dependency fails at the downstream system instead.
  • Name collisions after deletion: soft-deleted vaults and objects hold their names until purged or expired.

Trade-offs

One vault or many? Use one vault per application, per environment, per region. That bounds the blast radius of a leaked role assignment, spreads throttling limits, and makes deletion safe. Large shared vaults are convenient until one noisy consumer throttles everyone.

Vault, Premium or Managed HSM? Standard suits secrets and software keys. Premium adds HSM-protected keys, at half the software-key throttling rates. Managed HSM is for dedicated tenancy and high key-operation rates. Compare with OCI Vault if you run multi-cloud.

Key Vault references or SDK? References need no code but refresh only every 24 hours. The SDK gives you control over caching and rotation timing, at the cost of code you must test.

What to do next

  1. List your vaults and record each one's access model, public network setting, soft-delete retention and purge protection.
  2. Turn on purge protection for every vault holding encryption keys.
  3. Update infrastructure code to control-plane API 2026-02-01 or later before 27 February 2027, and set the access model explicitly.
  4. Move data-plane access to RBAC with the narrowest built-in role, scoped per vault or per secret.
  5. Put production vaults behind private endpoints with the private DNS zone linked, and test CI and operator access paths.
  6. Add in-process caching with a TTL to every service that reads secrets, then load-test a full rollout against the 4,000-per-10-second limit.
  7. Set exp on rotating secrets, subscribe to SecretNearExpiry, and automate rotation with overlapping credentials.
  8. Send AuditEvent logs to Log Analytics and alert on 429 and 403 counts.
Key takeaway: Azure Key Vault stores secrets, keys and certificates behind Entra ID authentication, with separate control and data planes. New vaults created with API 2026-02-01 or later default to Azure RBAC, which you should prefer anyway. Reach vaults over private endpoints, read secrets with managed identities and cache them, because limits are per vault per 10 seconds. Turn on purge protection for key vaults, automate rotation from Event Grid events, and alert on 429 and 403 responses.