← Back to Articles         
Azure Three-Tier Architecture

Azure Three-Tier Architecture

Published on 23 Oct 2026     32 min read Microsoft AZURE
Azure Project

Azure Three-Tier Architecture — Complete Step-by-Step Implementation Notes

Project: ASP.NET Core MVC + Web API + Azure SQL Database Cloud Platform: Microsoft Azure Region: Central India Resource Group: rg-jntech-lab Custom Domain: syed.jntech.in

These consolidated notes cover the complete Azure lab implementation, from creating the ASP.NET Core applications and Azure Resource Group through networking, database configuration, VMSS deployment, IIS hosting, Key Vault integration, App Service deployment, and custom-domain troubleshooting.

The notes use our actual resource names and confirmed configuration values. Where the original Portal selections were not preserved, I have identified the appropriate configuration without inventing an exact historical value.

 

Part 1: Architecture Overview

1. What We Built

We implemented a three-tier architecture using the following Azure services.

Tier

Technology

Hosting

Presentation

ASP.NET Core MVC (.NET 8)

Azure App Service

Application

ASP.NET Core Web API (.NET 8)

Windows VM Scale Set + IIS

Database

Microsoft SQL Server

Azure SQL Database

Additional infrastructure:

  • Azure Virtual Network and three subnets

  • Network Security Groups and Application Security Group

  • Azure Internal Load Balancer

  • Azure NAT Gateway

  • Azure Bastion

  • Azure SQL Private Endpoint

  • Azure Private DNS

  • Azure Key Vault and Managed Identity

  • Azure Public DNS and custom domain

2. Architecture Diagram

                         INTERNET
                            |
                            | HTTPS
                            v
                     syed.jntech.in
                       (Azure DNS)
                            |
                            v
                 +---------------------+
                 | Azure App Service   |
                 | ASP.NET Core MVC    |
                 | Windows / .NET 8    |
                 |                     |
                 | MVC Instance 1      |
                 | MVC Instance 2      |
                 +----------+----------+
                            |
                     VNet Integration
                            |
                 snet-jntech-appservice
                      10.0.1.0/26
                            |
                            v
                 +---------------------+
                 | Internal Load       |
                 | Balancer            |
                 | 10.0.2.10:80        |
                 +----------+----------+
                            |
                    Backend Pool
                            |
                 +----------+----------+
                 |                     |
                 v                     v
          +-------------+       +-------------+
          | API VM 1    |       | API VM 2    |
          | Windows IIS |       | Windows IIS |
          | .NET 8 API  |       | .NET 8 API  |
          +------+------+       +------+------+
                 |                     |
                 +----------+----------+
                            |
                   snet-jntech-vmss
                      10.0.2.0/24
                            |
                            v
                 +---------------------+
                 | Azure SQL Database  |
                 | sqldb-jntech        |
                 | Private Endpoint    |
                 +---------------------+
                            |
                     Products Table


 Supporting Services
 -------------------

 Azure Bastion
       |
       +---- Secure RDP to private API VMs

 Azure NAT Gateway
       |
       +---- Outbound internet for VMSS subnet

 Azure Key Vault
       |
       +---- SQL connection string
               ^
               |
         Managed Identity

 NSGs / ASG
       |
       +---- Network traffic control

The diagram shows the intended custom-domain entry point. The MVC application and private API/database path are operational, but custom-domain validation and HTTPS binding remain pending.

3. How the Application Works

  1. The user opens the MVC website through Azure App Service.

  2. App Service distributes requests across two MVC instances.

  3. MVC calls http://10.0.2.10/api/products.

  4. VNet Integration routes the request into the Azure virtual network.

  5. The internal load balancer forwards the request to a healthy API VM.

  6. The ASP.NET Core API running under IIS reads its SQL connection string from Azure Key Vault.

  7. The API connects to Azure SQL through the private endpoint.

  8. SQL returns the product data.

  9. The API returns JSON to MVC.

  10. MVC displays the products to the user.

4. Complete Resource Inventory

Resource Type

Resource Name / Configuration

Resource Group

rg-jntech-lab

Region

Central India

Virtual Network

vnet-jntech

VNet Address Space

10.0.0.0/16

App Service Subnet

snet-jntech-appservice — 10.0.1.0/26

VMSS Subnet

snet-jntech-vmss — 10.0.2.0/24

Database Subnet

snet-jntech-db — 10.0.3.0/27

VMSS NSG

nsg-jntech-vmss

Database NSG

nsg-jntech-db

Application Security Group

asg-jntech-api

Azure SQL Database

sqldb-jntech

SQL Private Endpoint

pe-jntech-sql

SQL Private DNS Zone

privatelink.database.windows.net

Key Vault

kv-jntech-lab

NAT Gateway

nat-jntech

NAT Public IP

pip-jntech-nat

VM Scale Set

vmss-jntech-api

VMSS Orchestration

Flexible

VM Image

Windows Server 2022 Azure Edition

VM Size

Standard_D2s_v3

VM Instances

2

Azure Bastion

bas-jntech

Bastion Tier

Standard

Bastion Public IP

pip-jntech-bastion

Internal Load Balancer

lb-jntech-internal

Internal LB IP

10.0.2.10

LB Backend Pool

be-jntech-api

LB Health Probe

hp-jntech-api

LB Rule

rule-jntech-api

App Service Plan

asp-jntech-mvc

App Service Tier

Basic B1

MVC App Service

app-jntech-mvc

MVC Instances

2

Azure DNS Zone

syed.jntech.in

Verified App Service Inbound IP

20.192.170.9

 

Part 2: Prepare ASP.NET Core Applications

Step 1: Create ASP.NET Core MVC Application

We needed a public-facing application to display products.

1. Open Visual Studio

Navigate to:

Visual Studio → Create a new project

Select:

ASP.NET Core Web App (Model-View-Controller)

2. Configure Project

Setting

Value

Framework

.NET 8

Project type

ASP.NET Core MVC

Authentication

None, for this simple lab

HTTPS

Enabled for local development

The exact original project name is not retained in the available history.

3. Create Product Model

Example:

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
}

4. Configure API Base URL

In appsettings.json:

{
  "ApiSettings": {
    "BaseUrl": "http://localhost:5000/"
  }
}

The local URL is illustrative. Use the actual local API address during development.

Later, in Azure App Service, we override it with:

ApiSettings__BaseUrl = http://10.0.2.10/

5. Configure MVC to Call API

Example service:

public class ProductService
{
    private readonly HttpClient _httpClient;

    public ProductService(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async Task<List<Product>> GetProductsAsync()
    {
        return await _httpClient
            .GetFromJsonAsync<List<Product>>("api/products")
            ?? new List<Product>();
    }
}

Register the service in Program.cs:

builder.Services.AddHttpClient<ProductService>(client =>
{
    client.BaseAddress = new Uri(
        builder.Configuration["ApiSettings:BaseUrl"]!);
});

The MVC controller can call this service and return the Products view.

 

Step 2: Create ASP.NET Core Web API

The API retrieves product information from SQL Server.

1. Create Project

Visual Studio → Solution → Add → New Project

Select:

ASP.NET Core Web API

2. Configure

Setting

Value

Project

AzurePractice.Api

Framework

.NET 8

Authentication

None for the practice API

API Endpoint

/api/products

3. Install EF Core Packages

Using Package Manager Console or the terminal:

dotnet add package Microsoft.EntityFrameworkCore.SqlServer --version 8.*

4. Create Product Entity

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
}

5. Create DbContext

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options)
        : base(options)
    {
    }

    public DbSet<Product> Products => Set<Product>();
}

6. Register DbContext

In Program.cs:

builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(
        builder.Configuration.GetConnectionString("DefaultConnection")));

7. Create Products Controller

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    private readonly AppDbContext _context;

    public ProductsController(AppDbContext context)
    {
        _context = context;
    }

    [HttpGet]
    public async Task<IActionResult> Get()
    {
        var products = await _context.Products
            .AsNoTracking()
            .ToListAsync();

        return Ok(products);
    }
}

8. Test Locally

Run the API and open:

https://localhost:<api-port>/api/products

Expected JSON after configuring the database:

[
  { "id": 1, "name": "Laptop" },
  { "id": 2, "name": "Mouse" },
  { "id": 3, "name": "Keyboard" }
]

These code snippets provide a minimal reproducible implementation. They are not claimed to be byte-for-byte copies of the original source files.

 

Part 3: Azure Resource Group and Networking

Step 3: Create Resource Group

1. Navigate

Azure Portal → Resource groups → Create

2. Configure

Setting

Value

Subscription

Your Azure subscription

Resource group

rg-jntech-lab

Region

Central India

3. Create

Click:

Review + create → Create

4. Verify

Open:

Resource groups → rg-jntech-lab

Confirm the resource group exists.

 

Step 4: Create Azure Virtual Network

1. Navigate

Azure Portal → Virtual networks → Create

2. Configure Basics

Setting

Value

Resource group

rg-jntech-lab

Name

vnet-jntech

Region

Central India

3. Configure IP Addresses

Set:

Setting

Value

IPv4 address space

10.0.0.0/16

4. Create App Service Subnet

Click Add a subnet.

Setting

Value

Name

snet-jntech-appservice

Address range

10.0.1.0/26

Delegation

Microsoft.Web/serverFarms

This subnet is used for App Service regional VNet Integration.

5. Create VMSS Subnet

Setting

Value

Name

snet-jntech-vmss

Address range

10.0.2.0/24

Purpose

API VMs and internal load balancer

The VMSS subnet was configured for private VM networking.

6. Create Database Subnet

Setting

Value

Name

snet-jntech-db

Address range

10.0.3.0/27

Purpose

Azure SQL private endpoint

7. Create VNet

Click:

Review + create → Create

8. Verify

Navigate to:

Virtual networks → vnet-jntech → Subnets

Confirm all three subnets.

 

Step 5: Create Network Security Groups

We created two NSGs to control network traffic.

1. Create VMSS NSG

Navigate to:

Azure Portal → Network security groups → Create

Configure:

Setting

Value

Resource group

rg-jntech-lab

Name

nsg-jntech-vmss

Region

Central India

Click Create.

2. Create Database NSG

Repeat:

Setting

Value

Resource group

rg-jntech-lab

Name

nsg-jntech-db

Region

Central India

3. Associate VMSS NSG

Navigate to:

nsg-jntech-vmss → Subnets → Associate

Configure:

Setting

Value

Virtual network

vnet-jntech

Subnet

snet-jntech-vmss

Click OK.

4. Associate Database NSG

Navigate to:

nsg-jntech-db → Subnets → Associate

Configure:

Setting

Value

Virtual network

vnet-jntech

Subnet

snet-jntech-db

Click OK.

For the database subnet, private endpoint network policies must be configured appropriately if you intend the NSG to filter private endpoint traffic.

5. Configure Required VMSS Traffic

The API VMs must accept HTTP traffic from the internal application network.

A suitable lab rule would be:

Setting

Value

Source

App Service integration subnet 10.0.1.0/26

Destination

API VM subnet or VM interfaces

Protocol

TCP

Destination port

80

Action

Allow

The load balancer health probes also need to be permitted. Azure's default AllowAzureLoadBalancerInBound rule may satisfy that requirement unless overridden by higher-priority deny rules.

Important: This is a recommended rule pattern. The exact original NSG rule priorities were not preserved in our conversation.

6. Verify

Navigate to:

nsg-jntech-vmss → Inbound security rules

Check that the API traffic and health probes are allowed.

 

Step 6: Create Application Security Group

We also created an Application Security Group for the API tier.

1. Navigate

Azure Portal → Application security groups → Create

2. Configure

Setting

Value

Resource group

rg-jntech-lab

Name

asg-jntech-api

Region

Central India

Click Review + create → Create.

3. Purpose

An ASG can group API VM network interfaces so that NSG rules can target the API tier without listing individual IP addresses.

4. Actual Status

The ASG was created, but our retained history does not confirm that it was attached to the API VM NICs or used in a security rule.

To check:

Virtual machines → API VM → Networking → Network interface → Application security groups

 

Part 4: Azure SQL Database and Private Endpoint

Step 7: Create Azure SQL Database

1. Navigate

Azure Portal → SQL databases → Create

2. Configure Basics

Setting

Value

Resource group

rg-jntech-lab

Database name

sqldb-jntech

Server

Create or select a SQL logical server

Region

Central India

Workload

Development

The original implementation used a development-oriented SQL configuration. The exact logical server name and pricing configuration should be confirmed from the deployed resource.

3. Configure SQL Authentication

If creating a logical server, select Create new.

Configure:

Setting

Value

Server name

Unique SQL server name

Location

Central India

Authentication

SQL authentication

Administrator login

Your SQL administrator

Password

Secure password

4. Configure Database Compute

Select the development-appropriate compute tier or free offer if eligible.

5. Create

Click:

Review + create → Create

6. Verify

Navigate to:

SQL databases → sqldb-jntech → Overview

Verify:

Status: Online

 

Step 8: Create Products Table

We used a simple Products table containing three records.

1. Connect to SQL

Use SQL Server Management Studio, Azure Data Studio, or another authorized SQL client.

At this stage, SQL connectivity must be available through the configured network path.

2. Create Table

Example minimal schema:

CREATE TABLE dbo.Products
(
    Id INT IDENTITY(1,1) PRIMARY KEY,
    Name NVARCHAR(100) NOT NULL
);

3. Insert Data

INSERT INTO dbo.Products (Name)
VALUES
('Laptop'),
('Mouse'),
('Keyboard');

4. Verify

SELECT * FROM dbo.Products;

Expected product names:

Id

Name

1

Laptop

2

Mouse

3

Keyboard

 

Step 9: Create SQL Private Endpoint

The SQL Database must be accessed privately through the VNet.

1. Navigate

Azure Portal → Private endpoints → Create

2. Configure Basics

Setting

Value

Resource group

rg-jntech-lab

Name

pe-jntech-sql

Region

Central India

3. Configure Resource

Setting

Value

Connection method

Connect to an Azure resource in my directory

Resource type

Microsoft.Sql/servers

Resource

Your Azure SQL logical server

Target sub-resource

sqlServer

4. Configure Networking

Setting

Value

Virtual network

vnet-jntech

Subnet

snet-jntech-db

5. Configure Private DNS

Setting

Value

Private DNS integration

Yes

Private DNS zone

privatelink.database.windows.net

Click Review + create → Create.

6. Verify Private Endpoint

Navigate to:

Private endpoints → pe-jntech-sql → Overview

Confirm that the connection is approved.

7. Verify Private DNS

Navigate to:

Private DNS zones → privatelink.database.windows.net → Virtual network links

Verify vnet-jntech is linked.

 

Step 10: Disable SQL Public Network Access

1. Navigate

Azure Portal → SQL servers → Your SQL logical server → Networking

2. Configure

Under Public access:

Public network access: Disabled

Click Save.

3. Verify From a VM

Once the VM is available, run:

nslookup <sql-server>.database.windows.net

Expected: the SQL server hostname resolves to a private endpoint IP.

Test SQL connectivity:

Test-NetConnection `
    <sql-server>.database.windows.net `
    -Port 1433

Expected:

TcpTestSucceeded : True

This confirms TCP connectivity. SQL authentication and querying must be tested separately.

 

Part 5: Azure Key Vault and Managed Identity

Step 11: Create Azure Key Vault

We used Key Vault to store the SQL connection string.

1. Navigate

Azure Portal → Key vaults → Create

2. Configure Basics

Setting

Value

Resource group

rg-jntech-lab

Key Vault name

kv-jntech-lab

Region

Central India

3. Configure Permission Model

Use Azure role-based access control for Key Vault permissions.

Click Review + create → Create.

4. Verify

Navigate to:

Key vaults → kv-jntech-lab → Overview

Vault URI:

https://kv-jntech-lab.vault.azure.net/
 

Step 12: Add SQL Connection String to Key Vault

1. Navigate

Key Vault → kv-jntech-lab → Secrets → Generate/Import

2. Configure Secret

Setting

Value

Upload options

Manual

Name

ConnectionStrings--DefaultConnection

Value

SQL connection string

Enabled

Yes

Click Create.

3. Why Use Double Hyphen?

Azure Key Vault secret names cannot contain colons.

The .NET Key Vault configuration provider converts:

ConnectionStrings--DefaultConnection

into:

ConnectionStrings:DefaultConnection

The application can therefore use:

builder.Configuration.GetConnectionString("DefaultConnection");

4. Verify

Navigate to:

Key Vault → Secrets

Confirm the secret exists.

Do not copy the actual password into application code or documentation.

 

Step 13: Configure Managed Identity

The API uses managed identity to authenticate to Key Vault.

1. Enable Identity on Each VM

After the VMSS instances have been created:

Azure Portal → Virtual machines → API VM → Identity

Under System assigned:

Set Status: On.

Click Save.

Repeat for both API VMs if each uses its own system-assigned identity.

2. Assign Key Vault Role

Navigate to:

Key Vault → kv-jntech-lab → Access control (IAM) → Add role assignment

Configure:

Setting

Value

Role

Key Vault Secrets User

Assign access to

Managed identity

Members

API VM managed identity

Click Review + assign.

Repeat for the second identity.

3. Verify

Navigate to:

Key Vault → Access control (IAM) → Role assignments

Confirm that both API VM identities have secret-reading access.

4. Key Point

Managed Identity removes the need to store Azure client secrets in the API.

It does not automatically remove the SQL username and password if the connection string itself uses SQL authentication.

 

Part 6: Azure NAT Gateway

Step 14: Create NAT Gateway Public IP

1. Navigate

Azure Portal → Public IP addresses → Create

2. Configure

Setting

Value

Resource group

rg-jntech-lab

Name

pip-jntech-nat

Region

Central India

SKU

Standard

Assignment

Static

Click Create.

 

Step 15: Create NAT Gateway

1. Navigate

Azure Portal → NAT gateways → Create

2. Configure Basics

Setting

Value

Resource group

rg-jntech-lab

Name

nat-jntech

Region

Central India

3. Configure Outbound IP

Select:

pip-jntech-nat

4. Associate Subnet

Setting

Value

Virtual network

vnet-jntech

Subnet

snet-jntech-vmss

Click Review + create → Create.

5. Verify

Navigate to:

NAT gateways → nat-jntech → Subnets

Verify:

snet-jntech-vmss

6. Purpose

NAT Gateway allows the private API VMs to initiate outbound internet connections.

It does not provide inbound access from the internet.

 

Part 7: Azure Virtual Machine Scale Set

Step 16: Create Windows VM Scale Set

We created two Windows Server VMs to host the Web API.

1. Navigate

Azure Portal → Virtual machine scale sets → Create

2. Configure Basics

Setting

Value

Resource group

rg-jntech-lab

VMSS name

vmss-jntech-api

Region

Central India

Orchestration mode

Flexible

Image

Windows Server 2022 Azure Edition

VM size

Standard_D2s_v3

Initial instance count

2

3. Configure Administrator Account

Provide:

  • Administrator username

  • Secure administrator password

Do not use credentials that are hardcoded in deployment scripts.

4. Configure Networking

Setting

Value

Virtual network

vnet-jntech

Subnet

snet-jntech-vmss

Public IP

None

NSG

Existing network security configuration

Load balancing

Existing internal load balancer/backend pool, where configured

The exact original VMSS NIC wizard selections were not retained. In a new deployment, associate the VM network interfaces with be-jntech-api after creating the internal load balancer if that pool is not available during VMSS creation.

5. Create VMSS

Click:

Review + create → Create

6. Verify Instances

Navigate to:

VM Scale Sets → vmss-jntech-api → Instances

Our VM names:

vmss-jntech-api_c32bc018
vmss-jntech-api_c4db76b7

Verify both are running.

7. Verify Private Networking

Open each VM:

Virtual machines → VM → Networking

Confirm:

  • Private IP assigned from 10.0.2.0/24

  • No public IP assigned

  • Correct VNet and subnet

  • Required NSG association

 

Part 8: Azure Bastion

Step 17: Create Azure Bastion Subnet

Bastion requires a dedicated subnet.

1. Navigate

Virtual networks → vnet-jntech → Subnets → Add subnet

2. Configure

Setting

Value

Subnet name

AzureBastionSubnet

Address range

Non-overlapping /26 or larger

The exact original Bastion subnet address was not retained.

Choose an available range within 10.0.0.0/16.

3. Save

Click Add.

 

Step 18: Create Azure Bastion

1. Navigate

Azure Portal → Bastions → Create

2. Configure

Setting

Value

Resource group

rg-jntech-lab

Name

bas-jntech

Region

Central India

Virtual network

vnet-jntech

Subnet

AzureBastionSubnet

Tier

Standard

Public IP

pip-jntech-bastion

3. Enable Native Client

Enable native client support in the Bastion configuration.

This allows Azure CLI to launch an RDP connection.

4. Create

Click:

Review + create → Create

 

Step 19: Connect to Private VM Through Bastion

We used Azure CLI from the local Windows computer.

1. Install Azure CLI

Install Azure CLI if not already available.

2. Sign In

az login

3. Retrieve VM Resource ID

For VM2:

$vmId = az vm show `
    -g rg-jntech-lab `
    -n vmss-jntech-api_c4db76b7 `
    --query id -o tsv

4. Connect Through Bastion

az network bastion rdp `
    -n bas-jntech `
    -g rg-jntech-lab `
    --target-resource-id $vmId

5. Verify

The RDP client opens and connects to the private VM.

Use the appropriate Windows administrator credentials.

6. Repeat for VM1

Change the VM name:

$vmId = az vm show `
    -g rg-jntech-lab `
    -n vmss-jntech-api_c32bc018 `
    --query id -o tsv

Then run the same Bastion RDP command.

 

Part 9: IIS and ASP.NET Core Web API Deployment

Step 20: Install IIS on Both API VMs

Repeat this process on both VMs.

1. Connect Through Bastion

Open the VM using the Bastion RDP command.

2. Open PowerShell as Administrator

Run:

Install-WindowsFeature Web-Server `
    -IncludeManagementTools

3. Verify IIS Installation

Run:

Get-WindowsFeature Web-Server

Expected:

Install State : Installed

4. Open IIS Manager

inetmgr

Verify IIS Manager opens.

 

Step 21: Install ASP.NET Core Hosting Bundle

The Hosting Bundle installs the components required for IIS to host ASP.NET Core applications.

1. Download Hosting Bundle

Download the .NET 8 ASP.NET Core Hosting Bundle from Microsoft's official .NET download site.

2. Install

Run the installer on each VM.

3. Restart IIS

iisreset

4. Verify

Ensure the ASP.NET Core Module is available in IIS.

The .NET runtime and Hosting Bundle versions must be compatible with the published API.

 

Step 22: Publish Web API From Visual Studio

1. Open API Project

In Visual Studio:

Right-click AzurePractice.Api → Publish

2. Select Target

Choose:

Folder

3. Configure

Setting

Value

Configuration

Release

Target framework

.NET 8

Deployment target

Folder

4. Publish

Click Publish.

The output contains:

AzurePractice.Api.dll
web.config
appsettings.json
Other application dependencies

5. Copy Published Files

Copy the complete published output to each API VM.

Destination:

C:\Apps\AzurePractice.Api
 

Step 23: Create IIS Application Pool

1. Open IIS Manager

Run:

inetmgr

2. Navigate

IIS Manager → Application Pools → Add Application Pool

3. Configure

Setting

Value

Name

JntechApiPool

.NET CLR version

No Managed Code

Managed pipeline mode

Integrated

Click OK.

4. Verify

Confirm JntechApiPool appears in Application Pools.

 

Step 24: Create IIS Website

1. Navigate

IIS Manager → Sites → Add Website

2. Configure

Setting

Value

Site name

JntechApi

Application pool

JntechApiPool

Physical path

C:\Apps\AzurePractice.Api

Binding type

HTTP

Port

80

3. Create

Click OK.

Ensure another IIS website is not already using the same IP/port/host-header binding.

4. Verify

Open:

IIS Manager → Sites → JntechApi

Confirm the website is running.

 

Step 25: Configure Key Vault in the API

1. Install NuGet Packages

In the API project:

dotnet add package Azure.Identity
dotnet add package Azure.Extensions.AspNetCore.Configuration.Secrets

2. Configure Program.cs

Add the required namespaces:

using Azure.Identity;
using Microsoft.EntityFrameworkCore;

Configure Key Vault before registering services that read the SQL connection string:

var builder = WebApplication.CreateBuilder(args);

if (builder.Environment.IsProduction())
{
    var vaultUri =
        builder.Configuration["KeyVault:VaultUri"];

    builder.Configuration.AddAzureKeyVault(
        new Uri(vaultUri!),
        new DefaultAzureCredential());
}

builder.Services.AddDbContext<AppDbContext>(options =>
{
    options.UseSqlServer(
        builder.Configuration.GetConnectionString("DefaultConnection"));
});

builder.Services.AddControllers();

var app = builder.Build();

app.MapControllers();

app.Run();

3. Configure IIS Environment Variables

Open:

C:\Apps\AzurePractice.Api\web.config

Inside the existing <aspNetCore> element, add:

<environmentVariables>
  <environmentVariable
    name="ASPNETCORE_ENVIRONMENT"
    value="Production" />

  <environmentVariable
    name="KeyVault__VaultUri"
    value="https://kv-jntech-lab.vault.azure.net/" />
</environmentVariables>

Do not replace the rest of the published web.config; preserve the IIS handler and process configuration.

4. Restart IIS Website

In IIS Manager:

Sites → JntechApi → Restart

5. Verify Key Vault Access

The API should be able to read the SQL connection string using the VM's managed identity.

If it fails, verify:

  • VM system-assigned managed identity is enabled

  • Key Vault Secrets User role is assigned

  • Secret name is correct

  • Vault URI is correct

  • Key Vault network restrictions permit the VM's access

  • API is running in Production

 

Step 26: Test API on Both VMs

1. Connect to VM1

Run:

Invoke-RestMethod http://localhost/api/products

2. Verify Output

Expected product names:

Laptop
Mouse
Keyboard

3. Repeat on VM2

Run the same command.

4. Verify

Both VMs must return the same data from Azure SQL.

This confirms that the API deployment, Key Vault access, and database connectivity are working on both servers.

 

Part 10: Azure Internal Load Balancer

Step 27: Create Internal Load Balancer

1. Navigate

Azure Portal → Load balancers → Create

2. Configure Basics

Setting

Value

Resource group

rg-jntech-lab

Name

lb-jntech-internal

Region

Central India

SKU

Standard

Type

Internal

3. Configure Frontend IP

Setting

Value

Virtual network

vnet-jntech

Subnet

snet-jntech-vmss

IP assignment

Static

Private IP

10.0.2.10

4. Create

Click Review + create → Create, completing the frontend configuration as required by the wizard.

 

Step 28: Configure Backend Pool

1. Navigate

Load balancers → lb-jntech-internal → Backend pools → Add

2. Configure

Setting

Value

Backend pool name

be-jntech-api

Virtual network

vnet-jntech

Backend targets

API VM1 and API VM2

Associate the correct VM network interfaces or IP configurations.

3. Save

Click Save.

4. Verify

The backend pool should contain two VM targets.

 

Step 29: Configure Health Probe

1. Navigate

lb-jntech-internal → Health probes → Add

2. Configure

Setting

Value

Name

hp-jntech-api

Protocol

TCP

Port

80

Click Save.

3. Purpose

The load balancer checks whether the backend VM accepts TCP connections on port 80.

A TCP probe confirms the port is reachable; it does not verify that the /api/products endpoint or SQL database is healthy.

 

Step 30: Configure Load Balancing Rule

1. Navigate

lb-jntech-internal → Load balancing rules → Add

2. Configure

Setting

Value

Name

rule-jntech-api

Frontend IP

10.0.2.10

Protocol

TCP

Frontend port

80

Backend port

80

Backend pool

be-jntech-api

Health probe

hp-jntech-api

Click Save.

3. Verify Backend Health

Navigate to:

Load Balancer → Backend pools → be-jntech-api

Both API VMs were confirmed healthy in our lab.

 

Step 31: Test Internal Load Balancer

1. Connect to an API VM

Use Bastion.

2. Execute

Invoke-RestMethod http://10.0.2.10/api/products

3. Expected Result

The API returns the three products.

4. Actual Result

We successfully tested the endpoint through the internal load balancer.

This confirmed that the internal load balancer could forward requests to the private API tier and return SQL data.

 

Part 11: Azure App Service for MVC

Step 32: Create App Service Plan

1. Navigate

Azure Portal → App Service plans → Create

2. Configure

Setting

Value

Resource group

rg-jntech-lab

Name

asp-jntech-mvc

Region

Central India

Operating system

Windows

Pricing tier

Basic B1

Click Review + create → Create.

 

Step 33: Create MVC App Service

1. Navigate

Azure Portal → App Services → Create → Web App

2. Configure Basics

Setting

Value

Resource group

rg-jntech-lab

Name

app-jntech-mvc

Publish

Code

Runtime stack

.NET 8

Operating system

Windows

Region

Central India

App Service plan

asp-jntech-mvc

3. Create

Click:

Review + create → Create

4. Verify

Open:

App Services → app-jntech-mvc → Overview

The actual default hostname is:

app-jntech-mvc-gbbzazh9b0hdgzen.centralindia-01.azurewebsites.net

 

Step 34: Configure App Service VNet Integration

This is the networking connection that allows MVC to call the private API.

1. Navigate

Azure Portal → App Services → app-jntech-mvc → Networking

2. Locate VNet Integration

Under Outbound traffic configuration, find Virtual network integration.

Click Add virtual network integration.

3. Configure

Setting

Value

Virtual network

vnet-jntech

Subnet

snet-jntech-appservice

Subnet address range

10.0.1.0/26

Click Connect.

4. Verify

Property

Expected

Status

Connected

Virtual network

vnet-jntech

Subnet

snet-jntech-appservice

5. Important Concept

App Service VNet Integration provides outbound private-network connectivity.

It does not make the MVC App Service private. The MVC website remains publicly accessible through its App Service hostname.

 

Step 35: Configure MVC API Base URL

1. Navigate

App Services → app-jntech-mvc → Settings → Environment variables

2. Add Application Setting

Setting

Value

Name

ApiSettings__BaseUrl

Value

http://10.0.2.10/

Click Apply → Save.

3. Why This Address?

10.0.2.10 is the private frontend IP of the internal load balancer.

MVC calls the load balancer instead of calling either API VM directly.

4. Verify

The application configuration should resolve:

builder.Configuration["ApiSettings:BaseUrl"]

to:

http://10.0.2.10/
 

Step 36: Publish MVC From Visual Studio

1. Open MVC Project

In Visual Studio:

Right-click MVC project → Publish

2. Select Target

Choose:

Azure → Azure App Service (Windows)

3. Select Existing App Service

Select:

app-jntech-mvc

4. Configure

Setting

Value

Configuration

Release

Target

Existing Azure App Service

Runtime

.NET 8

5. Publish

Click Publish.

6. Verify

Open the App Service default hostname.

The Products page should display the data returned by the API.

7. Actual Result

We successfully published the MVC application and confirmed that the product list was displayed.

The working path was:

MVC App Service
      |
      v
VNet Integration
      |
      v
Internal LB: 10.0.2.10
      |
      v
API VMSS
      |
      v
Azure SQL Database
 

Step 37: Scale Out MVC App Service

We configured two MVC instances.

1. Navigate

App Services → app-jntech-mvc → Scale out (App Service plan)

2. Configure

Setting

Value

Plan

asp-jntech-mvc

Instance count

2

Click Save.

3. Verify

Confirm the App Service plan has two instances.

4. Key Point

Azure App Service automatically handles routing across the MVC workers.

The internal load balancer is used separately for the API VMs.

 

Part 12: Azure Public DNS and Custom Domain

Step 38: Create Azure DNS Zone

We planned to use:

syed.jntech.in

1. Navigate

Azure Portal → DNS zones → Create

2. Configure

Setting

Value

Resource group

rg-jntech-lab

DNS zone name

syed.jntech.in

Click Review + create → Create.

3. Verify Azure Name Servers

Azure assigned:

ns1-08.azure-dns.com
ns2-08.azure-dns.net
ns3-08.azure-dns.org
ns4-08.azure-dns.info

These are the authoritative Azure DNS servers for the child zone.

 

Step 39: Delegate Subdomain From Plesk to Azure DNS

The parent domain jntech.in remains hosted with the existing DNS provider.

1. Open Plesk

Navigate to:

Plesk → Websites & Domains → jntech.in → DNS Settings

2. Create NS Records

Add four records in the parent zone:

Host

Type

Value

syed.jntech.in

NS

ns1-08.azure-dns.com

syed.jntech.in

NS

ns2-08.azure-dns.net

syed.jntech.in

NS

ns3-08.azure-dns.org

syed.jntech.in

NS

ns4-08.azure-dns.info

3. Save

Click Update in Plesk to apply DNS changes.

4. Verify Parent DNS

Run:

nslookup -type=NS syed.jntech.in ns1.mysecurecloudhost.com

Expected:

ns1-08.azure-dns.com
ns2-08.azure-dns.net
ns3-08.azure-dns.org
ns4-08.azure-dns.info

We verified the four Azure NS records through the hosting provider's four authoritative nameservers.

5. Important

Do not change the parent domain's registrar nameservers.

Only syed.jntech.in is delegated to Azure DNS.

 

Step 40: Configure Azure DNS A Record

1. Find App Service Inbound IP

Navigate to:

App Services → app-jntech-mvc → Properties

The verified inbound IPv4 address was:

20.192.170.9

2. Open Azure DNS

DNS zones → syed.jntech.in → Record sets → Add

3. Configure A Record

Setting

Value

Name

@

Type

A

IP address

20.192.170.9

Click Add.

4. Verify

nslookup -type=A syed.jntech.in ns1-08.azure-dns.com

Expected:

20.192.170.9

The App Service IP should be rechecked in Properties if the application configuration changes.

 

Step 41: Configure TXT Verification Record

Azure App Service uses a TXT record to verify domain ownership.

1. Open Azure DNS

DNS zones → syed.jntech.in → Record sets → Add

2. Configure TXT Record

Setting

Value

Name

asuid

Type

TXT

TTL

300 seconds

Value

App Service verification ID

The verification ID was:

44F9F7F6229B4E5C4DA6414423755CB5DEA0435D16C3AC74439823B5EE2DD118

Click Add.

3. Verify Azure Authoritative DNS

Run:

nslookup -type=TXT asuid.syed.jntech.in ns1-08.azure-dns.com

Repeat for:

nslookup -type=TXT asuid.syed.jntech.in ns2-08.azure-dns.net
nslookup -type=TXT asuid.syed.jntech.in ns3-08.azure-dns.org
nslookup -type=TXT asuid.syed.jntech.in ns4-08.azure-dns.info

4. Actual Result

All four Azure authoritative DNS servers returned the correct TXT value.

 

Step 42: Validate Custom Domain in App Service

1. Navigate

Azure Portal → App Services → app-jntech-mvc → Custom domains

2. Add Domain

Click Add custom domain.

Enter:

syed.jntech.in

Select A record mapping.

3. Validate

Click Validate.

4. Actual Result

Validation failed because public DNS resolvers could not find the TXT verification record.

This led to our DNS troubleshooting.

 

Part 13: DNS Troubleshooting Performed

Step 43: Test Google Public DNS

Run:

nslookup -type=TXT asuid.syed.jntech.in 8.8.8.8

Actual result:

Non-existent domain

Google returned NXDOMAIN.

 

Step 44: Test Cloudflare DNS

Run:

nslookup -type=TXT asuid.syed.jntech.in 1.1.1.1

Actual result:

Non-existent domain

Both Google and Cloudflare failed to resolve the TXT record.

 

Step 45: Verify Public NS Delegation

Run:

nslookup -type=NS syed.jntech.in 8.8.8.8

Actual result:

ns1-08.azure-dns.com
ns2-08.azure-dns.net
ns3-08.azure-dns.org
ns4-08.azure-dns.info

This confirmed Google could see the Azure DNS delegation.

 

Step 46: Test Google DNS-over-HTTPS

We opened:

Google DNS TXT lookup

Google returned:

{
  "Status": 3,
  "Authority": [
    {
      "name": "syed.jntech.in.",
      "type": 6,
      "TTL": 1241,
      "data": "ns1-08.azure-dns.com. mailtosyedmohamed.gmail.com. 2026102204 10800 3600 1209600 10800"
    }
  ]
}

Status: 3 means NXDOMAIN.

The authority section contained an SOA record that did not match Azure DNS's authoritative SOA.

 

Step 47: Test With DNSSEC Validation Disabled

We opened:

Google DNS lookup with DNSSEC validation disabled

Actual result:

{
  "Status": 3,
  "CD": true
}

Disabling DNSSEC validation did not resolve the issue.

This made DNSSEC validation a less likely explanation.

 

Step 48: Compare Azure and Plesk SOA Records

1. Query Azure SOA

nslookup -type=SOA syed.jntech.in ns1-08.azure-dns.com

Azure returned:

primary name server = ns1-08.azure-dns.com
responsible mail addr = azuredns-hostmaster.microsoft.com
serial = 1

2. Query Hosting Provider SOA

nslookup -type=SOA syed.jntech.in ns1.mysecurecloudhost.com

The hosting provider returned:

primary name server = ns1-08.azure-dns.com
responsible mail addr = mailtosyedmohamed.gmail.com
serial = 2026102204

3. Comparison

Property

Azure DNS

Hosting Provider

Primary name server

ns1-08.azure-dns.com

ns1-08.azure-dns.com

SOA administrator

azuredns-hostmaster.microsoft.com

mailtosyedmohamed.gmail.com

SOA serial

1

2026102204

The different SOA records revealed that the hosting provider was still serving its own child-zone information.

 

Step 49: Identify Separate Plesk DNS Zone

In Plesk, we opened:

Websites & Domains → DNS Settings for syed.jntech.in

The page showed a separate DNS zone with records including:

  • A records

  • MX record

  • CNAME records

  • SPF TXT record

  • DMARC TXT record

  • Four Azure NS records

It also showed pending unsaved A-record changes.

This confirmed that Plesk had its own DNS configuration for the child subdomain.

 

Step 50: Disable Plesk Local DNS Service

1. Open Plesk DNS Settings

Navigate to:

Plesk → Websites & Domains → syed.jntech.in → DNS Settings

2. Revert Unsaved Changes

Click Revert.

3. Disable Local DNS

Click Disable.

4. Verify

Plesk displayed:

The local DNS service for the DNS zone syed.jntech.in is switched off.

The four Azure name servers remained listed.

5. Important

We disabled DNS only for the child zone.

The parent jntech.in DNS service must remain active.

 

Step 51: Verify Hosting Provider SOA After Disabling DNS

We ran:

Resolve-DnsName -Name syed.jntech.in `
  -Type SOA `
  -Server 13.248.158.180 `
  -DnsOnly `
  -NoRecursion

Actual result:

PrimaryServer: ns1-08.azure-dns.com
NameAdministrator: mailtosyedmohamed.gmail.com
SerialNumber: 2026102205

The provider continued returning an SOA record for the child zone.

We also queried another provider server:

Resolve-DnsName -Name syed.jntech.in `
  -Type SOA `
  -Server 13.248.204.129 `
  -DnsOnly `
  -NoRecursion

It returned the same SOA serial:

2026102205

 

Step 52: Test TXT Directly Against Hosting Provider

We ran:

Resolve-DnsName -Name asuid.syed.jntech.in `
  -Type TXT `
  -Server 13.248.158.180 `
  -DnsOnly `
  -NoRecursion

Actual result:

DNS name does not exist.

This supported the diagnosis that the hosting provider's DNS infrastructure was still serving conflicting child-zone data.

 

Step 53: Required Hosting Provider Correction — Pending

The hosting provider needs to ensure that its authoritative DNS servers delegate syed.jntech.in to Azure DNS without independently serving the child zone.

The required configuration is:

Parent Zone: jntech.in
       |
       | NS delegation for syed
       v
Azure DNS Zone: syed.jntech.in
       |
       +-- A record: 20.192.170.9
       |
       +-- TXT record: asuid

The four parent-zone NS records must remain:

ns1-08.azure-dns.com
ns2-08.azure-dns.net
ns3-08.azure-dns.org
ns4-08.azure-dns.info

Current status: This provider-side DNS correction has not been verified as completed.

 

Part 14: Finish Custom Domain and HTTPS

The following steps remain pending.

Step 54: Verify Public DNS After Provider Correction

1. Check Google DNS

nslookup -type=TXT asuid.syed.jntech.in 8.8.8.8

2. Check Cloudflare DNS

nslookup -type=TXT asuid.syed.jntech.in 1.1.1.1

3. Verify A Record

nslookup -type=A syed.jntech.in 8.8.8.8

Expected IP:

20.192.170.9

4. Verify TXT

The TXT response must contain the App Service verification ID.

Only proceed when public DNS resolution succeeds.

 

Step 55: Add Custom Domain to App Service

1. Navigate

Azure Portal → App Services → app-jntech-mvc → Custom domains

2. Add Custom Domain

Click Add custom domain.

Configure:

Setting

Value

Domain

syed.jntech.in

DNS mapping

A record

A record IP

20.192.170.9

Verification TXT

asuid.syed.jntech.in

3. Validate

Click Validate.

4. Add Domain

Once validation succeeds, click Add.

5. Verify

Confirm syed.jntech.in appears in the custom-domain list.

 

Step 56: Create App Service Managed Certificate

1. Navigate

App Services → app-jntech-mvc → Certificates

2. Create Certificate

Select the App Service Managed Certificate option.

Choose:

syed.jntech.in

3. Create

Complete certificate issuance after Azure validates the domain and the hostname meets managed-certificate requirements.

 

Step 57: Configure TLS Binding

1. Navigate

App Services → app-jntech-mvc → Custom domains

2. Configure Binding

For syed.jntech.in, select the issued certificate.

Setting

Value

Hostname

syed.jntech.in

Certificate

App Service Managed Certificate

TLS/SSL type

SNI SSL

Save the binding.

 

Step 58: Enable HTTPS Only

1. Navigate

App Services → app-jntech-mvc → Configuration / General settings

2. Configure

Set:

HTTPS Only: On

Save.

3. Verify

Open:

https://syed.jntech.in

Expected: the MVC website loads with a valid HTTPS certificate.

 

Part 15: End-to-End Testing

Step 59: Verify API and SQL

Connect to an API VM using Bastion.

Run:

Invoke-RestMethod http://localhost/api/products

Expected product names:

Laptop
Mouse
Keyboard

This confirms the local API can retrieve SQL data.

 

Step 60: Verify Internal Load Balancer

Run:

Invoke-RestMethod http://10.0.2.10/api/products

Expected: the same products.

This confirms the internal load-balancing path.

 

Step 61: Verify MVC App Service

Open the App Service default URL:

Open MVC App Service

Verify the Products page displays the SQL data.

This test succeeded in our lab.

 

Step 62: Verify Custom Domain

After DNS correction and HTTPS binding, open:

https://syed.jntech.in

Verify that the MVC Products page loads successfully.

This test is still pending.

 

Part 16: Important Azure Interview Concepts

Question

Answer

Why use App Service for MVC?

Managed hosting, deployment, scaling, and public HTTPS access.

Why use VMSS for the API?

Multiple VM instances provide capacity and resilience when properly configured.

Why use an internal load balancer?

Distributes API traffic without exposing a public load-balancer frontend.

Why use VNet Integration?

Allows App Service to reach private resources inside the VNet.

Why use a private endpoint for SQL?

Enables private network access to Azure SQL using a private IP.

Why use Private DNS?

Resolves the SQL server hostname to its private endpoint address inside the VNet.

Why use NAT Gateway?

Provides outbound internet connectivity for private VMs.

Why use Bastion?

Enables secure RDP access without assigning public IPs to VMs.

Why use Key Vault?

Centralizes sensitive application secrets.

Why use Managed Identity?

Allows Azure resources to authenticate without storing Azure client credentials.

Why use NSGs?

Controls allowed inbound and outbound network traffic.

Why use an ASG?

Groups VM network interfaces for simpler NSG rule management.

Why use Azure DNS?

Hosts public DNS records for the delegated custom subdomain.

Why use a managed certificate?

Provides Azure-managed TLS certificate issuance and renewal for eligible App Service hostnames.

 

Part 17: Final Implementation Status

Component

Status

Resource Group

Completed

VNet and three application subnets

Completed

NSGs

Created

Application Security Group

Created; attachment not confirmed

Azure SQL Database

Completed

Products table and sample data

Completed

SQL Private Endpoint

Completed

SQL Private DNS

Completed

SQL Public Network Access

Disabled

Azure Key Vault

Completed

VM Managed Identity

Configured

Key Vault Secrets User RBAC

Configured

NAT Gateway

Completed

VMSS with two Windows VMs

Completed

Azure Bastion

Completed

IIS API on VM1

Completed

IIS API on VM2

Completed

Internal Load Balancer

Completed

Two healthy API backends

Verified

MVC App Service

Completed

App Service VNet Integration

Completed

MVC API Base URL

Configured

MVC application deployment

Completed

MVC scale-out to two instances

Completed

MVC → API → SQL data retrieval

Verified

Azure Public DNS zone

Created

Azure DNS A and TXT records

Configured

Parent-zone NS delegation

Verified

Plesk local child DNS

Disabled

Hosting-provider DNS conflict

Unresolved

App Service custom-domain binding

Pending

Managed HTTPS certificate

Pending

HTTPS custom-domain verification

Pending

Final Architecture

PUBLIC INTERNET
      |
      v
syed.jntech.in
      |
      v
Azure App Service
ASP.NET Core MVC
2 Instances
      |
      | VNet Integration
      v
vnet-jntech
10.0.0.0/16
      |
      v
Internal Load Balancer
10.0.2.10:80
      |
      +--------------------+
      |                    |
      v                    v
API VM1                 API VM2
Windows IIS             Windows IIS
.NET 8                  .NET 8
      |                    |
      +---------+----------+
                |
                v
         Azure SQL DB
         Private Endpoint
                |
                v
           Products Table

Implementation outcome: The core three-tier application is working: the public MVC App Service retrieves data from Azure SQL through VNet Integration, the internal load balancer, and the private API VMSS. The remaining work is to resolve the hosting-provider DNS conflict, validate syed.jntech.in, and enable HTTPS for the custom domain.

One additional enhancement discussed but not yet implemented is displaying the MVC App Service instance ID and API VM machine name on the Products page to observe request distribution between the two tiers.

← Back to Articles