What this usually means
Cold start is the delay between the Functions host starting (loading assemblies, binding the trigger, initializing DI) and the actual function invocation. On the Consumption plan, idle instances are deallocated after ~20 minutes. When a new request arrives, Azure spins up a fresh sandbox, loads your function app's code, and runs your startup logic. The more dependencies, the heavier the assembly load; the more DI registrations, the longer the warm-up. Platform-level delays (e.g., storage account binding resolution) can also add seconds. For Premium and Dedicated plans, cold starts are rarer but still happen on scaling events or after deployments.
The first ten minutes — establish facts before touching code.
- 1Check the Function App's 'Function Runtime Scaling' metric in Azure Monitor; look for spikes in 'Function Execution Count' that coincide with long durations.
- 2Enable Application Insights (if not already) and run a query: requests | where timestamp > ago(1h) | project duration, operation_Id, customDimensions['ProcessId'] | order by duration desc | take 10.
- 3In Application Insights, open an end-to-end transaction of a slow request; check the 'dependencies' tab for storage account delays (table/queue init).
- 4Look at the 'File System' and 'Assemblies' performance counters via KQL: performanceCounters | where name == 'Total Assembly Load Time' | render timechart.
- 5Simulate cold start by calling the function after 30 minutes of inactivity; use a simple HTTP trigger and measure response time with curl -w '%{time_total}'.
- 6Check the Azure Functions host logs (under 'Logs' blade) for the duration of 'Loading functions' and 'Host lock acquired' messages.
The specific files, logs, configs, and dashboards that usually own this bug.
- searchApplication Insights -> Performance -> 'Operations' -> select the function -> view 'Dependencies' for storage/DB call durations
- searchAzure Monitor -> Metrics -> 'Function Execution Count' and 'Function Execution Duration' for that function app
- searchKusto query: requests | where timestamp > ago(6h) | summarize avg(duration) by bin(timestamp, 5m), cloud_RoleInstance
- searchFunction App -> 'Diagnose and solve problems' -> 'Performance' -> 'Function App Slow'
- searchLocal.settings.json (DEV) or App Settings (PROD): setting FUNCTIONS_WORKER_RUNTIME and WEBSITE_RUN_FROM_PACKAGE=1
- searchHost.json: check for 'functionTimeout' and 'extensions.http.maxConcurrentRequests'
- searchLinux consumption plan? Check WEBSITE_CONTENTAZUREFILECONNECTIONSTRING and WEBSITE_CONTENTSHARE
Practical causes, not theory. These are the things you will actually find.
- warningConsumption plan without Always On — instances are recycled after ~20 minutes of inactivity
- warningHeavy assembly loading: referencing large NuGet packages, excessive static constructors or module initializers
- warningSlow DI registration: many transient services, or services that perform heavy I/O during construction
- warningLarge 'WEBSITE_RUN_FROM_PACKAGE' zip (over 200 MB) causing slow extraction on cold start
- warningBinding startup delay: checking blob/queue existence via storage account API on every cold start
- warningPremium/Elastic Premium plan with minimum instance count = 0 (no pre-warmed instances)
- warningLinux consumption plan with custom Linux container that has a large base image
- warningUsing Azure Functions v1 (Windows-only, slower cold starts) instead of v3/v4
Concrete fix directions. Pick the one that matches your root cause.
- buildSwitch to Premium plan with a pre-warmed instance count of at least 1; this eliminates cold starts entirely.
- buildEnable 'Always On' on the App Service Plan (only applies to Dedicated plans, not Consumption).
- buildReduce assembly load: trim unused NuGet packages, use lazy initialization for expensive singletons.
- buildUse Azure Functions Proxies to warm up the function via a scheduled ping (e.g., Azure Scheduler every 5 min).
- buildOptimize DI: register services as Singletons where possible, avoid I/O in constructors, use async lazy patterns.
- buildDeploy via 'WEBSITE_RUN_FROM_PACKAGE=1' and keep the package under 100 MB; remove large static assets.
- buildUse 'Azure Functions Warmup' package to trigger initialization before first user request.
- buildMove heavy initialization to a background task after the function starts (Task.Run) instead of blocking startup.
A fix you cannot prove is a guess. Close the loop.
- verifiedAfter fix, call the function after 30+ minutes idle and measure response time; it should be under 2 seconds.
- verifiedRun a KQL query: requests | where timestamp > ago(24h) | summarize p95(duration) by bin(timestamp, 1h). The p95 should drop to near p50.
- verifiedCheck 'Function Execution Duration' metric in Azure Monitor for the function; the max should no longer spike above normal.
- verifiedMonitor 'Assemblies Loaded' perf counter; ensure no new assemblies are loading on cold start after the fix.
- verifiedUse Application Insights 'Availability' tests to probe the function from multiple regions; report no timeouts.
- verifiedFor Premium plan: verify 'Always Ready Instances' count stays at configured number via 'Premium Functions' blade.
Things that make this bug worse or harder to find.
- warningDo not wrap the entire function in a try-catch that swallows cold-start exceptions; they indicate real issues.
- warningAvoid setting 'functionTimeout' too low (e.g., 5 seconds) — cold starts often exceed that and cause timeouts.
- warningDon't add long-running synchronous operations in the static constructor of the startup class.
- warningAvoid using 'Azure Functions Core Tools' local warmup as a substitute for production testing — local runtime is different.
- warningDo not blindly add 'Always On' on Consumption plan — it's not supported and will be ignored.
- warningDon't assume cold start is only a Consumption plan problem; check Premium plan's 'Minimum Instances' setting.
The 15-Second First Request That Cost a Demo
Timeline
- 09:00Sales demo scheduled for 10 AM. Function app deployed last night.
- 09:45QA reports first request to the API takes 15 seconds, subsequent calls <500ms.
- 09:50I check Application Insights: p95 duration for GET /orders is 14.8s, p50 is 320ms.
- 09:55Open end-to-end transaction of the slow request: 'dependencies' shows 12s gap before any outbound calls.
- 09:58Check host logs: 'Loading functions: 8.5s', 'Host lock acquired: 1.2s', 'Function loaded: 4.1s'.
- 10:00Emergency fix: deploy a warmup endpoint and schedule a ping every 5 min from Azure Scheduler.
- 10:02Demo proceeds with first request taking 2.1s — acceptable. Root cause identified post-demo.
- 10:30Post-mortem: Function app referenced a 50 MB NuGet package (Azure.Storage.Blobs) plus Entity Framework Core with 20+ assemblies.
- 11:00Fix: removed unused packages, moved EF Core initialization to Lazy<T>, enabled 'WEBSITE_RUN_FROM_PACKAGE=1'.
The day started with a demo at 10 AM. I'd deployed a new Azure Function app the night before. QA called at 9:45 saying the first API call took 15 seconds — a disaster for the demo. I pulled up Application Insights and saw the p95 duration was 14.8s, but p50 was 320ms. Classic cold start pattern. The end-to-end transaction showed a 12-second gap before any dependency call — that's the Functions host starting up.
I dug into the host logs. 'Loading functions' alone took 8.5 seconds. 'Function loaded' added another 4.1 seconds. That's the assembly JIT compilation and DI initialization. The function used Entity Framework Core, Azure.Storage.Blobs, and several other packages. The startup class registered 30+ services, many as transient. I also noticed the package was deployed as a 50 MB zip but without 'WEBSITE_RUN_FROM_PACKAGE', so the runtime extracted it on every cold start.
With the demo in 15 minutes, I couldn't fix the root cause. I quickly added a warmup endpoint and configured Azure Scheduler to call it every 5 minutes. That kept the instance alive. After the demo, I removed unused NuGet packages (dropped from 50 MB to 12 MB), switched EF Core initialization to Lazy<DbContext>, and enabled 'WEBSITE_RUN_FROM_PACKAGE'. Cold start dropped to under 2 seconds. Lesson: always measure assembly load time and trim dependencies aggressively.
Root cause
Excessive assembly loading due to large NuGet packages and heavy DI initialization, combined with no warmup mechanism on the Consumption plan.
The fix
Removed unused packages, deferred EF Core initialization with Lazy<T>, enabled 'WEBSITE_RUN_FROM_PACKAGE', and added a scheduled warmup ping.
The lesson
Cold start latency is not just a platform problem — it's a code problem. Profile assembly load time and DI initialization before blaming Azure. And always have a warmup strategy for production demos.
The first step is to quantify the cold start delay. Use Application Insights' 'Performance' blade and select your function. Look at the 'Duration' distribution — a second peak at 5-20 seconds while the main peak is under 1 second indicates cold starts. For precise measurement, run this Kusto query: requests | where timestamp > ago(24h) | summarize p95(duration) by bin(timestamp, 1h). A drop in p95 after activity confirms the pattern.
To isolate the cold start phase, examine the 'dependencies' table. A long gap (multiple seconds) before any dependency call is the host startup time. You can also check the 'trace' table for 'Host started' and 'Function processed' events. The difference is the cold start penalty. For deeper analysis, enable 'Sampling' in Application Insights to capture full traces without cost explosion.
The Functions host uses dependency injection. Every transient service registered in 'ConfigureServices' is constructed on each request, but even singleton services are built during startup if they are injected into the function constructor. To minimize this, register heavy services as 'Lazy<T>' or use 'AddSingleton' with a factory that does lazy initialization.
A pattern that works: create a 'LazyInitializer' class that wraps expensive objects. For example: services.AddSingleton<ExpensiveService>(sp => new Lazy<ExpensiveService>(() => new ExpensiveService()).Value). However, be careful: the lazy initialization still happens on first access, which might be during a request. Better: use a background warmup that triggers lazy init before user requests arrive.
On the Consumption plan, the function app idles out after about 20 minutes. There's no 'Always On' setting. The only way to keep it warm is a scheduled ping. On the Premium plan, you can set 'Always Ready Instances' to keep one or more instances warm. The Dedicated plan (App Service) has 'Always On' but costs more.
Key App Settings: 'WEBSITE_RUN_FROM_PACKAGE' should be set to '1' — this runs the function from a remote zip, reducing cold start I/O. 'FUNCTIONS_WORKER_RUNTIME' should match your runtime. For Linux Consumption, ensure 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING' and 'WEBSITE_CONTENTSHARE' are set correctly; misconfiguration adds startup delays.
The simplest warmup: create an HTTP trigger '/warmup' that does nothing but returns 200. Then schedule a ping from Azure Logic Apps, Azure Scheduler, or even StatusCake every 5 minutes. For Premium, use the 'Always Ready Instances' feature — specify a minimum of 1 instance. This eliminates cold starts entirely.
More advanced: use 'Azure Functions Warmup' NuGet package (Microsoft.Azure.WebJobs.Extensions.Warmup). This triggers a special warmup action before the first real request. Or, use a TimerTrigger function that runs every 5 minutes and calls the expensive initialization. Be aware that warmup calls count toward your execution quota.
Frequently asked questions
What is the difference between cold start on Consumption vs Premium plan?
On Consumption, the app is deallocated after ~20 minutes of inactivity. A new request triggers a full sandbox creation, which includes loading the runtime, your assemblies, and executing startup code. On Premium, you can configure 'Always Ready Instances' (pre-warmed instances) that reduce cold start to near zero. However, Premium still experiences cold starts during scaling events if the minimum instance count is set to 0.
How do I measure cold start time accurately?
Use Application Insights. The 'duration' metric on the request includes cold start time. To isolate it, query the 'trace' table for 'Host started' and 'Function started' events. The difference is the cold start overhead. You can also use a simple HTTP test: call the function after 30+ minutes idle and record the total time with curl -w '%{time_total}'.
Can I eliminate cold starts entirely?
On Premium or Dedicated plans with 'Always On' or 'Always Ready Instances', yes. On Consumption, you cannot eliminate them but can reduce their frequency and duration. Frequent warmups (every 5 minutes) keep the instance alive. Optimizing code reduces the duration. There is no 100% elimination on Consumption without a warmup.
Does language choice affect cold start?
Yes. .NET and Java have heavier cold starts due to assembly loading and JIT compilation. Node.js and Python are generally faster because they are interpreted. However, dependency initialization still matters. For critical paths, consider using Node.js or PowerShell if cold start is a major concern, but always profile your specific case.
Why does my function cold start take 20 seconds even after optimizing code?
Check if you're on Linux Consumption plan without 'WEBSITE_RUN_FROM_PACKAGE'. The file system on Linux is slower. Also verify that your storage account is in the same region as the function app — cross-region storage access adds latency. Another cause: a large number of bindings (e.g., 10+ input bindings) each require initialization. Reduce bindings or make them conditional.