Azure App Service, Azure Key Vault, .NET 10, and a config issue that turned a simple secret update into a full redeployment.
The problem
I recently ran into a configuration issue on a .NET backend running in Azure App Service that I didn't expect at all.
The setup was pretty standard. The backend was deployed to App Service, its settings lived in App Service environment variables, and anything sensitive was a Key Vault reference. The code read everything through the normal .NET IConfiguration system.
It all worked fine until we had to change one of the secrets.
We updated the value in Key Vault and expected the backend to pick it up. It didn't. The app kept using the old value.
That part didn't surprise me much. Config gets cached all the time, so I restarted the App Service.
The backend still behaved as if the old secret was in place.
What finally fixed it was rebuilding and republishing the backend. That left me with a question I couldn't let go of: why would redeploying the code be needed for a value that doesn't even live in the code?
How Key Vault references reach your app
It helps to walk through how the value actually travels.
Say you have an app setting called ExternalApi__ApiKey. Instead of putting the API key in App Service directly, you point it at Key Vault:
@Microsoft.KeyVault(SecretUri=https://my-keyvault.vault.azure.net/secrets/external-api-key/)In the backend you read it like any other setting:
var builder = WebApplication.CreateBuilder(args);
var apiKey = builder.Configuration["ExternalApi:ApiKey"];The double underscore in the environment variable name maps to the colon .NET uses for nested keys.
So the value takes this path:
Azure Key Vault
|
| secret value
v
Azure App Service
|
| resolves the Key Vault reference
v
App Service environment variable
|
| .NET configuration provider
v
ASP.NET Core backend
|
v
Your servicesThe key detail is that App Service resolves the Key Vault reference and hands your app a plain environment variable. Your .NET code usually never talks to Key Vault at all.
That gives you two separate places where a value can go stale: App Service, and your app.
What actually happened
Here's the sequence, step by step.
Step 1: We updated the secret in Key Vault. Same secret name, new value.
Step 2: We tested the backend. Still the old value.
Step 3: We restarted the App Service. I expected the environment variable to be refreshed. It still looked like the old value.
Step 4: We rebuilt and republished the backend to the same App Service. After that deployment, the app used the new secret.
The redeploy got us unstuck, but it didn't prove the binaries needed rebuilding. Something in the deployment process refreshed the configuration, and a plain restart apparently didn't.
What Microsoft says about Key Vault refresh
Microsoft's App Service docs are clear that Key Vault references are cached. If a reference doesn't pin a version, App Service uses the latest one, but your app won't see every update straight away.
The documented behaviour is:
- References are refreshed automatically within 24 hours.
- Changing the App Service configuration triggers a restart and an immediate refetch of all references.
- There's a management API endpoint you can call to force a refresh.
So you should never need to rebuild a .NET app just to get a new Key Vault secret. If a restart doesn't fix it, something in the pipeline is holding on to the old value, and it's worth finding out which part.
Reference: Microsoft: Use Key Vault references as app settings
Likely causes
1. App Service is caching the reference
Updating a secret in Key Vault doesn't instantly update the environment variable App Service builds from it. For a versionless reference, App Service fetches the latest version on its own schedule.
A restart on its own can be misleading here, especially if the reference wasn't actually re-resolved. The reliable approach is to refresh the references explicitly and then check their resolution status.
2. The reference is pinned to one version
A reference can point at the latest version of a secret or at one specific version:
# Latest version
@Microsoft.KeyVault(SecretUri=https://myvault.vault.azure.net/secrets/api-key/)
# One specific version
@Microsoft.KeyVault(SecretUri=https://myvault.vault.azure.net/secrets/api-key/abc123)The second one stays on abc123 forever. You can add as many new versions as you like and it won't move until you change the reference. If you rotate secrets, use versionless references.
3. The app captured the value at startup
How your code reads configuration matters too:
public class ExternalApiService
{
private readonly string? _apiKey;
public ExternalApiService(IConfiguration configuration)
{
_apiKey = configuration["ExternalApi:ApiKey"];
}
}The key is read once, when the service is created. If the configuration changes while that instance is alive, _apiKey won't change with it. The same goes for anything copied into a singleton or a static field.
That said, a real restart starts a fresh process, and if the environment variables were refreshed properly the value should be read again. So startup caching alone doesn't explain a problem that survives a successful restart.
4. Another config source is winning
ASP.NET Core can read settings from several places:
appsettings.jsonappsettings.Production.json- Environment variables
- Command-line arguments
- Any extra configuration providers you've added
They have a defined order of precedence. Command-line arguments, for example, override environment variables in the default setup, and a custom provider can change things further. It's worth confirming that the value your code is using really comes from the App Service environment variable you think it does.
5. The deployment changed more than the code
This was the interesting part for us. A deployment can do a lot more than swap binaries. Depending on your pipeline, it might:
- Restart or recycle the app processes
- Change App Service configuration
- Apply deployment-specific settings
- Replace files that contain configuration
- Start the app in a different way
Any of these could explain why the new deployment got the right value when the restart didn't. Without better diagnostics at the time, I can't say for certain which one it was.
A better fix: refresh the references without redeploying
Microsoft provides a management API just for this. Instead of rebuilding and publishing the whole backend, you can ask App Service to refresh its Key Vault backed settings:
az rest --method post \
--uri "https://management.azure.com/subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.Web/sites/<app-name>/config/configreferences/appsettings/refresh?api-version=2022-03-01"You'll need the right Azure management permissions to run it.
Afterwards, check that the reference resolved correctly and that the running app is actually using the new value. If your code captures configuration at startup, you may still need a controlled restart.
The point is that refreshing configuration or restarting the app is not the same as rebuilding and republishing your code, and it's a lot cheaper.
How I'd troubleshoot it next time
If this happens again, this is the order I'll work through:
- Check the secret version. Make sure the new value saved in Key Vault and that the App Service reference points at the version you expect.
- Look at the App Service setting. Open the Key Vault reference in the App Service configuration and check its resolution status.
- Force a refresh. Call the management API above.
- Restart if needed. Restart the App Service and confirm a new process actually started.
- Find the effective config source. Work out which provider is supplying the value. Log only non-sensitive diagnostics, never the secret itself.
- Compare before and after a deployment. If redeploying still seems like the only fix, compare the App Service configuration and process state on either side of it.
That should tell you whether the problem is in Azure's reference resolution, in how .NET loads configuration, or in the deployment pipeline.
Does .NET 10 change anything?
Not really. This behaviour isn't specific to .NET 10. ASP.NET Core 10 still supports the same configuration providers, environment variables included, and an app reading a Key Vault reference through an environment variable still depends on App Service resolving it.
It's also worth not confusing IOptionsMonitor<T> and similar patterns with Key Vault refresh. They react to change notifications from providers that support them, but the environment variable provider doesn't poll Key Vault for you.
If you need secrets to rotate in near real time, look at a different approach, such as Azure App Configuration with an explicit Key Vault secret refresh interval.
Final thoughts
What looked like a simple Key Vault update turned into an unexpected deployment problem. The secret was updated, the backend kept using the old one, a restart didn't seem to help, and a full redeploy did.
The lesson isn't that .NET apps need rebuilding when Key Vault secrets change. They don't.
It's that configuration passes through layers, and each one can hold on to an old value:
Key Vault stores the secret. App Service resolves and caches the reference. .NET loads and uses the result.
When something goes stale, finding the layer that's holding the old value beats redeploying until it works. Next time I'll start with an explicit reference refresh, not a full deployment.
Sometimes the most useful thing you learn from a bug is asking why an unrelated fix worked.
Technologies: .NET 10, ASP.NET Core, Azure App Service, Azure Key Vault, IConfiguration, environment variables, Azure CLI.