Migrate ASP.NET Core from IIS to Azure App Service (.NET 10)

If you need to migrate an ASP.NET Core website from IIS to Azure App Service, the move is easy once you know the order of the steps. This beginner-friendly guide shows you how to retarget the app to .NET 10, create the Azure resources, move your SQL Server database, store secrets safely, add a custom domain with SSL, and deploy with GitHub Actions. Each step shows the Azure Portal path and the Azure CLI command.

Migrate ASP.NET Core from IIS to Azure App Service (.NET 10)

Versions Used in This Guide

Old tutorials cause many migration problems, so check versions first. These were checked in October 2026:

  • .NET 10 (LTS). Microsoft's support policy page, updated September 8, 2026, lists 10.0.12 as the latest patch and support until November 14, 2028.
  • .NET 8 and .NET 9 end support on November 10, 2026. If your IIS site still runs on one of them, move to .NET 10 during this migration.
  • GitHub Actions: actions/checkout@v6, actions/setup-dotnet@v5, azure/login@v3, and azure/webapps-deploy@v3.
  • Terraform: the azurerm provider 4.54 or newer, the release that added .NET 10 support for Linux web apps.
  • Azure CLI: the current release. If a flag differs on your machine, run the command with --help.

Patch numbers change every month, so install the newest 10.0.x patch when you follow along.

What You Will Learn

  • How to prepare an ASP.NET Core app that runs on IIS for Azure App Service
  • How to create a resource group, an App Service plan, and a web app
  • How to move a SQL Server database to Azure SQL Database
  • How to move secrets out of appsettings.json into App Service settings and Azure Key Vault
  • How to add a custom domain and a free SSL certificate
  • How to deploy automatically with GitHub Actions, and how to fix the most common errors

Prerequisites

You do not need Azure experience. You do need the following:

  • An Azure subscription. A free account works to start, but the Basic plan and Azure SQL used here are billed against your credit or card.
  • The Azure CLI installed. Check it with az --version.
  • The .NET 10 SDK on your computer. Check it with dotnet --list-sdks.
  • Your project in a GitHub repository.
  • SQL Server access and the SqlPackage tool, if your site uses a database. Depends on your setup.
  • A domain name you control, if you want a custom domain. Depends on your setup.
  • A backup of your IIS site folder and your database. Take it before you change anything.

Terms you will meet: a resource group is a folder that holds related Azure resources. An App Service plan is the server capacity you pay for. A web app is your site running on that plan. Azure SQL Database is the managed version of SQL Server. Key Vault is a safe store for passwords and keys.

IIS vs Azure App Service: What Changes?

On IIS you manage the server. On Azure App Service you manage only the app, and Azure runs the machine underneath.

In cloud terms, a self-managed IIS server on a virtual machine is IaaS, where you run everything above the hardware. App Service is PaaS, where Azure runs the operating system and web server and you run only the app. This table shows the main differences.

AreaIIS on your own serverAzure App Service
CostYou pay for hardware or a virtual machine, a Windows license, and admin time.You pay for the plan you choose. A Free tier exists for testing, and you can change plans later.
ScalingYou add servers and a load balancer yourself.You scale up (bigger plan) or out (more instances) from the Portal or CLI. Autoscale needs a higher tier.
MaintenanceYou patch Windows, IIS, and the .NET runtime.Azure patches the operating system and platform. You keep your app and NuGet packages up to date.
SecurityYou manage the firewall, certificates, and secrets.You get an HTTPS-only setting, free managed certificates, managed identities, and Key Vault integration.

Is Your Site Still on .NET Framework 4.8?

Many IIS sites are classic ASP.NET on .NET Framework 4.8, not ASP.NET Core. The steps in this guide target ASP.NET Core, but you can still move a 4.8 site in two stages.

  1. Lift and shift first. App Service can host .NET Framework 4.8 on a Windows plan. Run the Azure App Service Migration Assistant on the IIS server, which can create the Windows-based web app for you. For a manual setup, remove --is-linux in Step 3 and pick the ASP.NET 4.8 value from az webapp list-runtimes --os-type windows.
  2. Modernize later. Once the site runs in Azure, port it to ASP.NET Core and .NET 10 in smaller steps. Microsoft's .NET upgrade tooling can help with this, but expect manual work for System.Web code, Web Forms, and older authentication.

The Windows plan keeps your app running on IIS, so web.config, Windows-only libraries, and registry access behave closer to what you have today. The tradeoff is that you stay on Framework 4.8 until you finish the port, so plan that second stage rather than leaving it open-ended.

Migration Assistant, Azure Migrate, or the Manual Method?

Microsoft offers two official tools, so you should know them before you pick a method.

  • Azure App Service Migration Assistant. A free tool you run on the IIS server. It checks whether each site is ready for App Service and can move the site content and configuration. It does not migrate the database. See appmigration.microsoft.com.
  • Azure Migrate. Built to discover, assess, and move many ASP.NET web apps across many servers at once, so it suits teams and larger estates. See Microsoft's Azure Migrate tutorial.
  • The manual method in this guide. You build the target yourself, so you choose the runtime (.NET 10), the operating system, the secrets setup, and the deployment pipeline. It takes longer, but you learn every piece.

A practical approach is to run the Migration Assistant readiness check first, even if you migrate manually. It can flag unsupported IIS features before you start. Tools change, so check the current documentation for supported IIS versions, .NET versions, and plan types.

Pre-Migration Checklist

Walk through this list before you create anything in Azure.

  • Framework version. Open your .csproj file and note the TargetFramework.
  • Backups. Copy the IIS site folder, web.config, and a full database backup.
  • All settings. List everything in appsettings*.json, web.config, environment variables, and connection strings.
  • IIS-only features. Look for Windows authentication, URL Rewrite rules, custom IIS modules, scheduled tasks, and registry or file-share access.
  • Files written to disk. Plan to store user uploads in Azure Blob Storage instead of the local disk.
  • Outbound IP rules. If a partner API or SMTP server allows only your server IP, send them the new App Service outbound IPs.
  • DNS TTL. Lower the TTL on your DNS records to a few minutes a day before cutover, so the final switch is fast.
  • Region. Use one region close to your users for both the web app and the database.

How to Migrate an ASP.NET Core Website from IIS to Azure App Service, Step by Step

Follow the steps in order and test each one. Do the whole process on a test copy first, then repeat it for production.

This is the setup you will have at the end. Visitors reach App Service over HTTPS. GitHub Actions deploys the code. The app reads its connection string from Key Vault through its managed identity, and then connects to Azure SQL.

Architecture after migrating to Azure App Service GitHub Actions deploys to Azure App Service. Visitors reach the app over HTTPS. The app reads a secret from Azure Key Vault and connects to Azure SQL Database. Visitors (browser) HTTPS + domain GitHub Actions build, test, deploy OIDC deploy Azure App Service ASP.NET Core on .NET 10 reads secret Azure Key Vault holds the connection string connects Azure SQL Database your migrated data

Figure 1: the target architecture after migration.

Step 1: Retarget Your App to .NET 10

If your app already targets net10.0, skip this step. Otherwise open the .csproj file and change the target framework:

<PropertyGroup>
  <TargetFramework>net10.0</TargetFramework>
</PropertyGroup>

Then update packages that must match the framework, such as Entity Framework Core, and test locally:

dotnet list package --outdated
dotnet build --configuration Release
dotnet test

Depends on your setup: apps built on .NET Framework 4.8 are not ASP.NET Core. See "Is Your Site Still on .NET Framework 4.8?" above for the Windows plan path, then return here when you are ready to port to .NET 10.

Expected result: the project builds and runs locally on .NET 10 with no errors.

Gotcha: cookie login no longer redirects API calls in .NET 10

If your site mixes MVC or Razor pages with API endpoints, check this after retargeting. In .NET 10, cookie authentication returns 401 or 403 for known API endpoints instead of redirecting to the login page. Browser pages still redirect as before. If an AJAX call that used to land on your login page now fails with 401, this is why. Handle the 401 in your JavaScript, or restore the old redirect for everything:

builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(options =>
    {
        options.Events.OnRedirectToLogin = context =>
        {
            context.Response.Redirect(context.RedirectUri);
            return Task.CompletedTask;
        };
        options.Events.OnRedirectToAccessDenied = context =>
        {
            context.Response.Redirect(context.RedirectUri);
            return Task.CompletedTask;
        };
    });

Restoring the redirect for APIs is usually the wrong long-term choice, because API clients expect a status code. Use it only as a temporary bridge.

Tip: replace old WebHost startup code

Older apps often start with WebHost.CreateDefaultBuilder and a Startup class. In .NET 10 those WebHost, WebHostBuilder, and IWebHost types are obsolete, so the build shows warnings. Move to WebApplicationBuilder, which is what the code in Step 2 uses:

// Old style
WebHost.CreateDefaultBuilder(args).UseStartup<Startup>().Build().Run();

// New style: ConfigureServices becomes builder.Services.*,
// and Configure becomes app.Use*() and app.Map*()
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.Run();

Step 2: Make the App Cloud-Ready

App Service does not read files from your old IIS server. Fix these four things before you deploy:

  • No secrets in appsettings.json. Passwords and connection strings must come from settings, not from files in Git.
  • No hard-coded Windows paths. Use Path.Combine and avoid C:\ paths. Linux plans use forward slashes and care about upper and lower case.
  • A health endpoint. A simple /healthz URL makes it easy to check the app after each deployment.
  • No user files on local disk. Use Blob Storage for uploads.

Here is the kind of file to avoid. If a real password was ever committed to Git, change that password now:

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=OLDSERVER\\SQLEXPRESS;Database=ShopDb;User Id=sa;Password=MyPassword123;"
  }
}

Remove the connection string from the file. The value will come from App Service settings in Step 5:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information"
    }
  },
  "AllowedHosts": "*"
}

The code below reads the connection string from configuration and adds the health endpoint. The ShopDbContext name is an example, so use your own context:

var builder = WebApplication.CreateBuilder(args);

// Reads ConnectionStrings:DefaultConnection from user secrets locally
// and from App Service settings in Azure.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
    ?? throw new InvalidOperationException("Connection string 'DefaultConnection' was not found.");

builder.Services.AddDbContext<ShopDbContext>(options => options.UseSqlServer(connectionString));
builder.Services.AddHealthChecks();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.MapHealthChecks("/healthz");

// Your existing middleware and routes go here.

app.Run();

For local development, store the value with user secrets so it never touches your repository:

dotnet user-secrets init
dotnet user-secrets set "ConnectionStrings:DefaultConnection" "YOUR_LOCAL_CONNECTION_STRING"

Expected result: the app still runs locally, and https://localhost:PORT/healthz returns Healthy.

Step 3: Create the Resource Group, App Service Plan, and Web App

Azure Portal path: search for App Services, select Create, then choose Web App. Pick your resource group, a unique name, Code as the publish type, .NET 10 (LTS) as the runtime stack, Linux as the operating system, a region, and a pricing plan. Menu names change over time, so verify them in the current Portal.

Azure CLI path: the commands below use Bash syntax, which works in Azure Cloud Shell, WSL, macOS, and Linux. In PowerShell, set variables like $RG = "rg-myshop-prod".

RG=rg-myshop-prod
LOCATION=eastus          # replace with your region
PLAN=plan-myshop-prod
APP=myshop-prod-12345    # must be globally unique

az login
az group create --name $RG --location $LOCATION
az appservice plan create --name $PLAN --resource-group $RG --sku B1 --is-linux
az webapp create --name $APP --resource-group $RG --plan $PLAN --runtime "DOTNETCORE:10.0"

If the runtime value is rejected, list the values your CLI accepts and use one of them:

az webapp list-runtimes --os-type linux

Windows or Linux? Linux plans are a common choice for ASP.NET Core and are used in this guide. Choose a Windows plan if your app needs Windows-only features, such as COM components or registry access. Windows plans host your app through IIS, which is why the 500.30 and 502.5 errors later in this guide appear there. For a Windows plan, remove --is-linux and use a runtime value from az webapp list-runtimes --os-type windows.

Expected result: the web app exists, and its default URL shows the Azure placeholder page. Get the URL with:

az webapp show --name $APP --resource-group $RG --query defaultHostName --output tsv

Step 4: Migrate SQL Server to Azure SQL Database

Skip this step if your site has no database. Otherwise create the Azure SQL server first. The S0 objective below is only an example, so check the current pricing before you choose one. The empty database is created here so you can test the connection, but you will delete it just before the import in this step, because SqlPackage creates the database itself.

SQL_SERVER=myshop-sql-12345   # must be globally unique

az sql server create --name $SQL_SERVER --resource-group $RG --location $LOCATION \
  --admin-user sqladminuser --admin-password "REPLACE_WITH_A_STRONG_PASSWORD"
az sql db create --resource-group $RG --server $SQL_SERVER --name ShopDb --service-objective S0

Azure SQL blocks all connections by default. Add a firewall rule for your own IP address so you can import the data, and one rule that lets Azure services reach the server:

az sql server firewall-rule create --resource-group $RG --server $SQL_SERVER \
  --name AllowMyIp --start-ip-address YOUR_PUBLIC_IP --end-ip-address YOUR_PUBLIC_IP
az sql server firewall-rule create --resource-group $RG --server $SQL_SERVER \
  --name AllowAzureServices --start-ip-address 0.0.0.0 --end-ip-address 0.0.0.0

The Azure services rule is broad. It is fine for learning, but for production plan to use a private endpoint or virtual network rules.

Now export the database to a .bacpac file and import it into Azure SQL with the SqlPackage tool. Run these on a machine that can reach both servers:

sqlpackage /Action:Export /SourceConnectionString:"Server=OLDSERVER\SQLEXPRESS;Database=ShopDb;Integrated Security=True;TrustServerCertificate=True;" /TargetFile:"ShopDb.bacpac"

sqlpackage /Action:Import /SourceFile:"ShopDb.bacpac" /TargetConnectionString:"Server=tcp:myshop-sql-12345.database.windows.net,1433;Database=ShopDb;User ID=sqladminuser;Password=YOUR_PASSWORD;Encrypt=True;"

Three things to know before you run these:

  • An export from a live database can be inconsistent if users are writing data, so stop writes first.
  • Before you run the import, delete the empty ShopDb database (az sql db delete --resource-group $RG --server $SQL_SERVER --name ShopDb --yes), because the import creates it. Check the SqlPackage documentation for current parameters.
  • Azure SQL Database does not support everything SQL Server does. SQL Agent jobs and cross-database queries are common surprises. Depends on your setup, so test the app against the imported copy.

Expected result: you can connect to ShopDb on the Azure SQL server and see your tables and row counts.

Step 5: Move Secrets to App Service Settings and Azure Key Vault

Never put the database password in your code or in appsettings.json. App Service gives you two safe places: app settings, and Key Vault references that point to a secret stored in Key Vault. Key Vault is the better choice for production because access is controlled and can be audited.

Azure Portal path: open your web app, then Settings, then Environment variables (older Portal versions call this Configuration). Add a setting named ConnectionStrings__DefaultConnection. The double underscore tells ASP.NET Core that this setting maps to ConnectionStrings:DefaultConnection.

Azure CLI path with Key Vault and a managed identity. A managed identity is an Azure-managed login for your app, so the app needs no password to read the vault:

KV=kv-myshop-12345   # must be globally unique

az keyvault create --name $KV --resource-group $RG --location $LOCATION --enable-rbac-authorization true
KV_ID=$(az keyvault show --name $KV --query id --output tsv)

# Give yourself permission to write secrets
MY_ID=$(az ad signed-in-user show --query id --output tsv)
az role assignment create --role "Key Vault Secrets Officer" --assignee-object-id $MY_ID --assignee-principal-type User --scope $KV_ID

# Store the connection string as a secret
az keyvault secret set --vault-name $KV --name ShopDbConnection \
  --value "Server=tcp:myshop-sql-12345.database.windows.net,1433;Database=ShopDb;User ID=sqladminuser;Password=YOUR_PASSWORD;Encrypt=True;"

# Let the web app read secrets
az webapp identity assign --resource-group $RG --name $APP --scope $KV_ID --role "Key Vault Secrets User"

# Point the app setting at the secret
SECRET_URI=$(az keyvault secret show --vault-name $KV --name ShopDbConnection --query id --output tsv)
az webapp config appsettings set --resource-group $RG --name $APP \
  --settings "ConnectionStrings__DefaultConnection=@Microsoft.KeyVault(SecretUri=$SECRET_URI)"

If the secret set command is refused right after the role assignment, wait a minute or two and run it again, because role assignments can take a short time to apply.

The secret URI returned by the CLI includes a version number, which pins the app to that exact version. To follow the newest version, remove the version part at the end of the URI. App Service caches reference values and refetches them about every 24 hours, so a rotated secret may take time to appear.

Expected result: the setting shows a resolved Key Vault reference, and the app can read the connection string at startup.

Step 6: Deploy the App Manually the First Time

Do the first deployment by hand, so you know the app works before you add automation. Publish the app, zip it, and send it to Azure:

dotnet publish --configuration Release --output ./publish
cd publish
zip -r ../app.zip .
cd ..
az webapp deploy --resource-group $RG --name $APP --src-path app.zip --type zip

On Windows without zip, use Compress-Archive -Path ./publish/* -DestinationPath app.zip in PowerShell. Then open the default URL and the /healthz page.

Expected result: your site loads on the azurewebsites.net address and /healthz returns Healthy. If you see an error page, jump to the errors section below.

Step 7: Add a Custom Domain and Free SSL Certificate

Custom domains need at least the Shared tier, and the free App Service managed certificate needs the Basic tier or higher. The B1 plan from Step 3 already meets this. The Free tier does not support custom TLS bindings.

Azure Portal path: open the web app, then Custom domains, then Add custom domain. Enter your domain, choose App Service Managed Certificate, and follow the DNS instructions. Verify the menu names in your current Portal.

You need two DNS records at your domain provider: a CNAME (or an A record for a root domain) that points to your app, and a TXT record named asuid. plus your host name that proves you own the domain. Get the TXT value with the CLI:

az webapp show --name $APP --resource-group $RG --query customDomainVerificationId --output tsv

After the DNS records are live, add the domain, create the certificate, bind it, and force HTTPS. Flag names can change, so verify with --help if a command fails:

az webapp config hostname add --webapp-name $APP --resource-group $RG --hostname www.example.com
az webapp config ssl create --resource-group $RG --name $APP --hostname www.example.com
az webapp config ssl bind --resource-group $RG --name $APP --certificate-thumbprint THUMBPRINT_FROM_PREVIOUS_COMMAND --ssl-type SNI
az webapp update --resource-group $RG --name $APP --https-only true
az webapp config set --resource-group $RG --name $APP --min-tls-version 1.2

Managed certificates have limits, such as which domain types they cover. Read the current limitations in the App Service TLS binding documentation before you rely on one for a root domain.

Expected result: https://www.example.com loads with a valid padlock, and http:// redirects to HTTPS.

Step 8: Deploy Automatically with GitHub Actions

Now replace manual zips with a pipeline. The safest login method is OpenID Connect (OIDC). GitHub proves its identity to Azure with a short-lived token, so you store no passwords. First create an identity in Azure that GitHub can use. Replace the placeholders with your own values:

APP_ID=$(az ad app create --display-name gh-deploy-myshop --query appId --output tsv)
az ad sp create --id $APP_ID
az ad app federated-credential create --id $APP_ID --parameters credential.json

APP_RESOURCE_ID=$(az webapp show --resource-group $RG --name $APP --query id --output tsv)
az role assignment create --assignee $APP_ID --role "Website Contributor" --scope $APP_RESOURCE_ID

Create the credential.json file before you run the federated credential command. It tells Azure which GitHub repository and environment may log in:

{
  "name": "github-production",
  "issuer": "https://token.actions.githubusercontent.com",
  "subject": "repo:YOUR_GITHUB_USER/YOUR_REPO:environment:production",
  "audiences": ["api://AzureADTokenExchange"]
}

In your GitHub repository, add three secrets: AZURE_CLIENT_ID (the APP_ID value), AZURE_TENANT_ID, and AZURE_SUBSCRIPTION_ID. The Portal Deployment Center can also create this workflow for you, but the file below shows exactly what happens.

Save this as .github/workflows/deploy.yml. Change the app name and the project path:

name: Build and deploy to Azure App Service

on:
  push:
    branches: [main]
  workflow_dispatch:

# Needed for OIDC login to Azure
permissions:
  id-token: write
  contents: read

env:
  AZURE_WEBAPP_NAME: myshop-prod-12345
  DOTNET_VERSION: 10.0.x

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    environment: production   # must match the subject in credential.json

    steps:
      - name: Check out the code
        uses: actions/checkout@v6

      - name: Set up .NET
        uses: actions/setup-dotnet@v5
        with:
          dotnet-version: ${{ env.DOTNET_VERSION }}

      - name: Restore and build
        run: |
          dotnet restore
          dotnet build --configuration Release --no-restore

      # Remove this step if your solution has no test projects
      - name: Run tests
        run: dotnet test --configuration Release --no-build

      - name: Publish
        run: dotnet publish ./MyShop.Web/MyShop.Web.csproj --configuration Release --no-build --output ./publish

      - name: Log in to Azure with OIDC
        uses: azure/login@v3
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

      - name: Deploy to App Service
        uses: azure/webapps-deploy@v3
        with:
          app-name: ${{ env.AZURE_WEBAPP_NAME }}
          package: ./publish

If your repository uses a .slnx solution file, or has more than one solution file in the folder, pass the solution path to dotnet restore, dotnet build, and dotnet test, for example dotnet restore MyShop.slnx.

Expected result: a push to main builds, tests, and deploys the site, and the run shows green in the Actions tab. Create the production environment in your repository settings first, and add required reviewers there if you want a manual approval before each deployment.

Step 9: Plan the Cutover and a Safe Rollback

Switching traffic is the risky moment, so do it in a planned order:

  1. Test the full site on the azurewebsites.net address, including login, forms, uploads, and email.
  2. Choose a quiet time. Put the old site into maintenance mode so no new data is written.
  3. Export the database one last time, import it into Azure SQL, and deploy the latest code.
  4. Change the DNS record to point to App Service. Because you lowered the TTL earlier, the change spreads quickly.
  5. Keep the old IIS server and the database backup for at least a week.

Rolling back is a DNS change back to the old server, which is fast because of the low TTL. For later releases, Standard tier and above support deployment slots, so you can deploy to a staging slot and swap it into production.

Optional: Create the Same Resources with Terraform

Terraform keeps the infrastructure in code, so you can rebuild it at any time. This example creates the resource group, the plan, and the web app. It needs the azurerm provider 4.54 or newer for dotnet_version = "10.0" on Linux.

terraform {
  required_version = ">= 1.6"
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 4.54"
    }
  }
}

provider "azurerm" {
  features {}
  subscription_id = var.subscription_id
}

variable "subscription_id" {
  type = string
}

variable "location" {
  type    = string
  default = "eastus"
}

resource "azurerm_resource_group" "rg" {
  name     = "rg-myshop-prod"
  location = var.location
}

resource "azurerm_service_plan" "plan" {
  name                = "plan-myshop-prod"
  resource_group_name = azurerm_resource_group.rg.name
  location            = azurerm_resource_group.rg.location
  os_type             = "Linux"
  sku_name            = "B1"
}

resource "azurerm_linux_web_app" "app" {
  name                = "myshop-prod-12345"
  resource_group_name = azurerm_resource_group.rg.name
  location            = azurerm_resource_group.rg.location
  service_plan_id     = azurerm_service_plan.plan.id
  https_only          = true

  identity {
    type = "SystemAssigned"
  }

  site_config {
    always_on         = true
    health_check_path = "/healthz"

    application_stack {
      dotnet_version = "10.0"
    }
  }
}

output "default_hostname" {
  value = azurerm_linux_web_app.app.default_hostname
}

Run terraform init, then terraform plan, then terraform apply. For a Windows plan, use azurerm_windows_web_app and the v10.0 version string.

App Service Cost Note: Free, Basic, and Standard

Prices change by region and over time, so check the current Azure App Service pricing page before you decide. In general:

  • Free (F1): for learning and tests only. It uses shared capacity with strict limits and has no custom TLS bindings.
  • Basic (B1 and up): dedicated capacity, custom domains, and free managed certificates. This is a sensible start for a small production site.
  • Standard (S1 and up): adds deployment slots, scheduled backups, and autoscale for safer releases and automatic scaling.

Azure SQL Database, Key Vault, and storage are billed separately from the plan.

Common Errors and Fixes

When a deployment fails, read the logs before you change anything. Turn on application logging, then stream the logs:

az webapp log config --name $APP --resource-group $RG --application-logging filesystem --level information
az webapp log tail --name $APP --resource-group $RG

In the Portal, the Log stream page shows the same output. Error wording can vary slightly between platform versions, so match on the error number.

HTTP Error 500.30 - ASP.NET Core app failed to start

This appears on Windows plans, where IIS starts your app through the ASP.NET Core Module. The app crashed during startup. Common causes:

  • A missing setting, such as ConnectionStrings__DefaultConnection, that makes Program.cs throw an exception.
  • The runtime stack in App Service does not match your target framework. An app built for net10.0 needs the .NET 10 stack.
  • A database error during startup, such as a migration that cannot connect.

Fix: read the log stream for the real exception message. Then correct the setting or the runtime stack and restart the app.

HTTP Error 502.5 - Process Failure

This is also a Windows and IIS error. The process that should serve your app did not start or stopped right away. Some versions word it as "ANCM Out-Of-Process Startup Failure".

Fix: run the published app on your computer with dotnet YourApp.dll from the publish folder. If it fails there, the cause is in your code or published files. Also check that web.config was not edited by hand.

Application Error or "Container ... didn't respond to HTTP pings on port"

This appears on Linux plans when the app did not start or listens on a port App Service cannot reach.

Fix: remove hard-coded URLs and ports, such as UseUrls("http://localhost:5000"), so the default hosting settings apply. Then read the log stream for a startup exception.

Login failed for user

The app reached Azure SQL, but the login was refused. Common causes:

  • The user name or password in the connection string is wrong, or special characters in the password are not handled correctly.
  • The Key Vault reference did not resolve, so the app received the raw @Microsoft.KeyVault(...) text instead of the real value.

Fix: test the same connection string from your computer, then check the setting value in the Portal.

Client with IP address is not allowed to access the server

This is the Azure SQL firewall (error 40615). The server rejects the caller's IP address.

Fix: add a firewall rule for your own IP when you work from your computer. For the web app, either enable the Azure services rule from Step 4 or add the app's outbound IPs:

az webapp show --name $APP --resource-group $RG --query possibleOutboundIpAddresses --output tsv

Key Vault reference is not resolved

The setting still shows the reference text and not the secret value. The usual causes are a missing Key Vault Secrets User role for the app's managed identity, a wrong secret URI, or a Key Vault firewall that blocks the app. Role changes can take a few minutes.

Fix: confirm the identity is enabled, check the role assignment on the vault, copy the secret URI again, and restart the app.

Pages, CSS, or images return 404 on Linux but worked on IIS

Windows ignores upper and lower case in file names, and Linux does not. A link to /css/Site.css fails when the file is site.css.

Fix: make the names and the references match exactly, and use forward slashes in paths.

Frequently Asked Questions

How much does it cost to host an ASP.NET Core website on Azure App Service?

It depends on the plan, the region, and the database. A small site often starts on the Basic tier. Use the Azure pricing calculator and include Azure SQL and storage.

Can I migrate with zero downtime?

You can get close. Test the Azure site first, lower the DNS TTL, and keep the final data copy short. The database must stop receiving writes during the last export, so plan a brief maintenance window.

Is there a free tier for App Service?

Yes, the Free (F1) tier. It is good for learning, but it has strict limits and no custom TLS bindings, so it does not suit a real production site.

How do I roll back if something goes wrong?

Point DNS back to the old server, which stays online for a week. For code problems later, redeploy the previous commit, or swap back a deployment slot on Standard tier.

How do I add a custom domain to my App Service?

Use Basic tier or higher, add the DNS records, then add the domain and a managed certificate. Step 7 shows the full process.

Summary and Next Steps

You now know how to migrate an ASP.NET Core website from IIS to Azure App Service. You retargeted the app to .NET 10, created the Azure resources, moved the SQL Server database to Azure SQL, stored secrets in Key Vault, added a custom domain with SSL, and set up GitHub Actions with OIDC. You also have a cutover plan and a fix list for the most common errors.

Good next steps are:

  • Add Application Insights so you can see errors and slow requests.
  • Replace the SQL password with a managed identity, so the connection string holds no secret at all.
  • Move uploads to Blob Storage and add a deployment slot for safer releases.
  • Stay on .NET 10 LTS, and update to each new 10.0.x patch, until support ends in November 2028.

For deeper reference, read the official App Service .NET quickstart and the .NET support policy. If you hit an error not covered here, share the exact message in the comments.

Codingvila provides articles and blogs on web and software development for beginners as well as free Academic projects for final year students in Asp.Net, MVC, C#, Vb.Net, SQL Server, Angular Js, Android, PHP, Java, Python, Desktop Software Application and etc.

If you have any questions, contact us on info.codingvila@gmail.com