App Service configuration and networking — startup, build, deployment auth, outbound IPs, VNet integration, Key Vault references
How an App Service app is configured to run and reach other services — Linux startup for Node.js (PORT, PM2, startup command), build automation settings, deployment methods and basic authentication, slot-sticky settings, inbound and outbound IP addresses (and Premium V4's lack of stable ones), regional VNet integration with outboundVnetRouting, access restrictions, health check, managed identity endpoints and Key Vault reference syntax, identity and refresh.
markdownreferencev176WR-H9ZXstage: buildtrust: officialservice: app-serviceinterface: iacinterface: cliApp Service Node.js startupprocess.env.PORTpm2 start --no-daemonaz webapp config set --startup-fileSCM_DO_BUILD_DURING_DEPLOYMENTPRE_BUILD_COMMANDPOST_BUILD_COMMANDOryx buildaz webapp deploybasicPublishingCredentialsPoliciesdeployment slot setting stickyoutboundIpAddressespossibleOutboundIpAddressesPremium V4 outbound IP not stableregional VNet integration /28 /26 MPSJMicrosoft.Web/serverFarms delegationoutboundVnetRouting allTrafficvnetRouteAllEnabled legacyWEBSITE_VNET_ROUTE_ALLaccess restrictionshealthCheckPathIDENTITY_ENDPOINT IDENTITY_HEADER@Microsoft.KeyVault(SecretUri=)@Microsoft.KeyVault(VaultName=;SecretName=)keyVaultReferenceIdentityKey Vault reference 24 hoursaz webapp list-runtimes
App Service configuration and networking
Running a Node.js app on Linux
- Listen on
process.env.PORT. App Service sets it and forwards requests there. (Custom containers: 80 or 8080, orWEBSITES_PORT.) - Startup. With a
startscript, the container runsnpm start(Microsoft calls it development use). For production, set a startup command that runs PM2 in the foreground:az webapp config set -g <rg> -n <app> --startup-file "pm2 start dist/server.js --no-daemon". After Node 14 the container no longer starts PM2 on its own. - Runtime versions change with Node's release cycle; read them live:
az webapp list-runtimes --os-type linux. - Build automation (Oryx) runs for Git deployments, and for zip deployments only with
SCM_DO_BUILD_DURING_DEPLOYMENT=true:npm install, thennpm run buildandnpm run build:azureif defined, withPRE_BUILD_COMMAND/POST_BUILD_COMMANDhooks. Without it, the zip must already containnode_modulesand compiled output. App Service runs Node in production mode: testNODE_ENV=productionlocally.
Deploying, and authenticating the deployment
az webapp deploy -g <rg> -n <app> --src-path app.zip --type zipuses the Kudu publish API with your Entra sign-in. It's the scripted default.- Basic auth (publish profiles, FTP, Web Deploy user and password) is controlled by
basicPublishingCredentialsPolicies/scmand/ftp. Turn both off; deploy with Entra ID or GitHub Actions OIDC (patterns/github-actions-to-azure.md). FTP basic auth needs SCM basic auth on. - Slots (the number allowed depends on the tier: App Service limits) let you deploy to staging, warm it, then swap. A setting marked deployment slot setting stays with the slot; everything else swaps with the code.
Outbound and inbound addresses
| Need | Read it with |
|---|---|
| Addresses the app uses now | az webapp show -g <rg> -n <app> --query outboundIpAddresses -o tsv |
| Every address it could use, across tiers in the same deployment | az webapp show -g <rg> -n <app> --query possibleOutboundIpAddresses -o tsv |
- The current set changes when you scale between tier families (Basic/Standard/Premium, PremiumV2, PremiumV3); the possible set can grow when Microsoft adds tiers. Allow-list the possible set.
- Premium V4 provides no stable set of outbound IPs, by design: the portal shows "Dynamic" and the CLI and ARM return empty strings. Don't allow-list it by IP; use VNet integration with private endpoints or service endpoints (or, the hub's reading, route egress through a NAT gateway you control).
- Same-region storage accounts never see these public IPs (
reference/storage/network-and-firewall.md). - The app has one inbound IP. It can change if you delete and re-create the app in another resource group, delete the last app in a resource group and region and re-create it, or delete an IP-based TLS binding. Restrict inbound with access restrictions (a priority-ordered list of IP ranges and VNet subnets through service endpoints), or a private endpoint for the app.
Regional VNet integration
- An unused subnet,
/28or larger (/26or larger when several plans join it, called MPSJ), delegated toMicrosoft.Web/serverFarms, in the same region. Its size can't change later: leave room for scale-out and platform upgrades. - Routing:
outboundVnetRouting.allTraffic: true(recommended) sends all outbound traffic, including image pulls and content share access, through the VNet;applicationTrafficcovers app traffic only. The oldervnetRouteAllEnabledsite setting andWEBSITE_VNET_ROUTE_ALLapp setting still work. Both properties exist inMicrosoft.Web2025-03-01. - With route-all on and no service endpoint, private endpoint or route, calls to storage accounts in other regions can fail.
Managed identity and Key Vault references
- The app reaches its identity through
IDENTITY_ENDPOINTandIDENTITY_HEADER; the SDKs read them (guides/managed-identity/use-managed-identity.md). - Key Vault reference as an app setting value:
@Microsoft.KeyVault(SecretUri=https://<vault>.vault.azure.net/secrets/<name>)or@Microsoft.KeyVault(VaultName=<vault>;SecretName=<name>). Without a version, the app picks up new versions within 24 hours; any config change refetches at once. - References use the system-assigned identity unless you set
keyVaultReferenceIdentityto a user-assigned identity's resource ID (az webapp update -g <rg> -n <app> --set keyVaultReferenceIdentity=<identity-resource-id>). That identity needs Key Vault Secrets User and a network path to the vault. - Don't depend on the app's public outbound IPs in the vault firewall: use VNet integration with route-all and a private endpoint, or a service endpoint.
Health check
There's no default path. Set one (healthCheckPath) that checks the app itself quickly and without authentication. Instances are pinged every minute; after 10 failed requests (non-2xx) an instance leaves the load balancer, and returns when healthy. Run at least two instances for it to route around a bad one: a single unhealthy instance stays in rotation and is replaced after an hour of failed pings.
Errors: reference/app-service/errors.md. Single-tenant hosting in your VNet: reference/app-service/app-service-environment.md.