Azure App Service is the managed application host most teams reach for first, and it is the one most teams misjudge. It runs a startling share of the production web traffic on Azure, from internal line-of-business applications to public APIs serving millions of requests, and it does so by hiding the operating system, the patching, the load balancer, and the autoscaler behind a deployment that can be as simple as a single git push. That convenience is exactly why engineers stop reasoning about it. They pick a plan tier from a dropdown, deploy, and discover months later that the tier they chose silently caps their scale-out, forbids their deployment slot, or blocks the virtual network integration the security team now requires. The gap between using App Service and understanding it is the gap between an application that quietly outgrows its plan and a site whose plan was a deliberate decision from the first deployment.

Azure App Service worker model and plan tiers explained - Insight Crunch

This article is the engineering deep dive that the product page and the quickstart never give you. It builds the mental model of App Service as a compute plan that hosts sites on a worker fleet, explains how that fleet is provisioned and shared, walks the tier ladder and the precise capabilities each rung unlocks, and then takes you through the configuration that actually decides whether your workload behaves in production: deployment slots and the swap, scale-up versus scale-out, the always-on setting and the idle-unload behavior it defeats, and the difference between virtual network integration and a private endpoint that confuses nearly everyone the first time. By the end you should be able to look at a workload, name the lowest tier that satisfies its requirements, and predict the failure modes before they happen rather than after.

What Azure App Service Actually Is

The single most useful mental model is this: an App Service plan is a set of virtual machines that Azure manages for you, and an application (sometimes still called a Web App) is a unit of code or a container that runs on those virtual machines. You never see the machines directly, you do not RDP or SSH into them in the normal flow, and you do not patch them. Azure runs a worker fleet, places your site on one or more workers, fronts them with a load balancer, terminates TLS, and routes requests to your process. What you configure is the plan (how big the workers are and how many you are allowed to have) and the workload (your code, your settings, your bindings). Everything that frustrates people downstream traces back to whether they understood that the plan, not the application, owns the compute.

It helps to place these in the resource hierarchy. A plan and a site are both Azure resources that live in a resource group in a subscription, the plan is created in a region and pinned there, and a workload is always associated with exactly one plan at a time. Moving an application between plans is possible within the same region and the same resource group scope, which is how you promote a site from a smaller plan to a larger one, but a workload cannot straddle two plans or two regions at once. Deployment slots are themselves child resources of the application, each effectively a sibling site sharing the parent’s plan, which is why slots consume the plan’s capacity and why their count is bounded by the tier. Holding this hierarchy in mind, plan in a region, workloads on the plan, slots under the applications, makes the capacity and billing behavior follow logically rather than feeling like a set of unrelated rules, and it is the frame the rest of this article builds on.

What is Azure App Service and how does its worker model work?

App Service is a managed platform-as-a-service for hosting web sites, REST APIs, and background-capable web jobs without managing servers. Your workload runs inside a sandboxed worker process on a virtual machine defined by an App Service plan, and Azure handles the operating system, the load balancing, TLS termination, and scaling. You manage code and configuration, not infrastructure.

The plan is the unit of compute and the unit of billing. When you create a plan you choose a tier and a size, and from that moment you are paying for the virtual machine instances that back the plan regardless of how many workloads you run on them or how much traffic those applications receive. This is the first thing that surprises people coming from a consumption or per-request billing mental model: a Basic or Standard plan bills by the hour for the reserved instances, not by the request. An empty Standard plan with no sites still costs money because the instances are provisioned and waiting. Stopping an application does not stop the plan’s compute charge in the dedicated tiers; you stop paying by scaling the plan down, deleting it, or moving the site to a tier where the billing model differs.

Multiple workloads can live in one plan, and they share that plan’s compute. This is a genuine cost lever and a genuine footgun at the same time. Co-locating ten low-traffic internal applications in a single Standard plan is efficient because they share the reserved instances and the idle capacity. Co-locating your highest-traffic public API with nine other sites means those nine compete for the same CPU and memory, and a memory leak in one can pressure the others. The rule that follows is simple to state and easy to forget: compute is dedicated at the plan level, not the workload level, so isolation between workloads requires separate plans.

How does an application map to a worker instance?

Each site instance runs as a sandboxed process on a plan worker. When you scale the plan out to three instances, App Service runs a copy of your workload on each of the three workers and load-balances across them. The application must therefore be effectively stateless or externalize its state, because any request can land on any instance, and instances come and go.

The practical consequence of the multi-instance model is that local state betrays you. Files written to the local filesystem outside the shared content directory are not guaranteed to survive a restart or to be visible to other instances. In-memory session state lives on exactly one instance, so a user whose next request lands on a different worker loses it unless you have configured affinity or moved session state to a distributed store. App Service does offer an ARR affinity cookie that pins a client to an instance, and it is on by default for many configurations, but leaning on it is a design smell: it concentrates load unevenly and it does not survive instance recycling. The durable design externalizes session and cache state to Azure Cache for Redis or a database and treats every worker as disposable. This is the same disposability discipline you would apply to any horizontally scaled tier, and the Azure Virtual Machines complete guide makes the same argument about availability sets and zones: design for the instance you will lose, not the instance you have.

The content of your site, by default, lives on a shared network file store mounted into every worker, which is why a deployment is visible to all instances at once and why the local disk is not the right place for durable writes. That shared store is convenient and it is also a latency surface; applications that do heavy local file IO sometimes mistake the network-mounted content share for a fast local disk and pay for it in tail latency. Knowing where the bytes physically live is part of reasoning about why a workload is slow.

How App Service Works Internally

Underneath the abstraction, App Service is a multi-tenant system in the shared and lower dedicated tiers and a single-tenant system at the top. In the Free and Shared tiers your application runs on infrastructure shared with other customers’ sites, and your site receives a quota of CPU minutes rather than dedicated instances; exceed the quota and the workload is throttled or stopped until the quota window resets. These tiers cannot scale out at all, which makes them strictly development and experimentation tiers, never production. The moment you move to Basic or above, the plan provisions dedicated virtual machine instances that are not shared with other customers, though workloads inside your own plan still share them with each other.

The request path is worth holding in your head because it explains several failure modes. A request arrives at Azure’s front ends, which terminate TLS and apply platform-level routing, then reaches the load balancer for your plan, which distributes across your healthy instances, and finally arrives at the worker process running your application behind a local reverse proxy. Each hop has a behavior that can surface as a symptom. The front ends enforce request timeouts. The load balancer routes only to instances the platform considers healthy, and an instance that fails its health signal is taken out of rotation, which is good until every instance is failing and you get a platform-level error. The worker process itself can be cold (not yet started), recycling, or out of memory, and each of those produces a distinct symptom that a careful engineer learns to read rather than guess at. When a 503 appears, the discipline is to ask which hop produced it before changing anything, and that diagnostic method is the entire subject of the dedicated App Service 503 troubleshooting guide.

One front-end behavior deserves singling out because it ends long requests in a way people misread: the platform enforces an idle request timeout, so a request that produces no response data for an extended period is cut off by the front end regardless of what your application intended. An workload doing a long synchronous operation inside a request, a slow report generation or a large synchronous import, can hit this ceiling and return an error that looks like an application failure but is the platform refusing to hold the connection open indefinitely. The lesson is architectural rather than a setting to raise: long work belongs in a background job, a queue, or an asynchronous pattern where the request returns quickly with a handle and the work completes out of band, not in a synchronous request that races the front-end timeout. Designing around the timeout instead of fighting it is both more reliable and a better user experience, because the client is not left holding a connection for minutes either.

The worker sandbox and why it restricts you

The worker process does not run with the freedom of a virtual machine you own. It runs inside a sandbox that exists to make multi-tenancy safe and the platform manageable, and the sandbox imposes restrictions that catch developers porting a traditional Windows or Linux application. You cannot reliably spawn arbitrary long-lived child processes the way you would on a server you control. Access to parts of the Windows registry is restricted. Calls into graphics device interface APIs (the GDI and GDI+ family used by some image and document libraries) are blocked or heavily limited, which is the specific reason that an image-processing library or an old report generator that works on a developer’s machine throws an access-denied or unsupported-operation error the moment it runs in App Service. The local drive is writable only in specific locations. And outbound connections are subject to a hard limit on source network address translation ports per instance, the constraint behind the SNAT port exhaustion that high-fan-out applications eventually hit.

The reason to internalize the sandbox is not to memorize the list but to predict the class of failure. Anything that assumes full operating-system control, anything that wants to be a long-running service rather than a request handler, and anything that reaches into native platform internals is a candidate to fail in the sandbox. When it does, the answer is rarely a configuration toggle; it is a recognition that the workload wants a container or a virtual machine, not a sandboxed worker. That recognition is the whole value of understanding the sandbox before you deploy rather than after.

How do background jobs and continuous processes fit?

App Service supports background work through WebJobs and through the platform’s always-on setting, but a worker is still fundamentally a request-driven host. A continuous WebJob runs alongside the site, and a triggered WebJob runs on a schedule, but both share the workload’s sandbox and instances. For heavy or independently scaling background work, a separate compute service is usually the better home.

The honest framing is that App Service can do background processing, and for modest workloads it does so well, but the platform is optimized for handling HTTP requests. If your background job is the main event, if it needs to scale independently of your web tier, or if it runs long enough to fight the sandbox, you are better served by Azure Functions for event-driven work or by a container platform for long-running services. Bending App Service into a job runner is possible; making it the right tool for a job-heavy workload is usually a mistake the architecture review should catch.

Runtime Stacks: Windows, Linux, and Containers

A second axis that shapes behavior, alongside the plan tier, is the runtime your application runs on. App Service offers built-in stacks for the common languages and frameworks, runs those stacks on either a Windows or a Linux worker fleet, and additionally lets you bring your own container image when the built-in stacks do not fit. The axis matters because the operating system and the stack together decide which sandbox behaviors apply, how startup works, and how you pin a version so a platform-side runtime update does not silently change what your code runs on.

The Windows fleet hosts the in-process model for some stacks, where your application runs inside the platform’s web host, and the Linux fleet runs your site behind a reverse proxy in a more container-like arrangement. The distinction is not academic. The in-process startup model on Windows is the source of the specific 500.30 family of startup errors, where the host loads your application into its own process and a failure in that load surfaces as a precise error code rather than a generic crash. On Linux the workload runs in its own container, started from a built-in image for the chosen stack, which changes both the startup semantics and the way you read logs. When you choose a stack you are implicitly choosing one of these models, and the failure modes differ accordingly.

How do I pin a runtime version so the platform does not change it under me?

Set the runtime stack and its version explicitly on the application rather than relying on a default, because the platform periodically updates and retires minor and major runtime versions. Pinning a specific supported version means a platform-side update to a newer version does not silently move your application onto a runtime it was never tested against.

The reason this matters in practice is that an unpinned or loosely pinned stack inherits whatever the platform considers current, and platforms move forward. A minor runtime bump can change a default, deprecate an API, or alter timing in a way that turns a working site into a failing one with no deployment on your side to blame. The disciplined posture treats the runtime version as part of your configuration, sets it explicitly through the stack settings or your container tag, and tracks the platform’s deprecation notices so a forced migration is a planned change rather than a surprise incident. This is the same reasoning the broader platform rewards: pin what you depend on, and treat a forced change as a scheduled migration.

A custom container collapses the version question into the image you build, which is its main attraction. When you run your own image, the runtime, its version, the base operating system, and the installed dependencies are all decided by the image rather than by the platform’s built-in stack catalog. You gain exact control and you take on the responsibility that comes with it: patching the base image, keeping it secure, and making sure it starts within the platform’s container start timeout. The container still runs as the workload on the plan’s instances inside the managed envelope, so you keep slots, scaling, managed TLS, and the rest of the platform features; you have simply swapped the built-in runtime for one you own. The choice between a built-in stack and a custom container is therefore a control-versus-maintenance trade: the built-in stack carries less maintenance and less control, the container carries more of both.

What is the difference between the Windows and Linux fleets for the same application?

They run the same kinds of sites but with different startup, logging, and sandbox specifics. The Windows fleet supports the in-process host model for some stacks and a broader set of legacy integration points, while the Linux fleet runs each site in its own container with container-style startup and logs. The right choice usually follows the stack and whether you need a Windows-only dependency.

In day-to-day terms, a modern cross-platform application is usually a good fit for the Linux fleet, which tends to be the cheaper and more straightforward home for container-friendly stacks, while an application with a Windows-specific dependency or a legacy integration that expects Windows behavior belongs on the Windows fleet. The sandbox restrictions overlap heavily but are not identical, so a library that fails on one fleet may behave differently on the other, and reproducing a problem on the same fleet your production workload uses is part of diagnosing it honestly. Picking the fleet deliberately, by the stack and the dependencies rather than by habit, avoids a class of subtle differences that are hard to debug when you assumed the two fleets were interchangeable.

The Tiers, Limits, and Quotas That Shape Design

The tier ladder is where most App Service decisions are actually made, and most of them are made badly because people pick by name (Premium sounds like production) rather than by requirement. The tiers, from the bottom, are Free and Shared (multi-tenant, CPU-quota billed, no scale-out, no custom TLS in the way production needs, no slots), then the dedicated compute tiers Basic, Standard, Premium v3, and the newer Premium v4 generation, and finally the Isolated v2 tier that runs your workloads on dedicated virtual machines inside a dedicated virtual network through an App Service Environment. Each rung up the ladder unlocks a specific set of capabilities, and the capability you need is what should pick the tier, not the label.

The capabilities that move with the tier are the ones that bite. Scale-out instance count rises with the tier, so a Basic plan caps you at a low instance ceiling while Premium v3 scales to a much higher count and Isolated v2 higher still. Deployment slots appear at Standard and multiply at Premium and Isolated. Custom domains with managed TLS, autoscaling rather than manual scaling, virtual network integration, daily backups, and Traffic Manager integration each switch on at a specific rung. Microsoft’s own guidance is blunt about the economics at the top: because Premium v3 packs more memory and faster hardware per instance, it often serves a given load on fewer instances than a lower tier would need, which can make the higher per-instance price the cheaper total bill at scale. That is the opposite of the intuition that cheaper tiers save money, and it is worth verifying against your own load before you assume either way.

The InsightCrunch App Service plan decision table. Read it by requirement, not by tier name: find the capability your workload forces, and the table names the lowest tier that satisfies it. Buying above that line is paying for a badge; buying below it is the misconfiguration you will discover in production.

Requirement your workload has Lowest tier that satisfies it The deciding signal
Just experimenting, no production traffic Free / Shared Multi-tenant, CPU-quota billed, no scale-out, throwaway only
A dedicated instance, custom domain, low traffic, no scale-out needed Basic Dedicated compute but no autoscale and no deployment slots
Deployment slots, autoscale, daily backups, Traffic Manager Standard First tier with slots (a small fixed number) and autoscale
More slots, higher scale-out ceiling, faster hardware, VNet integration Premium v3 (or Premium v4 where available) The production workhorse for most real applications
Network isolation, dedicated VNet, compliance-grade tenancy, maximum scale-out Isolated v2 (App Service Environment v3) You need single-tenant isolation, and you accept the environment fee

Treat every count, ceiling, and price as a value to confirm against the current official limits at the time you read this, because Azure revises them and adds generations. The Isolated v1 and v2 environments that some older guides describe were retired, and the current isolated offering is Isolated v2 on App Service Environment v3; the newer Premium v4 generation sits above Premium v3 as the latest dedicated hardware. The durable facts are the shape of the ladder and the capability-per-tier mapping; the exact numbers are the part to verify. The same discipline applies to anything that touches the broader platform, which is why the Azure Resource Manager explained deep dive insists you reason about the control plane rather than memorizing a portal layout that moves.

Which App Service plan tier should I choose?

Choose the lowest tier that satisfies the hardest requirement your workload actually has. If you need deployment slots or autoscale, Standard is your floor. If you need virtual network integration or a high scale-out ceiling, Premium v3 is the floor. If you need single-tenant network isolation for compliance, Isolated v2 is the floor. Everything below that line will fail a requirement.

The common error is selecting Premium for a small SaaS launch because it carries the production aura, when the workload needs four gigabytes of memory and one deployment slot, both of which Standard provides at a fraction of the cost. The opposite error is launching production on Basic to save money and then discovering at the first traffic spike that Basic cannot autoscale and cannot hold a staging slot, so the deployment that should have been a slot swap becomes an in-place overwrite with downtime. Both errors come from picking by tier name instead of by requirement, and both are avoided by the table above.

How do scale-up and scale-out differ on App Service?

Scaling up changes the size of each instance, giving every worker more CPU and memory, and it is how you serve a more demanding application per instance. Scaling out changes the number of instances, adding workers behind the load balancer, and it is how you serve more concurrent requests. Scale-up is a tier-and-size change; scale-out is an instance-count change bounded by the tier’s ceiling.

The two levers solve different problems and people reach for the wrong one constantly. An application that is slow because each request is CPU-heavy or memory-hungry needs scaling up to a larger instance; adding more small instances will not make an individual request faster. An site that is fine per request but falling over under concurrency needs scaling out to more instances; making each instance bigger will not help if you are already bound by instance count. The most disciplined approach measures first and then chooses the lever the metric points to, and configures autoscale rules (available from Standard upward) so the instance count tracks demand automatically rather than depending on someone watching a dashboard. The full treatment of autoscale rules, the metrics worth scaling on, and the cool-down behavior that prevents flapping lives in the dedicated scaling Azure App Service guide.

What does App Service actually cost, and how do I cut it without losing capability?

The bill is the plan’s instance size multiplied by its instance count, charged hourly, plus any per-workload extras like certificates and outbound bandwidth beyond the included allowance. You cut it by right-sizing the instance, tuning autoscale so you are not paying for idle instances, consolidating low-traffic apps onto shared plans, and committing to reservations or a savings plan where the workload is steady.

The counterintuitive part of App Service cost is that a higher tier is sometimes the cheaper total. Because Premium v3 instances carry more memory and faster processors than lower tiers, a load that needs, say, six Basic instances might run on three Premium v3 instances, and three larger instances can cost less than six smaller ones while also leaving headroom. Microsoft’s own cost guidance makes this point directly: the highest non-isolated tier is frequently the most cost-effective way to serve a host at scale, and it becomes more so once you apply a reservation or a compute savings plan to the steady baseline. The mistake is to assume the cheapest-named tier is the cheapest bill; the honest method is to model the instance count each tier needs for your real load and compare totals, then commit the steady portion to a discount and let autoscale handle the variable portion on demand.

The other large lever is idle compute. In a scale-out deployment it is easy to leave instances running that the current traffic does not need, and every idle instance is wasted money in the dedicated tiers because you pay for reserved compute whether or not it is busy. Autoscale, available from the Standard tier upward, is the primary defense: rules that scale in during quiet periods and out during busy ones keep the instance count close to the demand curve rather than pinned at a peak that occurs for an hour a day. Scheduled scaling helps where the load is predictable, such as scaling down overnight for an internal application and back up before the workday. Stopping unused deployment slots, which keep running and billing if left active, is a smaller but real saving. The cost discipline, in short, is right-size the instance, commit the steady baseline to a discount, and let autoscale chase the rest, and it composes naturally with the broader cost reasoning the series applies across services.

The Configuration That Actually Matters

A correctly chosen tier is necessary but not sufficient; the configuration on top of it decides whether the site behaves. The settings that matter most are the ones that change runtime behavior in non-obvious ways: deployment slots and the swap, the always-on setting and the idle-unload it defeats, the run-from-package deployment model, and the networking choices. Get these wrong and the symptoms look mysterious; get them right and the workload does what you expect under load and during deploys.

How do App Service deployment slots and swaps work?

A deployment slot is a fully separate instance of your application within the same plan, with its own hostname and its own configuration, used to stage a new version before it goes live. A swap exchanges the content and the warmed-up state of two slots, typically staging and production, so the new version takes production traffic only after it is running and warm. Slots are available from the Standard tier upward.

The mechanics are where the value and the danger both live. When you swap staging into production, App Service does not simply flip a pointer; it applies the production application settings to the staging slot, warms up the staging instances by sending them startup requests, waits for them to respond healthily, and only then routes production traffic to them while the old production version moves into staging. Done right, this is a near-zero-downtime deployment with an instant rollback: if the new version misbehaves, you swap back and the previous version, still warm in the now-staging slot, takes traffic again. The danger is in the configuration. Some settings are slot-specific (they stick to the slot and do not travel during a swap) and some are not, and an engineer who assumes all settings travel will swap a staging version configured against the staging database straight into production pointed at the wrong data. Mark connection strings and environment-specific settings as slot settings so they stay put, configure swap-with-preview when you want to validate the warmed target before committing, and never treat a swap as a fire-and-forget button. The belief that a slot swap is instant and risk-free is one of the two misconceptions this service punishes most; the other is the belief that App Service autoscales without limit, which the tier ceiling quietly refutes.

Can I send a percentage of traffic to a slot for a canary release?

Yes. Beyond the all-or-nothing swap, App Service can route a configurable percentage of production traffic to a non-production slot, which gives you a canary or testing-in-production capability without external tooling. You set the percentage on the slot, and the platform splits live traffic between the production version and the slot version using the affinity cookie to keep a given client consistent.

This turns slots into a progressive-delivery mechanism rather than just a staging area. You deploy the new version to a slot, route a small slice of real traffic to it, watch the metrics and the error rate for that slice, and increase the percentage as confidence grows, then complete the rollout with a swap once the canary has proven itself. If the canary regresses, you set its traffic share back to zero and no swap ever happened, so production was never fully exposed. The subtlety to respect is the affinity cookie: because clients are pinned, a single user’s experience stays on one version rather than flipping between versions on each request, which is what you want for a coherent test but which also means your canary sample is by client rather than by request. Used deliberately, slot traffic routing gives you a measured, reversible rollout that most teams assume requires a separate deployment platform.

There is also an automatic-swap option, where a deployment to a designated slot triggers a swap into production once the slot is warm, which suits a continuous-deployment pipeline that wants the warmup-and-swap safety without a manual step. The trade is control for automation: auto-swap is convenient for a mature pipeline with strong pre-deployment gates, and risky for a pipeline that has not yet earned that trust, because it removes the human checkpoint that catches the swap-with-wrong-settings mistake. Match the automation to how much you trust the gates in front of it.

What does the always-on setting do, and why does my site go cold?

By default, an App Service workload is unloaded from memory after roughly twenty minutes with no incoming requests, so the next request after an idle period pays a cold-start penalty while the process restarts. The always-on setting keeps the host loaded by having the platform ping it periodically, which removes that idle-unload cold start. Always-on is available in the dedicated tiers.

This single setting explains a large fraction of the slow-first-request complaints engineers file. An internal application that nobody touches overnight is unloaded, and the first person in the morning waits several seconds for the process to spin up, the runtime to JIT, and the dependency graph to initialize. It is not a bug and it is not a platform fault; it is the idle-unload working as designed on a site that should have always-on enabled. Always-on is also a prerequisite for continuous WebJobs and for any timer or background trigger that needs the host alive between requests, because an unloaded host cannot fire a timer. Turn it on for any workload where a cold first request is unacceptable, and understand that it does not eliminate the cold start that happens when an instance is genuinely new (a fresh scale-out instance still has to start your application); it only eliminates the cold start caused by idle unloading. The startup failures that look like cold starts but are actually the runtime failing to load (the in-process 500.30 and the 500.31 and 500.32 load failures) are a different problem entirely, diagnosed in the App Service 500.30 startup error guide.

Deploying with run-from-package and the SCM site

How you deploy changes how the application runs. The traditional model copies files into the content share, which means deployment is a file-by-file operation that can leave the site in a half-updated state mid-copy and that competes with the workload for the content share. The run-from-package model, enabled with the WEBSITE_RUN_FROM_PACKAGE setting, mounts a single immutable zip package as the application’s content, which makes deployment atomic (the application switches to the new package as a unit), makes the running files read-only (which prevents a class of drift and tampering), and generally improves cold-start and deployment reliability. For most modern deployments, run-from-package is the better default, and it is what the platform’s own newer tooling tends to use.

# Create a plan and an app, then deploy a zip with run-from-package.
az appservice plan create \
  --name plan-prod \
  --resource-group rg-app \
  --sku P1V3 \
  --is-linux

az webapp create \
  --name my-prod-app \
  --resource-group rg-app \
  --plan plan-prod \
  --runtime "DOTNETCORE:8.0"

# Enable run-from-package so the deployed zip is mounted immutably.
az webapp config appsettings set \
  --name my-prod-app \
  --resource-group rg-app \
  --settings WEBSITE_RUN_FROM_PACKAGE=1

# Deploy the package; the app switches to it atomically.
az webapp deploy \
  --name my-prod-app \
  --resource-group rg-app \
  --src-path ./app.zip \
  --type zip

Every App Service site also has a companion administrative site, the SCM or Kudu site, reachable at the app’s hostname with .scm. inserted before the domain. This is where the platform exposes the deployment engine, the live log stream, the process explorer, the environment, and a console into the running worker. When an app misbehaves, the SCM site is the first place to look: the log stream shows you what the worker is emitting in real time, the process explorer shows you what is actually running and how much memory it holds, and the environment view shows you the settings the app actually received rather than the ones you think you set. Learning to live in the SCM site is the difference between guessing at a problem and reading it. A staging slot has its own SCM site, which is how you inspect a warmed-up version before you swap it into production.

How does VNet integration differ from a private endpoint on App Service?

Virtual network integration controls outbound traffic: it lets your app reach resources inside a virtual network, such as a database or a storage account locked behind private networking, by routing the app’s outbound calls through the VNet. A private endpoint controls inbound traffic: it gives your app a private IP inside a VNet so that clients reach the app privately rather than over its public hostname. They solve opposite directions and are frequently confused.

The confusion is costly because the two are not interchangeable and an app that needs both needs both configured. A team that wants their app to call a private SQL database enables VNet integration, the outbound path, and is then surprised that the app is still reachable from the public internet, because they configured outbound, not inbound. A team that wants the app reachable only from inside their network adds a private endpoint, the inbound path, and is then surprised the app cannot reach its private database, because they configured inbound, not outbound. State the direction you need before you reach for a feature: outbound to private resources is VNet integration, inbound from private clients is a private endpoint, and an app fronting private data with private clients needs both. VNet integration also interacts with SNAT port limits, because routing outbound traffic through the VNet changes how source ports are allocated, which is one of the levers for apps that exhaust SNAT under heavy outbound fan-out.

Application Settings, Configuration, and Identity

The way App Service injects configuration into your app is simple to use and easy to misunderstand, and the misunderstandings produce some of the most confusing incidents, because the symptom (the app reads the wrong value) looks like an application bug when it is really a platform configuration behavior. The platform exposes two related stores on every app: application settings and connection strings. Both are injected into the running app, application settings as environment variables and connection strings through both environment variables and a framework-specific mechanism on some stacks, and both override values baked into your deployed configuration files. That override is the key fact: a setting configured on the app wins over the same setting in your shipped appsettings.json or equivalent, which is exactly how you keep secrets and environment-specific values out of source control and in the platform instead.

The precedence is where people trip. Because the platform settings override the file settings, an engineer who changes a value in the deployed file and sees no effect is usually being overridden by a platform setting they forgot was there, and the SCM site’s environment view is the fastest way to confirm what the app actually received versus what the file says. The reverse trap is assuming a value is set on the app when it lives only in the file, so it works locally and in one environment and silently falls back to a different value in another. The reliable mental model is that the running configuration is the file as a base, with the platform’s application settings and connection strings layered on top, and the layered values win. Treat the platform settings as the authoritative environment-specific layer, keep them out of the deployed files, and verify the merged result in the environment view rather than reasoning about the file alone.

How should I handle secrets so they are not sitting in app settings?

Use a managed identity on the app and reference secrets in Azure Key Vault rather than pasting secret values into application settings. A Key Vault reference lets you put a pointer to a vault secret in the app setting, and the platform resolves it at runtime using the app’s identity, so the secret value never lives in the app configuration or in source control.

This is the durable pattern for credentials on App Service and it removes an entire class of exposure. You assign the app a managed identity, which is an Azure-managed credential the app uses to authenticate to other Azure services without any secret of its own, grant that identity access to the vault, and then write the app setting as a Key Vault reference instead of a literal value. At startup and on refresh the platform reads the actual secret from the vault on the app’s behalf, so rotating the secret in the vault changes what the app uses without a redeployment or a settings edit. The same managed identity extends past Key Vault: you can grant it the data-plane role it needs on a storage account, a database, or another service and authenticate with no connection string at all, which is the keyless access model the broader series argues for whenever a service supports it. The Azure Key Vault complete guide covers the reference syntax and the access model in full; on the App Service side the rule is to give the app an identity and reference secrets rather than store them.

Can I mount external storage into an App Service app?

Yes. You can mount an Azure Files share into the app’s filesystem at a path you choose, which gives the app durable shared storage that survives restarts and is visible to every instance, unlike the local worker disk. It suits content an app must read or write that does not belong in a database and must persist beyond the immutable deployment package.

The reason to reach for a mount is precisely the local-state problem the worker model creates: because any request can land on any instance and instances are disposable, writing durable data to the local disk is unsafe, and a mounted share gives every instance a consistent view of the same files. The trade-off is that a network-mounted share is not a fast local disk, so an app that does latency-sensitive, high-volume file IO against a mount will feel it in tail latency, and the better design for that pattern is usually a purpose-built data store rather than a file share. Use the mount for genuinely file-shaped, shared, durable content, size your expectations to network-attached storage rather than local SSD, and keep hot, latency-sensitive data in a service built for it. The decision mirrors the storage reasoning in the Azure Virtual Machines complete guide: match the storage to the access pattern rather than defaulting to whatever is closest.

Networking Beyond Integration: Access Restrictions and SNAT

The networking story does not end at virtual network integration and private endpoints. Two more controls decide real behavior in production: access restrictions that filter who can reach the app, and the source network address translation limit that governs how many outbound connections an instance can hold open. Both are frequently discovered the hard way.

Access restrictions are inbound IP and network rules applied at the app’s front door, letting you allow or deny traffic by IP range or by service tag and ordering the rules by priority, with a default action that catches anything the explicit rules do not. They are how you lock an app to a known set of callers without a full private endpoint, for example restricting an administrative app to the office network or fronting the app only with a specific gateway. The common misconfiguration is an ordering or default-action mistake that either locks out legitimate traffic or, worse, leaves the app open because a permissive default action was left in place under a set of allow rules that the operator assumed were also denying everything else. Reason about the rules as an ordered list with an explicit default, test the deny path as carefully as the allow path, and confirm that the default action is the restrictive one when restriction is the goal.

Why does my high-traffic app start failing outbound connections?

Almost always because it exhausted its SNAT ports. Each instance has a bounded pool of source ports for outbound connections to public endpoints, and an app that opens many short-lived outbound connections, especially without reusing them, drains the pool and then fails new outbound connections in a way that looks like the downstream service is down when the limit is local.

This is one of the most misdiagnosed App Service failures because the symptom points outward. The app cannot reach an external API, the errors mention connection timeouts or failures, and the instinct is to suspect the API or the network between you and it, when the actual cause is that your instance has no free source ports left to open another connection. The fixes are about connection discipline and architecture: reuse connections through a pooled, long-lived client rather than creating a new connection per call, which is the single highest-leverage change for most apps; route outbound traffic through virtual network integration, which changes how source ports are allocated and relieves the public-endpoint SNAT pressure; scale out so the connection load spreads across more instances, each with its own port pool; and reduce the per-request outbound fan-out where the design allows. Confirm the diagnosis by watching the connection metrics rather than guessing, because the difference between a SNAT ceiling and a genuine downstream outage is the difference between a fix you own and a vendor you wait on.

How do I reach an on-premises system from App Service without a full VPN?

Hybrid Connections give an App Service app an outbound, application-level relay to a specific host and port, on-premises or in another network, without a site-to-site VPN or virtual network peering. You install a small relay agent next to the target, point the connection at a host and port, and the app reaches that endpoint through the relay, scoped narrowly to exactly what you configured.

The appeal is the narrow blast radius and the low setup cost compared with network-level connectivity. Where virtual network integration gives the app a route into a whole network, a Hybrid Connection gives it a path to one host and one port, which is often exactly right for a single legacy database or service that lives in a data center you are not ready to network into Azure wholesale. The trade-offs are that it is a per-endpoint relay rather than general network access, so it does not scale to “reach everything in that network,” and it depends on the relay agent staying healthy. Reach for it when you need an app to talk to a specific on-premises endpoint quickly and with minimal networking change, and reach for virtual network integration or a proper site-to-site connection when the app needs broad access to a network rather than a line to one host.

Custom Domains, Certificates, and Backups

Putting an app into production means giving it a real name, securing it with a certificate, and being able to recover it, and App Service handles all three with behaviors worth knowing before launch day rather than during it. A custom domain is bound to the app by proving you control the domain, typically through a DNS record the platform asks you to create, after which the app answers on your name alongside its default hostname. The capability to bind custom domains and the certificates that secure them lives in the dedicated tiers, which is one more reason the Free and Shared tiers are experimentation-only: they are not built to front a production domain.

Certificates come in a few flavors and choosing among them is a small but real decision. The platform can issue and manage a free certificate for a custom domain it can validate, which is the least-effort option for standard cases and renews itself, removing the expiry incident that bites teams who forget a manual renewal. You can also bring your own certificate, uploading one you obtained elsewhere, which you then bind to the domain, and you take on tracking its renewal. The binding itself is usually name-based, using server name indication so many certificates share an address, which is the default and the right choice for nearly everyone; an address-based binding that consumes a dedicated address exists for the rare client that cannot do name-based negotiation. The practical guidance is to prefer the managed certificate where it fits so renewal is automatic, bring your own only when a requirement forces it, and treat any manually managed certificate’s expiry as a calendar item, because an expired certificate is a self-inflicted outage that monitoring should have caught.

Backups, available in the higher tiers, capture the app’s content and optionally a linked database on a schedule to a storage account, and restore brings them back. They are not a substitute for source control or for a reproducible infrastructure definition, and treating them as your only recovery story is a mistake, because the durable way to recreate an app is to redeploy it from code and a template rather than to restore a snapshot. Backups earn their place for the stateful pieces that do not live in source, the uploaded content and the configuration drift, and as a quick rollback for an environment, but the recovery design that actually survives a bad day is the one where the app and its infrastructure are defined as code and the data is protected by its own service’s backup, with App Service backups as a convenience layer rather than the foundation.

Deployment Options and a Production CI/CD Flow

App Service accepts code and containers through several paths, and the path you choose shapes how safe and repeatable your releases are. At the simple end, you can push a zip package or deploy straight from a local build, which is fine for a quick iteration and poor as a production process because it depends on a developer’s machine and leaves no audit trail. The deployment center can wire the app to a source repository so a push triggers a build and deploy, and for a real pipeline you connect a continuous-integration system that builds, tests, and then deploys to App Service through a service connection, which is the model that gives you gates, history, and repeatability.

What does a safe production deployment flow look like on App Service?

A safe flow builds an immutable artifact once, deploys it to a staging slot using run-from-package so the deployment is atomic, warms and verifies the slot, then swaps it into production with the previous version left warm for instant rollback. Environment-specific values live as slot settings and secrets resolve from Key Vault through the app’s managed identity, so the same artifact promotes across environments without rebaking.

The discipline that makes this reliable is separating the artifact from the environment. You build the application package or container image one time, tag it, and promote that exact artifact through environments by changing configuration rather than rebuilding, which eliminates the “works in staging, fails in production” class of problem caused by a fresh build behaving differently. Each promotion deploys into a slot, the slot warms under its production-equivalent settings, you validate it, and only then does a swap route live traffic to it. Because the prior version remains in the now-staging slot, a regression is a single swap back rather than a frantic redeploy. Wrapping the plan, app, settings, and slot configuration in an infrastructure-as-code template means a new environment is reproducible from the definition rather than from someone’s memory of which toggles they flipped, which is the same reproducibility argument the Azure Resource Manager deep dive makes for the platform as a whole.

# Create a staging slot, deploy the built artifact to it, then swap.
az webapp deployment slot create \
  --name my-prod-app \
  --resource-group rg-app \
  --slot staging

# Deploy the immutable package to staging (run-from-package already enabled).
az webapp deploy \
  --name my-prod-app \
  --resource-group rg-app \
  --slot staging \
  --src-path ./app.zip \
  --type zip

# Optional: route 10 percent of production traffic to staging as a canary.
az webapp traffic-routing set \
  --name my-prod-app \
  --resource-group rg-app \
  --distribution staging=10

# When the canary is healthy, swap staging into production with warmup.
az webapp deployment slot swap \
  --name my-prod-app \
  --resource-group rg-app \
  --slot staging \
  --target-slot production

The failures that interrupt this flow, the deployment that errors partway, the artifact that will not start, the container that misses its start timeout, are exactly the ones catalogued in the App Service deployment failure guide, and the reason a slot-based flow is safer is that those failures happen in staging where they cost nothing rather than in production where they cost an outage.

A Reference Production Setup

It helps to see the pieces assembled into one coherent design, because the individual settings make more sense as parts of a whole than as a list of toggles. Consider a public API that handles real traffic, must reach a private database, must be reachable only through a controlled front door, and must deploy without downtime. The plan is Premium v3, chosen because the workload needs virtual network integration and a meaningful scale-out ceiling, and because at this load its larger instances serve the traffic on fewer instances than a lower tier would, with the steady baseline committed to a reservation and the variable portion handled by autoscale rules that track request volume.

The app runs from an immutable package with run-from-package, carries a managed identity, and reads every secret as a Key Vault reference so no credential sits in its configuration. It reaches the private database through virtual network integration, the outbound path, and is fronted by a private endpoint or a global router for the inbound path depending on whether the clients are internal or internet-facing, with access restrictions narrowing who can reach it at the front door. A staging slot holds the next version, with connection strings and environment-specific values marked as slot settings so a swap never points the wrong version at the wrong data, and the release process deploys to staging, optionally canaries a slice of traffic, and swaps with the previous version left warm for rollback. Health check keeps the load balancer from routing to a sick instance, auto-heal recycles a worker on a characterized failure signature, and Application Insights provides the dependency tracing that explains a slow request. Always-on is enabled because a cold first request is unacceptable for an API, and the long-running work the API would otherwise do synchronously is pushed to a background path so nothing races the front-end request timeout.

None of these choices is exotic, and that is the point: a production-grade App Service deployment is the accumulation of a dozen deliberate decisions, each of which defends against one of the failure patterns this article named. The team that makes those decisions up front runs an app that behaves under load, deploys without drama, and recovers from a bad release in one swap. The team that skips them deploys to a tier that fails a requirement, swaps the wrong settings into production, exhausts SNAT under its own outbound fan-out, and debugs a cold-start that always-on would have prevented. The difference is not luck or scale; it is whether the plan, the slots, the identity, the networking, and the observability were treated as design or as defaults. The same architecture-as-deliberate-choice habit runs through the App Service vs AKS vs Container Apps comparison, which is where this reference design gets pressure-tested against the workloads App Service should hand off.

Observability, Diagnostics, and Self-Healing

An app you cannot see into is an app you debug by guesswork, and App Service gives you several windows that turn guesswork into reading. Knowing they exist and what each one shows is the difference between a five-minute diagnosis and an afternoon of redeploying hopefully.

The platform’s built-in diagnostics, often reached through the portal’s diagnose-and-solve experience, run automated detectors against your app and surface the common problems, availability dips, high CPU or memory, failed requests, and restarts, with the timeline that situates them. It is the right first stop for an app behaving badly, because it correlates platform signals you would otherwise have to assemble by hand and frequently names the cause directly. Alongside it, the live log stream on the SCM site shows what the worker is emitting right now, application logging can be turned up to capture more detail temporarily, and the metrics blade gives you the request counts, response times, memory working set, and instance count over time that tell you whether you are looking at a capacity problem, a code problem, or a platform event. Wiring the app to Application Insights adds the distributed-tracing and dependency view that shows where a slow request actually spends its time, which is what you need when the app is up but slow rather than down.

Can App Service restart or recover an unhealthy app automatically?

Yes, through two complementary features. Health check lets you designate a path the platform probes, and an instance that repeatedly fails the probe is taken out of the load-balancer rotation and can be replaced, so a single sick instance stops receiving traffic instead of serving errors. Auto-heal lets you define rules that trigger an action, such as recycling the worker, when conditions like a memory threshold, a request-duration threshold, or a count of specific status codes are met.

Together these turn known failure signatures into automatic mitigations rather than pages. Health check is the one to configure on any multi-instance app, because without it the load balancer keeps routing to an instance that has silently gone bad, and with it the platform routes around the bad instance and, in the tiers that support replacement, brings up a healthy one. Auto-heal is the scalpel for a recurring, understood problem: an app with a slow memory leak can be set to recycle a worker when it crosses a memory threshold, buying time and stability while the leak is fixed properly, and an app that occasionally wedges on a class of request can be set to recycle on a pattern of long requests. The discipline is to use auto-heal to mitigate a known, characterized failure rather than as a substitute for fixing the cause, because a rule that masks a worsening problem only delays the reckoning. Configure health check always, reach for auto-heal deliberately, and treat both as buying time for a real fix rather than as the fix.

The Failure Modes and How to Avoid Them

The recurring App Service failures cluster into a small set of patterns, and nearly every one of them traces back to a plan decision, a configuration setting, or a sandbox restriction that an engineer did not reason about in advance. Naming the patterns is how you design against them.

The first pattern is the tier-bound surprise: the app that cannot scale out far enough, cannot hold the staging slot the release process assumed, or cannot integrate with the VNet the security review now requires, all because the plan tier was chosen by name rather than by requirement. The fix is almost never on the app; it is a tier decision that should have been made earlier, which is exactly the plan-bound capability rule. The second pattern is the cold or misconfigured swap: a slot swapped into production that serves cold instances because warmup was not respected, or serves the wrong data because slot-specific settings were not marked as such. The third is the sandbox collision: the image library, the report generator, or the background daemon that works locally and fails in the worker because it wants operating-system access the sandbox does not grant. The fourth is SNAT port exhaustion under heavy outbound fan-out, where an app opening many short-lived outbound connections runs out of source ports and starts failing connections in a way that looks like a downstream outage but is really a local port ceiling. The fifth is the idle-unload cold start, the slow first request after a quiet period on an app that should have had always-on enabled.

The throughline is that these are predictable. Each one is a consequence of a property of the plan, the swap, the sandbox, or a setting, and each one is preventable by reasoning about that property before deployment. The specific error strings these patterns surface (the 503 Service Unavailable, the 500.30 in-process startup failure, the 500.31 and 500.32 runtime-load failures, the deployment failures, the SNAT exhaustion, and the container start timeouts) each have a dedicated diagnosis in the troubleshooting block of this series, including the App Service deployment failure guide, and the right move when you hit one is to read which cause is yours rather than trying fixes at random.

The diagnostic method that ties these together is consistent across the patterns and worth stating as a method. Start at the metrics blade to classify the problem: a memory working set climbing toward the instance ceiling points at a leak or an undersized instance, a request-duration spike with flat resource use points at a slow dependency rather than a capacity problem, and a drop in healthy instance count points at a platform or startup event. Then go to the live log stream and the diagnose-and-solve detectors to see what the worker is actually emitting and which automated detector has already fingered the cause, which frequently shortcuts the whole investigation. Only after you know which hop and which signal are involved do you change anything, because the most expensive App Service debugging sessions are the ones where an engineer changed the tier, the settings, and the code in parallel and then could not tell which change helped. Change one variable, confirm it against the metric that defines the problem, and move on. This is slower for the first ten minutes and far faster for the next two hours, and it is the same root-cause-over-symptom discipline the troubleshooting block of this series applies to every specific error code.

Is App Service ever the wrong host?

Yes. App Service is the wrong host when your workload wants control or shape the sandbox cannot give: long-running stateful services, heavy background processing that should scale independently of the web tier, workloads that need native operating-system access, and architectures that want fine-grained control of the orchestration and networking. For those, a container platform or virtual machines fit better.

The decision is not App Service versus everything; it is App Service versus the next two or three reasonable hosts for a given workload. For an HTTP app or API that fits the sandbox and wants the least operational overhead, App Service is usually the right answer and the burden of proof is on moving off it. For a microservices system with complex orchestration, AKS earns its complexity. For event-driven or bursty workloads, Container Apps or Functions fit the shape better. The full branch-by-branch decision, with the deciding factor named at each fork, is the subject of the App Service vs AKS vs Container Apps comparison, and the honest summary is that App Service wins on simplicity for the common web workload and loses on control for the uncommon ones.

Designing for Availability Across Regions

A plan and its instances live in one region, so a single plan is a single regional dependency no matter how many instances you scale it to. Scaling out within the region protects you from losing an instance; it does not protect you from a regional problem, because every instance is in the same region. Engineers routinely conflate the two and assume that a healthy instance count means high availability, when instance redundancy and regional redundancy are different protections against different failures, and only one of them is solved by the autoscale slider.

The first level of resilience is keeping the instances spread within the region and letting the platform place them across the underlying fault domains, which the dedicated tiers do for a multi-instance plan, so the loss of a single piece of regional infrastructure does not take all your instances at once. This is necessary and it is not sufficient for an application that must survive a regional event. For that you run the app in more than one region, each with its own plan, and put a global router in front, either a content-and-routing front door that also gives you caching and a web application firewall, or a DNS-based traffic manager that directs clients to a healthy region. The router watches the health of each regional deployment and steers traffic away from a region that fails, which turns a regional outage into a degraded-but-up experience rather than a full outage.

Do I need active-active, or is active-passive enough?

It depends on your recovery-time tolerance and your data tier. Active-passive keeps a warm secondary region that takes over when the primary fails, which is simpler and cheaper and accepts a short failover window. Active-active runs both regions serving traffic at once, which removes the failover window and adds the cost and the complexity of keeping the data tier consistent across regions.

The deciding factor is almost always the data, not the app tier. The web tier is comparatively easy to run in two regions, because App Service plans are quick to stand up and a stateless app deploys identically to each, so the hard problem is making the data available and consistent in both places. If your data store can replicate across regions with the consistency and latency your app needs, active-active becomes feasible; if it cannot, or if the consistency cost is too high, active-passive with a clear failover procedure is the honest choice. Size the design to the recovery-time and recovery-point objectives the business actually has rather than to an aspiration, because a two-region active-active web tier in front of a single-region database is a false sense of security that fails exactly when the database’s region does. Reason about availability end to end, from the global router through the app tier to the data, and let the weakest link set your real guarantee.

When to Use App Service and When to Reach Past It

The clean way to hold the decision is to start from the workload’s shape and its non-negotiable requirements. A standard web app or REST API, fitting inside the sandbox, wanting managed TLS and custom domains and a near-zero-downtime deploy story, with a scale profile a dedicated tier can hold, is the canonical App Service workload, and choosing anything more complex for it is buying operational burden you do not need. The moment a hard requirement falls outside what App Service grants (native OS control, independent background scaling, single-tenant isolation below the Isolated price point, or orchestration complexity App Service does not model), that requirement is the signal to evaluate the next host rather than to fight the platform.

The plan-bound capability rule is the compression of everything above: nearly every App Service limit a developer hits (slots, scale-out ceiling, VNet integration, always-on, autoscale) is a property of the plan tier rather than the app, so the fix is almost always a tier decision that should have been made earlier. Internalize that and you stop debugging the app for problems the plan caused. You read a requirement, you find the lowest tier on the decision table that satisfies it, and you buy that tier deliberately. You mark slot-specific settings as slot settings so swaps stay safe. You enable always-on where a cold first request is unacceptable. You name the direction (outbound or inbound) before you reach for VNet integration or a private endpoint. And you recognize the sandbox collision before you port a workload that wants an operating system.

The strategic verdict is that App Service rewards the engineer who treats the plan as a design decision and punishes the one who treats it as a dropdown. It is the right default for the common web workload precisely because it removes so much operational work, and it becomes a liability only when a team chooses it for a workload it was never shaped to host, or chooses a tier that fails a requirement they did not think to check. Reason about the plan, the swap, the sandbox, and the networking direction up front, and App Service does what the product page promises. Skip that reasoning and you will meet every one of the failure patterns above, one production incident at a time.

When you want to put this into practice, run the hands-on Azure labs and command library on VaultBook, where you can deploy an app, create a staging slot, run a swap with preview, and watch the warmup and rollback behavior on a real plan rather than reading about it. Reproducing the slot swap once teaches more than any description of it, and the command library carries the tested az and Bicep snippets for provisioning each tier.

Frequently Asked Questions

Q: What is Azure App Service and how does its worker model work?

Azure App Service is a managed platform-as-a-service for hosting web apps, APIs, and background-capable web jobs without managing servers. Your code or container runs inside a sandboxed worker process on a virtual machine instance defined by an App Service plan, and the platform handles the operating system, patching, load balancing, and TLS termination. The plan owns the compute and the billing: you pay for the plan’s reserved instances in the dedicated tiers regardless of traffic, and multiple apps in one plan share those instances. The mental model that prevents most mistakes is that the plan is a fleet of managed virtual machines and the app is what runs on the fleet, so compute is dedicated at the plan level and not per app.

Q: Which App Service plan tier should I choose?

Choose the lowest tier that satisfies the hardest requirement your workload genuinely has, not the tier whose name sounds most production-ready. If you only need a dedicated instance and a custom domain with no scale-out, Basic suffices. If you need deployment slots, autoscale, or daily backups, Standard is the floor. If you need virtual network integration or a high scale-out ceiling, Premium v3 is the floor, and it is often the most cost-effective at scale because its larger instances serve a given load on fewer of them. If you need single-tenant network isolation for compliance, Isolated v2 is the floor, with an environment fee to match. Picking by requirement rather than by label is the entire discipline; every tier below the line your requirement draws will fail that requirement in production.

Q: How do App Service deployment slots and swaps work?

A deployment slot is a separate running instance of your app inside the same plan, with its own hostname and configuration, used to stage a version before it goes live. Slots are available from the Standard tier upward. A swap exchanges staging and production: the platform applies production settings to the staging slot, warms up its instances with startup requests, waits for healthy responses, then routes production traffic to the warmed version while the previous version moves into staging, which gives you a near-zero-downtime deploy and an instant rollback by swapping back. The critical detail is that some settings are slot-specific and stay with the slot during a swap while others travel, so mark environment-specific connection strings and settings as slot settings, or you will swap a staging build pointed at staging data straight into production.

Q: What can the App Service sandbox not do?

The worker sandbox blocks the things that assume full operating-system control, because it exists to make multi-tenancy safe. You cannot reliably run arbitrary long-lived child processes, access to parts of the Windows registry is restricted, and calls into graphics device interface APIs are blocked or limited, which is why some image-processing and document-generation libraries that work locally fail in App Service. The local disk is writable only in specific locations, durable writes belong in external storage, and outbound connections are bounded by a per-instance SNAT port limit. The useful takeaway is to predict the class of failure rather than memorize the list: anything wanting native OS access, anything that wants to be a standalone daemon rather than a request handler, and anything reaching into platform internals is a candidate to fail, and the right response is usually a container or a VM, not a toggle.

Q: When should I pick App Service over a VM or containers?

Pick App Service when your workload is a standard web app or API that fits the sandbox and you want the least operational overhead: managed TLS, custom domains, deployment slots, and autoscale without running servers. The burden of proof is on moving off it for the common web workload. Reach for containers on a platform like Container Apps or AKS when you need orchestration control, independent scaling of services, or a runtime the sandbox cannot host, and reach for a virtual machine when you need full operating-system control, custom system software, or a long-running stateful service. The deciding factor is control versus simplicity: App Service trades control for simplicity, which is the right trade for most web apps and the wrong trade for workloads that genuinely need the control.

Q: How does VNet integration differ from a private endpoint on App Service?

They solve opposite directions. Virtual network integration is the outbound path: it lets your app reach resources inside a virtual network, such as a private database or storage account, by routing the app’s outbound traffic through the VNet. A private endpoint is the inbound path: it gives your app a private IP inside a VNet so clients reach the app privately instead of over its public hostname. Because they are not interchangeable, an app that needs to both call private resources and be reached privately needs both configured. The frequent mistake is enabling VNet integration and expecting the app to become private to inbound clients, or adding a private endpoint and expecting the app to reach private resources. State the direction you need first, then choose the feature that serves it.

Q: Why does my App Service app feel slow on the first request after it has been idle?

Because by default an app is unloaded from memory after roughly twenty minutes without requests, so the next request pays a cold start while the process restarts, the runtime initializes, and dependencies load. This is the idle-unload behavior working as designed, not a fault. Enabling the always-on setting, available in the dedicated tiers, keeps the app loaded by having the platform ping it periodically, which removes the idle cold start and is also required for continuous WebJobs and timer-based background work to keep firing. Always-on does not remove the cold start that happens when a brand-new scale-out instance starts your app for the first time, since that instance genuinely has to load; it only removes the cold start caused by idle unloading on an existing instance.

Q: Can I run multiple apps in one App Service plan, and should I?

Yes, you can place many apps in a single plan, and they share the plan’s reserved compute. It is an effective cost lever for several low-traffic apps, because they share the idle capacity you are already paying for rather than each carrying its own plan. It becomes a footgun when you co-locate apps with very different load profiles, because they compete for the same CPU and memory, and a memory leak or a traffic spike in one can degrade the others. The rule is that compute is dedicated at the plan level, not the app level, so when you need isolation between apps, give the demanding or sensitive ones their own plans and group only the genuinely low-traffic, low-risk apps together.

Q: What is the difference between scaling up and scaling out on App Service?

Scaling up changes the size of each instance, giving every worker more CPU and memory, and it makes a demanding individual request faster or feasible. Scaling out changes the number of instances behind the load balancer, and it serves more concurrent requests. They solve different problems: an app slow because each request is heavy needs to scale up, while an app fine per request but failing under concurrency needs to scale out, and the instance-count ceiling for scale-out rises with the tier. Measure which one your metrics point to before choosing, and configure autoscale rules from the Standard tier upward so the instance count tracks demand automatically instead of relying on someone to react to a dashboard during a spike.

Q: Does stopping an App Service app stop the billing?

In the dedicated compute tiers, stopping an app does not stop the plan’s compute charge, because you pay for the plan’s reserved instances whether or not an app is running on them. The instances are provisioned and billed by the hour regardless of how many apps use them. To actually stop paying, you scale the plan down to a cheaper tier, scale its instance count down, delete the plan, or move the app to a tier with a different billing model. In the Shared tier, apps are billed by CPU quota, so an idle app accrues little, but the dedicated tiers bill for the reserved compute, which is why an empty Standard or Premium plan with no apps still costs money.

Q: What is the SCM or Kudu site and when do I use it?

Every App Service app has a companion administrative site reachable by inserting .scm. into the app’s hostname, commonly called the Kudu or SCM site. It exposes the deployment engine, a live log stream, a process explorer, the environment the app actually received, and a console into the running worker. You use it whenever an app misbehaves: the log stream shows what the worker emits in real time, the process explorer shows what is running and how much memory it holds, and the environment view confirms whether the settings the app received match what you intended to set. Each deployment slot has its own SCM site, which is how you inspect a warmed staging version before swapping it into production rather than discovering its problems after the swap.

Q: What does WEBSITE_RUN_FROM_PACKAGE do?

It switches the app from the traditional file-copy deployment model to mounting a single immutable zip package as the app’s content. The benefits are that deployment becomes atomic, since the app switches to the new package as a unit instead of being copied file by file and risking a half-updated state, and that the running files become read-only, which prevents drift and a class of tampering and often improves cold-start and deployment reliability. For most modern deployments it is the better default, and newer tooling tends to use it automatically. You set it as an app setting and then deploy a zip package, after which the app runs directly from that package rather than from a mutable copy on the content share.

Q: How does App Service handle TLS and custom domains across tiers?

The platform terminates TLS for you at its front ends, and the ability to bind a custom domain with a certificate moves up the tier ladder. The Free and Shared tiers serve apps on the platform’s default hostname and are not meant for production custom-domain hosting, while the dedicated tiers support custom domains with managed certificates and the certificate features production needs. Because the exact certificate options and any associated charges change over time, confirm the current capability for your target tier against the official details, but the durable shape is that custom domains and the production TLS story belong to the dedicated tiers and not to the experimentation tiers, which is one more reason Free and Shared are development-only.

Q: Why do my background WebJobs stop running when traffic is quiet?

Because a continuous WebJob runs inside the app’s host, and when the app is unloaded after an idle period, the host that the WebJob depends on is gone, so the job stops firing. The fix is the always-on setting, which keeps the host loaded so continuous and timer-triggered jobs keep running between requests. This is the same idle-unload mechanism behind the slow first request, surfacing in a different symptom. If the background work is substantial, scales on its own schedule, or runs long enough to fight the sandbox, the better architecture moves it out of App Service entirely to an event-driven or container-based host rather than keeping a web app warm purely to host a job.

Q: What is an App Service Environment and when do I need one?

An App Service Environment is the single-tenant deployment of App Service that runs your apps on dedicated virtual machines inside your own virtual network, which is what the Isolated v2 tier uses. You need it when you require network isolation and compute isolation beyond what the shared multi-tenant platform provides, typically for compliance reasons or for workloads that must run with no public dependency and full control of inbound and outbound networking, and it also offers the highest scale-out ceiling. It carries an environment fee in addition to the per-worker compute, so it is the right answer specifically when single-tenant isolation is a hard requirement and the wrong answer when ordinary VNet integration on a Premium plan would meet your networking needs at a fraction of the cost.

Q: How does the App Service plan relate to the region and to availability?

A plan is created in a specific region, and all of its instances and the apps on it run in that region, so the plan ties your app to a region’s capacity and to that region’s availability story. A single plan in a single region is a single regional dependency, and surviving a regional problem means running the app in more than one region behind a global router such as Front Door or Traffic Manager, with the data tier designed to match. The plan also bounds your scale: instances scale within the region up to the tier’s ceiling. Reasoning about availability therefore starts with the plan’s region and its instance spread, the same way reasoning about a virtual machine’s availability starts with its zone placement.

Q: Can App Service run containers, and how does that change the model?

Yes, App Service can run a custom container image as the app, which lets you bring a runtime or a dependency set the built-in stacks do not offer while keeping the managed plan, scaling, slots, and TLS. The plan and worker model are unchanged: the container runs as the app on the plan’s instances inside the same managed envelope. What changes is that you own the image and its contents, which both frees you from the built-in runtime versions and makes you responsible for the image’s base, its patching, and its start behavior, including the container start timeout the platform enforces. It is the middle ground between the fully managed built-in stacks and a full container orchestrator, suitable when you want a custom runtime without taking on orchestration.

Q: How do I make an App Service deployment safe and repeatable?

Treat the plan and app as infrastructure as code, deploy through slots, and verify before you commit. Define the plan, the app, and the settings in a template so the environment is reproducible rather than hand-clicked, mark environment-specific settings as slot settings so swaps stay safe, and deploy new versions into a staging slot with run-from-package so the deployment is atomic and the files are immutable. Use swap-with-preview to validate the warmed target against production settings before routing live traffic, and keep the previous version warm in the staging slot after the swap so a rollback is a single swap back rather than a redeploy. The combination of templated provisioning, slot-based deploys, and preview validation turns a deployment from a risky overwrite into a reversible, observable operation.

Q: How do I move an app to a different plan or change its tier?

You can move an app to another plan in the same region and resource scope, and you can change a plan’s tier in place, and the two operations serve different needs. Moving an app between plans is how you separate a noisy or sensitive app onto its own dedicated compute, or consolidate quiet apps together, since the app simply re-associates with the target plan and runs on its instances. Changing the tier of a plan, scaling up or down, changes the size and capability of every app on that plan at once, so a tier change is a plan-wide decision rather than a per-app one. Because the plan is pinned to a region, neither operation moves an app across regions; relocating to another region means deploying the app to a new plan there and shifting traffic. Plan the move around these boundaries: same-region re-association for isolation, in-place tier change for capability, redeploy for a region change.

Q: How do runtime stacks and versions work, and why should I pin them?

App Service offers built-in runtime stacks for the common languages and frameworks, running on either a Windows or a Linux worker fleet, and you select the stack and a specific version when you configure the app. You should pin the version explicitly rather than ride the default, because the platform periodically updates and retires runtime versions, and an unpinned app can be moved onto a newer runtime it was never tested against, turning a platform-side update into a failure with no deployment of yours to blame. Pinning makes the runtime part of your configuration and turns a forced upgrade into a planned migration you schedule and test. When the built-in stacks do not offer the runtime or version you need, a custom container moves the entire version decision into your image, giving you exact control in exchange for owning the base image’s patching and start behavior. Either way, treat the runtime as a dependency you manage deliberately rather than one the platform manages for you.