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
-
The user opens the MVC website through Azure App Service.
-
App Service distributes requests across two MVC instances.
-
MVC calls
http://10.0.2.10/api/products. -
VNet Integration routes the request into the Azure virtual network.
-
The internal load balancer forwards the request to a healthy API VM.
-
The ASP.NET Core API running under IIS reads its SQL connection string from Azure Key Vault.
-
The API connects to Azure SQL through the private endpoint.
-
SQL returns the product data.
-
The API returns JSON to MVC.
-
MVC displays the products to the user.
4. Complete Resource Inventory
|
Resource Type |
Resource Name / Configuration |
|---|---|
|
Resource Group |
|
|
Region |
Central India |
|
Virtual Network |
|
|
VNet Address Space |
|
|
App Service Subnet |
|
|
VMSS Subnet |
|
|
Database Subnet |
|
|
VMSS NSG |
|
|
Database NSG |
|
|
Application Security Group |
|
|
Azure SQL Database |
|
|
SQL Private Endpoint |
|
|
SQL Private DNS Zone |
|
|
Key Vault |
|
|
NAT Gateway |
|
|
NAT Public IP |
|
|
VM Scale Set |
|
|
VMSS Orchestration |
Flexible |
|
VM Image |
Windows Server 2022 Azure Edition |
|
VM Size |
|
|
VM Instances |
2 |
|
Azure Bastion |
|
|
Bastion Tier |
Standard |
|
Bastion Public IP |
|
|
Internal Load Balancer |
|
|
Internal LB IP |
|
|
LB Backend Pool |
|
|
LB Health Probe |
|
|
LB Rule |
|
|
App Service Plan |
|
|
App Service Tier |
Basic B1 |
|
MVC App Service |
|
|
MVC Instances |
2 |
|
Azure DNS Zone |
|
|
Verified App Service Inbound IP |
|
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 |
|
|
Framework |
.NET 8 |
|
Authentication |
None for the practice API |
|
API Endpoint |
|
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 |
|
|
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 |
|
|
Name |
|
|
Region |
Central India |
3. Configure IP Addresses
Set:
|
Setting |
Value |
|---|---|
|
IPv4 address space |
|
4. Create App Service Subnet
Click Add a subnet.
|
Setting |
Value |
|---|---|
|
Name |
|
|
Address range |
|
|
Delegation |
|
This subnet is used for App Service regional VNet Integration.
5. Create VMSS Subnet
|
Setting |
Value |
|---|---|
|
Name |
|
|
Address range |
|
|
Purpose |
API VMs and internal load balancer |
The VMSS subnet was configured for private VM networking.
6. Create Database Subnet
|
Setting |
Value |
|---|---|
|
Name |
|
|
Address range |
|
|
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 |
|
|
Name |
|
|
Region |
Central India |
Click Create.
2. Create Database NSG
Repeat:
|
Setting |
Value |
|---|---|
|
Resource group |
|
|
Name |
|
|
Region |
Central India |
3. Associate VMSS NSG
Navigate to:
nsg-jntech-vmss → Subnets → Associate
Configure:
|
Setting |
Value |
|---|---|
|
Virtual network |
|
|
Subnet |
|
Click OK.
4. Associate Database NSG
Navigate to:
nsg-jntech-db → Subnets → Associate
Configure:
|
Setting |
Value |
|---|---|
|
Virtual network |
|
|
Subnet |
|
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 |
|
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 |
|
|
Name |
|
|
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 |
|
|
Database name |
|
|
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 |
|
|
Name |
|
|
Region |
Central India |
3. Configure Resource
|
Setting |
Value |
|---|---|
|
Connection method |
Connect to an Azure resource in my directory |
|
Resource type |
|
|
Resource |
Your Azure SQL logical server |
|
Target sub-resource |
|
4. Configure Networking
|
Setting |
Value |
|---|---|
|
Virtual network |
|
|
Subnet |
|
5. Configure Private DNS
|
Setting |
Value |
|---|---|
|
Private DNS integration |
Yes |
|
Private DNS zone |
|
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 |
|
|
Key Vault name |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
Name |
|
|
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 |
|
|
Name |
|
|
Region |
Central India |
3. Configure Outbound IP
Select:
pip-jntech-nat
4. Associate Subnet
|
Setting |
Value |
|---|---|
|
Virtual network |
|
|
Subnet |
|
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 |
|
|
VMSS name |
|
|
Region |
Central India |
|
Orchestration mode |
Flexible |
|
Image |
Windows Server 2022 Azure Edition |
|
VM size |
|
|
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 |
|
|
Subnet |
|
|
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 |
|
|
Address range |
Non-overlapping |
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 |
|
|
Name |
|
|
Region |
Central India |
|
Virtual network |
|
|
Subnet |
|
|
Tier |
Standard |
|
Public IP |
|
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 |
|
|
.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 |
|
|
Application pool |
|
|
Physical path |
|
|
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 |
|
|
Name |
|
|
Region |
Central India |
|
SKU |
Standard |
|
Type |
Internal |
3. Configure Frontend IP
|
Setting |
Value |
|---|---|
|
Virtual network |
|
|
Subnet |
|
|
IP assignment |
Static |
|
Private IP |
|
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 |
|
|
Virtual network |
|
|
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 |
|
|
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 |
|
|
Frontend IP |
|
|
Protocol |
TCP |
|
Frontend port |
80 |
|
Backend port |
80 |
|
Backend pool |
|
|
Health probe |
|
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 |
|
|
Name |
|
|
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 |
|
|
Name |
|
|
Publish |
Code |
|
Runtime stack |
.NET 8 |
|
Operating system |
Windows |
|
Region |
Central India |
|
App Service plan |
|
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 |
|
|
Subnet |
|
|
Subnet address range |
|
Click Connect.
4. Verify
|
Property |
Expected |
|---|---|
|
Status |
Connected |
|
Virtual network |
|
|
Subnet |
|
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 |
|
|
Value |
|
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 |
|
|
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 |
|
|
DNS zone name |
|
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 |
|---|---|---|
|
|
NS |
|
|
|
NS |
|
|
|
NS |
|
|
|
NS |
|
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 |
|
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 |
|
|
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 lookupGoogle 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 disabledActual 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 |
|
|
|
SOA administrator |
|
|
|
SOA serial |
|
|
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 |
|
|
DNS mapping |
A record |
|
A record IP |
|
|
Verification TXT |
|
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 |
|
|
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 ServiceVerify 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.