Introduction to Troubleshooting App Service
Azure App Service is a fully managed platform for building, deploying, and scaling web applications. While it abstracts away much of the infrastructure complexity, developers still encounter issues related to deployment, performance, configuration, and connectivity. This tutorial walks through the most common App Service problems and provides practical, actionable solutions you can apply immediately.
Why Troubleshooting App Service Matters
When your application runs on App Service, downtime or degraded performance directly impacts your users and your business. Understanding how to diagnose and resolve issues quickly reduces mean time to recovery (MTTR), improves reliability, and helps you make informed architectural decisions. Mastering troubleshooting also helps you avoid recurring problems by addressing root causes rather than symptoms.
Common Issue 1: Application Startup Failures
One of the most frequent problems developers face is an application that fails to start after deployment. This often manifests as HTTP 502 Bad Gateway or HTTP 503 Service Unavailable errors. The root cause is typically a missing dependency, incorrect configuration, or an unhandled exception during startup.
Diagnosing Startup Failures
The first step is to check the application logs. You can enable application logging through the Azure Portal, the Azure CLI, or directly in your application code. The following Azure CLI command enables file system logging for an App Service:
az webapp log config --name my-app-service --resource-group my-resource-group --application-logging filesystem --level information
After enabling logging, you can stream the logs in real time to see what happens during startup:
az webapp log tail --name my-app-service --resource-group my-resource-group
Common Causes and Fixes
- Missing environment variables: Ensure all required application settings are configured in the App Service configuration section.
- Incorrect runtime version: Verify that the runtime stack selected in App Service matches your application requirements.
- Port binding issues: App Service expects your application to listen on the port defined by the PORT environment variable. In Node.js, use
process.env.PORTinstead of a hardcoded port. - Dependency resolution failures: Make sure all dependencies are included in your deployment package, especially when deploying a zip file without a build step.
For Node.js applications, a common mistake is hardcoding the listening port. Here is the correct approach:
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;
app.get('/', (req, res) => {
res.send('Hello from App Service');
});
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
Common Issue 2: Deployment Failures
Deployment failures can occur for many reasons, including incorrect deployment credentials, oversized packages, or conflicts with existing files. Understanding the deployment method you are using helps narrow down the problem.
Troubleshooting ZIP Deployments
ZIP deployment is one of the most common deployment methods. If your ZIP deployment fails, check the deployment logs first:
az webapp deployment log show --name my-app-service --resource-group my-resource-group
A frequent issue is deploying a ZIP file that contains the root folder structure rather than the application files directly. The ZIP file should contain your application files at the root level, not nested inside a folder. You can create a properly structured ZIP file using the following command:
cd /path/to/your/app
zip -r app.zip . -x "*.git*"
Then deploy using the Azure CLI:
az webapp deployment source config-zip --name my-app-service --resource-group my-resource-group --src app.zip
Handling Kudu Deployment Errors
Kudu is the deployment engine behind App Service. If you encounter Kudu-related errors, you can access the Kudu dashboard directly by navigating to https://<your-app-name>.scm.azurewebsites.net. From there, you can view deployment logs, check the file structure, and run diagnostic commands.
For .NET applications, a common deployment issue is missing build artifacts. Ensure your deployment package includes the compiled output, or enable the build during deployment by setting the application setting SCM_DO_BUILD_DURING_DEPLOYMENT to true.
Common Issue 3: Performance and Timeout Issues
Slow response times and request timeouts are common in App Service, especially when applications handle high traffic or perform resource-intensive operations. The default request timeout for App Service is 230 seconds. If your application takes longer than this, the request will be terminated.
Identifying Performance Bottlenecks
Use Application Insights to identify slow requests, database queries, and external dependency calls. If you have not already enabled Application Insights, you can do so with the following command:
az monitor app-insights component create --app my-app-insights --location eastus --resource-group my-resource-group --application-type web
Then link it to your App Service:
az webapp config appsettings set --name my-app-service --resource-group my-resource-group --settings APPINSIGHTS_INSTRUMENTATIONKEY="your-instrumentation-key"
Resolving Timeout Problems
If your application performs long-running operations, consider offloading them to a background process using Azure Functions, Azure Queue Storage, or WebJobs. Here is an example of how to refactor a long-running synchronous operation into an asynchronous pattern using a queue:
// Instead of processing synchronously, enqueue the work
const { QueueClient } = require('@azure/storage-queue');
const queueClient = new QueueClient(process.env.AZURE_STORAGE_CONNECTION_STRING, 'work-items');
app.post('/api/process', async (req, res) => {
const workItem = JSON.stringify(req.body);
await queueClient.sendMessage(Buffer.from(workItem).toString('base64'));
res.status(202).json({ message: 'Work item queued for processing' });
});
This pattern returns immediately with a 202 Accepted response, while the actual processing happens in a separate Azure Function or WebJob that reads from the queue.
Common Issue 4: Connection String and Configuration Problems
Applications often fail to connect to databases or other services because of misconfigured connection strings or environment variables. App Service manages these through Application Settings and Connection Strings, which are injected as environment variables at runtime.
Accessing Configuration Values
The way you access configuration values depends on your application stack. In .NET Core and .NET 5+, connection strings defined in the portal are available through Configuration.GetConnectionString(). In Node.js, Python, and other stacks, they are available as environment variables prefixed with specific strings.
For .NET applications:
// In Program.cs or Startup.cs
var connectionString = builder.Configuration.GetConnectionString("MyDatabase");
// Connection strings set in the portal appear under "ConnectionStrings:MyDatabase"
builder.Services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(connectionString));
For Node.js applications, connection strings appear with a prefix depending on the database type:
// SQL Server connection strings are prefixed with SQLCONNSTR_
// MySQL connection strings are prefixed with MYSQLCONNSTR_
// PostgreSQL connection strings are prefixed with POSTGRESQLCONNSTR_
// Custom connection strings are prefixed with CUSTOMCONNSTR_
const connectionString = process.env.SQLCONNSTR_MyDatabase;
if (!connectionString) {
console.error('Database connection string is not configured');
process.exit(1);
}
Verifying Configuration at Runtime
To verify that your application settings are correctly injected, you can use the Kudu environment variables endpoint. Navigate to https://<your-app-name>.scm.azurewebsites.net/Env to view all environment variables available to your application. Alternatively, add a temporary diagnostic endpoint in your application:
app.get('/debug/env', (req, res) => {
// Only enable this in non-production environments
const safeEnv = Object.keys(process.env)
.filter(key => !key.includes('KEY') && !key.includes('SECRET') && !key.includes('PASSWORD'))
.reduce((obj, key) => {
obj[key] = process.env[key];
return obj;
}, {});
res.json(safeEnv);
});
Always remove or secure diagnostic endpoints before deploying to production to avoid exposing sensitive information.
Common Issue 5: Custom Domain and SSL Certificate Issues
Mapping a custom domain to your App Service and configuring SSL certificates can be tricky. Common issues include DNS propagation delays, incorrect CNAME records, and certificate binding problems.
Mapping a Custom Domain
To map a custom domain, you need to create either a CNAME record or an A record in your DNS provider. For a subdomain like www.example.com, use a CNAME record pointing to your App Service default URL. For a root domain like example.com, use an A record pointing to the App Service IP address.
After configuring DNS, verify the record before binding it in Azure:
nslookup www.example.com
dig www.example.com CNAME
Once DNS resolves correctly, map the custom domain using the Azure CLI:
az webapp config hostname add --webapp-name my-app-service --resource-group my-resource-group --hostname www.example.com
Binding SSL Certificates
For SSL, you can use a free App Service Managed Certificate or upload your own certificate. To create a free managed certificate:
az webapp config ssl create --hostname www.example.com --name my-app-service --resource-group my-resource-group
If you are using your own certificate, ensure it is a valid PFX file and upload it with the correct password:
az webapp config ssl upload --name my-app-service --resource-group my-resource-group --certificate-file ./certificate.pfx --certificate-password "your-password"
After uploading, bind the certificate to your custom domain:
az webapp config ssl bind --name my-app-service --resource-group my-resource-group --certificate-thumbprint "your-thumbprint" --ssl-type SNI
Best Practices for App Service Troubleshooting
Enable Diagnostic Logging Early
Always enable application logging, web server logging, and detailed error messages in non-production environments. This gives you the data you need when issues arise. Use the following command to enable comprehensive logging:
az webapp log config --name my-app-service --resource-group my-resource-group \
--application-logging filesystem \
--detailed-error-messages true \
--failed-request-tracing true \
--web-server-logging filesystem \
--level verbose
Use Health Checks
App Service supports health check functionality that monitors a specific endpoint and automatically removes unhealthy instances from the load balancer. Implement a health check endpoint that verifies critical dependencies:
app.get('/health', async (req, res) => {
try {
// Check database connectivity
await db.ping();
// Check cache connectivity
await cache.ping();
res.status(200).json({ status: 'healthy' });
} catch (error) {
res.status(503).json({ status: 'unhealthy', error: error.message });
}
});
Configure the health check in the Azure Portal under Monitoring > Health check, or via the CLI:
az webapp config set --name my-app-service --resource-group my-resource-group --health-check-path /health
Implement Proper Error Handling
Unhandled exceptions are a leading cause of application crashes. Always implement global error handlers to catch and log unexpected errors gracefully:
// Express.js global error handler
app.use((err, req, res, next) => {
console.error('Unhandled error:', err);
res.status(500).json({
error: 'Internal server error',
requestId: req.id
});
});
// Catch unhandled promise rejections
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
});
// Catch uncaught exceptions
process.on('uncaughtException', (err) => {
console.error('Uncaught Exception:', err);
process.exit(1);
});
Monitor and Set Up Alerts
Proactive monitoring helps you catch issues before users report them. Set up alerts for key metrics such as CPU percentage, memory percentage, HTTP 5xx errors, and response time. Use the following CLI command to create an alert for high CPU usage:
az monitor metrics alert create --name "High CPU Alert" \
--resource-group my-resource-group \
--scopes "/subscriptions/<subscription-id>/resourceGroups/my-resource-group/providers/Microsoft.Web/sites/my-app-service" \
--condition "avg CpuPercentage > 80" \
--window-size 5m \
--evaluation-frequency 1m \
--action <action-group-id>
Use Deployment Slots for Safe Deployments
Deployment slots allow you to deploy and test new versions of your application in a staging environment before swapping to production. This reduces the risk of deployment-related issues affecting your users:
az webapp deployment slot create --name my-app-service --resource-group my-resource-group --slot staging
# Deploy to staging slot
az webapp deployment source config-zip --name my-app-service --resource-group my-resource-group --slot staging --src app.zip
# Swap staging to production after validation
az webapp deployment slot swap --name my-app-service --resource-group my-resource-group --slot staging --target-slot production
Conclusion
Troubleshooting Azure App Service effectively requires a combination of the right tools, a systematic approach, and an understanding of common failure patterns. By enabling diagnostic logging early, implementing health checks, using deployment slots, and setting up proactive monitoring, you can dramatically reduce the time it takes to identify and resolve issues. Remember that most App Service problems fall into a few well-known categories: startup failures, deployment errors, performance bottlenecks, configuration mismatches, and domain or SSL issues. When you encounter a problem, start with the logs, isolate the root cause, and apply the targeted solutions outlined in this tutorial. With these practices in place, you will be well-equipped to keep your App Service applications running smoothly and reliably.