Platform
Azure Secure Networking and Highly Available Web Application Design
A cloud operations note on Azure VNet, NSG, Bastion, VMSS, Traffic Manager, Load Balancer, and secure high-availability design.

The most confusing part when first dealing with Azure was not the fact that there were many resources, but rather what position each resource occupied in the traffic path. VNet, Subnet, NSG, Bastion, Application Gateway, Load Balancer, VMSS, and App Service can be created separately, but in actual services, they operate together within one request path.
So, rather than arranging Azure services like a list, this article organizes them according to the path through which external users enter, the path through which operators connect, the path through which the backend is protected, and the path through which expansion occurs in the event of a failure or load. Some of the names and address bands used in practice were also left behind. What is more important than the resource name itself is what traffic is allowed and blocked at what boundary.
Network flows vary starting from address design
VNet is a logical network created within Azure. It can be thought of as close to a VLAN or private network in an on-premises network, but in the cloud, components such as subnets, NICs, private IPs, public IPs, DNS, gateways, and load balancers are attached within this VNet.
For example, in the practice, vnet-krce was created in the 10.0.0.0/16 band, and snet-krce-01 was divided into 10.0.1.0/24. In other environments, subnets were divided by role within vnet-hallofarmor-us, such as snet-jarvis2-gw (10.16.1.0/24) for Application Gateway, 10.16.2.0/24 for front-end, and snet-jarvis2-be (10.16.3.0/24) for back-end.
The reason for dividing the address band is not to organize it nicely. This is because when adding NSG, Route Table, Bastion, NAT Gateway, and Load Balancer later, “which layer is exposed to the outside” and “which layer only allows internal communication” are determined on a subnet basis.
vnet-hallofarmor-us: 10.16.0.0/16
10.16.1.0/24 snet-jarvis2-gw Application Gateway
10.16.2.0/24 front-end subnet Web VM / Web VMSS
10.16.3.0/24 snet-jarvis2-be API / Backend VM / Internal LBIn an operational environment, it is difficult to design a perfect address from the beginning. Still, it is better to at least separate the Gateway, Web/API, Data, and Management areas. This is because even if you rent a simple web server at first, the cost of splitting the network again increases when expanding to configurations such as VMSS, Private Endpoint, Database, and AKS.
NSG is an intent logger, not a firewall rule.
NSG (Network Security Group) is attached to a subnet or NIC to control inbound and outbound traffic. Rules are evaluated starting from the lowest priority number, and the priority range is from 100 to 4096. Rules include Source, Source Port, Destination, Destination Port, Protocol, and Action.
Ground rules are also important. Azure basically allows internal VNet communication, allows health checks from Azure Load Balancer, and blocks the remaining inbound. For outbound, many configurations allow VNet and Internet directions by default.
Inbound default rules
- AllowVNetInBound
- AllowAzureLoadBalancerInBound
- DenyAllInbound
Outbound default rules
- AllowVNetOutBound
- AllowInternetOutBound
- DenyAllOutboundIn the practice stage, it is easy to quickly open SSH or HTTP to check, but when I reorganized it while writing, the key was not “opened the port,” but “to whom it was opened.” For example, VMs behind Application Gateway don't need to receive HTTP directly from all of the internet. You only need to allow traffic coming from the front end, such as Application Gateway or Front Door.
In Azure, you can use Service Tags and Application Security Groups (ASGs) to make rules more readable. Using Service Tags such as Internet, VirtualNetwork, AzureLoadBalancer, AppGateway, and SQL reduces the burden of directly managing the IP band. ASG can express rules by grouping VMs by application role, allowing management to be closer to a statement such as “Allow 443 from web tier to API tier” rather than an IP address.
Rule: allow-app-gateway-to-web
Priority: 200
Source: AppGateway
Destination: web-asg
Destination port: 80
Protocol: TCP
Action: Allow
Rule: deny-direct-web-from-internet
Priority: 300
Source: Internet
Destination: web-asg
Destination port: 80
Protocol: TCP
Action: DenyIt is also easy to make a mistake because NSG can be attached to both the subnet and the NIC. If one side allows it but the other side blocks it, communication will fail. Therefore, when looking at failures, you should check the NIC, Subnet, Route Table, Load Balancer health probe, and NSG Flow Logs rather than the VM itself.
AzureDiagnostics
| where Category contains "NetworkSecurityGroup"
| project TimeGenerated, Resource, OperationName, msg_s
| order by TimeGenerated desc
| take 20Separate management connections from service traffic
In the initial practice, we placed the Bastion Host VM in the Public Subnet and configured a method of connecting SSH to the VM in the Private Subnet. Create public-subnet and private-subnet within bastion-vnet, bastion-host has a public IP, and protected-resource has only a private IP without a public IP.
scp -i ./bastion-host_key.pem ./protected-resource_key.pem \
azureuser@4.205.181.27:/home/azureuser
ssh -i ./bastion-host_key.pem azureuser@4.205.181.27
chmod 400 ./protected-resource_key.pem
ssh -i ./protected-resource_key.pem azureuser@10.0.1.4This method is easy to understand the structure, but in the operating environment, the flow of copying the private key to the Bastion Host is burdensome. Azure Bastion is a managed connectivity service to reduce this problem. Provides browser-based RDP/SSH access without attaching a public IP to the VM. The conditions are clear. A dedicated subnet named AzureBastionSubnet is required within the VNet and must have a minimum size of /27. Public IP uses Standard SKU.
az network bastion create \
--name bastion-krce \
--resource-group rg-camp1-krce \
--vnet-name vnet-krce \
--public-ip-address pip-bastion-krce \
--location koreacentralIf you separate management access to Bastion, you can eliminate the VM's public IP. This difference may seem small, but it changes the security model. It is no longer a matter of “how far to open the SSH port,” but “who can access which VM through the Azure portal and Bastion” is managed with Entra ID, RBAC, NSG, and logs.
VM configuration should proceed with hardening verification rather than connection verification.
In Linux VM, we installed LAMP or NGINX based on Ubuntu 24.04 LTS to check web response. Rather than simply checking whether port 80 was open, we also checked the log location and access failures.
sudo su
sudo apt update -y && sudo apt upgrade -y
sudo apt install lamp-server^ -y
tail -n 10 /var/log/apache2/access.log
tail -n 10 /var/log/apache2/error.logThe NGINX-based VM is configured to provide simple HTML as follows.
sudo apt-get -y update
sudo apt-get -y install nginx
sudo sh -c 'echo "<html><body><h1>Ubuntu web node</h1></body></html>" > /var/www/html/index.html'Security tests also checked Fail2Ban and AppArmor. Fail2Ban is a tool that detects and blocks repetitive abnormal requests based on logs, and AppArmor limits the range of files that a process can access. For example, when Apache tries to read a file like /etc/passwd, you can see a rejection event in the kernel log.
sudo apt install fail2ban -y
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo systemctl restart fail2ban
sudo systemctl enable fail2ban
sudo fail2ban-client status
curl -A "EmailCollector" http://<VM_PUBLIC_IP>sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.apache2
sudo aa-status
journalctl -k | grep DENIEDOn a Windows VM, I installed IIS on Windows Server 2019 or 2022 and verified automatic startup of services and basic web page responses.
Install-WindowsFeature -Name Web-Server -IncludeManagementTools
Set-Service -Name W3SVC -StartupType AutomaticDuring this process, items such as Directory Browsing, Windows Authentication, Windows Defender testing, and IPBan were also checked. These are small security settings if you look at them one by one, but in web server operation, there is a difference between “the server responds” and “only responds in ways that are allowed to respond.”
Application Gateway and internal Load Balancer have different roles
Even though the word load balancing is the same, Application Gateway and Load Balancer have different locations and layers. Application Gateway is an L7 entry point that understands HTTP/HTTPS. Features such as path-based routing, host-based routing, and WAF can be added. On the other hand, Azure Load Balancer distributes TCP/UDP traffic based on L4.
In the lab, an Application Gateway called agw-jarvis2fe was created. The region was set to East US 2, the tier was set to Standard V2, and the automatic size adjustment was set to a minimum of 2 and a maximum of 10. The gateway dedicated subnet was snet-jarvis2-gw (10.16.1.0/24), and the frontend used the public IP pip-agw-jarvis2fe. vmjarvis2fe01 (10.16.2.4) and vmjarvis2fe02 (10.16.2.5) were added to the backend pool.
Application Gateway
- name: agw-jarvis2fe
- tier: Standard V2
- subnet: snet-jarvis2-gw (10.16.1.0/24)
- frontend: public IPv4, pip-agw-jarvis2fe
- backend pool: vmjarvis2fe01, vmjarvis2fe02
- listener: HTTP :80
- rule priority: 10
- backend setting: HTTP :80An internal Load Balancer was installed separately in the backend area. lbi-jarvis2be is an internal Load Balancer of Standard SKU, and the front-end IP is fixed to 10.16.3.99. vmjarvis2be01 (10.16.3.4) and vmjarvis2be02 (10.16.3.5) were added to the backend pool, and a health probe was performed on the HTTP / path at 5-second intervals.
Internal Load Balancer
- name: lbi-jarvis2be
- frontend IP: 10.16.3.99
- backend pool: vmjarvis2be01, vmjarvis2be02
- probe: HTTP /, port 80, interval 5s
- rule: TCP 80 -> 80In this structure, Application Gateway serves to receive external requests to the web layer, and the internal Load Balancer distributes backend services behind the web layer within the private network. NAT Gateway, on the other hand, was used to create a stable outbound route for the backend subnet. It is not a resource that comes directly from the outside to the backend, but is closer to a fixed outbound path that the backend will use when going out for patches or external API calls.
Azure Firewall creates a central control point
Basic traffic control is possible by attaching an NSG to each subnet. However, when you have multiple subnets, multiple VNets, and multiple workloads, you want to be able to observe and control “what traffic is going where” from one place. At this time, review the structure of placing Azure Firewall or NVA (Network Virtual Appliance) on the hub side.
Installing a firewall does not solve all security problems. NSG is still the first line of defense at the subnet or NIC level, and Firewall is closer to the central policy and logging point.
NSG
- narrows the allowed range close to the subnet or NIC
- performs basic filtering around source, destination, and port
Azure Firewall
- manages centralized egress and ingress policy
- separates application rules, network rules, and DNAT rules
- collects logs for audit and analysisTo route through a firewall, a route table must also be designed. For example, if you send the default route 0.0.0.0/0 of a private subnet to the Azure Firewall private IP, outbound traffic from that subnet will pass through the firewall policy.
Route table for private subnet
address prefix: 0.0.0.0/0
next hop type: Virtual appliance
next hop IP: Azure Firewall private IPA common mistake in this configuration is to only change routing and not prepare DNS, log, or exception policies. OS patches, container image pulls, package registry access, and external API calls can all be caught in the firewall policy. So, before operational application, you need to list which FQDNs and ports are needed and narrow down the policy by looking at the blocking log.
VMSS deals with operational policy, not the number of servers
VMSS (Virtual Machine Scale Sets) is a service that bundles, deploys, and expands VMs with the same role. Rather than simply a function to create multiple VMs, it is closer to a unit that manages images, disks, networks, upgrades, and scaling policies as one.
In the front-end VMSS exercise, vmssfridayfe was created in the North Europe region and availability zones 1 and 2 were used. The default number of instances was set to 1, minimum 1, and maximum 5, and a policy was set to increase one instance when CPU exceeds 75% and decrease one instance when it falls below 30%. The public Load Balancer lbe-vmssfriday forwarded TCP 80 to the backend 80, and also created a NAT rule that maps the frontend port range 10000 to the backend 3389 for RDP connection.
Front-end VMSS
- name: vmssfridayfe
- region: North Europe
- zones: 1, 2
- image: Windows Server 2022 Datacenter Azure Edition
- size: Standard_DS1_v2
- subnet: snet-friday-fe (192.168.2.0/24)
- autoscale: min 1, max 5
- scale out: CPU > 75%, +1
- scale in: CPU < 30%, -1
- load balancer: TCP 80 -> 80The backend VMSS was created with vmssfridaybe and the Ubuntu Server 22.04 LTS image was used. The subnet was snet-friday-be (192.168.3.0/24), and no external Load Balancer was attached. It was important to not expose the frontend, which receives external traffic, and the backend, which is only called internally, in the same way.
Back-end VMSS
- name: vmssfridaybe
- image: Ubuntu Server 22.04 LTS
- subnet: snet-friday-be (192.168.3.0/24)
- load balancer: none
- access: private network / BastionWhen configuring a VMSS, you must be as careful with scale-in conditions as you do scale-out conditions. Removing old instances immediately after a brief drop in traffic can impact sessions, log collection, and tasks being deployed. Health check, graceful shutdown, log transmission, and distribution strategy must be considered together to make VMSS an operable structure that goes beyond simple automatic expansion functions.
Traffic Manager does not proxy requests directly
Traffic Manager is a DNS-based traffic distribution service. It is not a resource that directly enters and proxyes the request path like Application Gateway or Load Balancer. Which endpoint the client will go to is guided through the DNS response.
Understanding this difference makes it easier to choose a routing method.
Priority active-passive failover
Weighted canary, blue-green, gradual migration
Performance latency-based routing
Geographic country or region based routing
MultiValue multiple healthy endpoints in DNS response
Subnet source IP range based routingFor example, if you want to send a new version to only a few users, Weighted is suitable. If the structure is to transfer to a secondary region in the event of a failure, priority is simple. If you want to reduce user latency by running the same service in multiple regions, you can review performance. However, because it is DNS-based, it is affected by TTL, client cache, and intermediate DNS cache. Don't expect anything like an L7 proxy to switch instantly.
In multi-region configuration, data boundaries take precedence over traffic switching.
Putting two or more endpoints in front of Traffic Manager seemingly completes the multi-region configuration. However, in actual failure transitions, the parts that are broken first are usually state and data, not routing. You need to decide whether the application is stateless, where sessions are stored, which region the database is primary in, and which storage account file uploads go into.
Deploying two Web Apps in this structure is relatively simple. The difficult point is whether the user can see the same data even when a request that was normally processed in Korea Central is transferred to East US 2. If it is a read-only dashboard, replication delay can be tolerated to some extent, but if it is a flow that requires strong consistency, such as payment or ordering, it is difficult to solve with a simple DNS failover.
In multi-region design, check at least the following items first.
multi-region readiness
1. application runtime is stateless or session storage is externalized
2. database replication, backup, and failover ownership are defined
3. object storage replication or fallback path is prepared
4. Key Vault secrets and application settings exist in each region
5. health probe path checks real dependencies, not only HTTP 200
6. DNS TTL and client-side caching behavior are understood
7. deployment pipeline can release both regions without manual driftNetwork addresses are also allocated in advance in the same way. If CIDRs overlap after the region increases, VNet peering, VPN, and private endpoint design all become complicated.
Korea VNet 10.10.0.0/16
app subnet 10.10.1.0/24
data subnet 10.10.2.0/24
gateway subnet 10.10.255.0/27
East US VNet 10.20.0.0/16
app subnet 10.20.1.0/24
data subnet 10.20.2.0/24
gateway subnet 10.20.255.0/27
On-premises 172.16.0.0/16Health checks are not completed with a simple /health endpoint. Even if the application process is alive, if database connection, external API dependency, queue backlog, or authentication token verification fails, it is a problem for the user. Conversely, if all dependencies are included in the health check, even small external failures can cause the entire region to fail. So, readiness and liveness are separated, and the health probe that Traffic Manager sees should be close to “Can we receive actual user traffic into this region?”
App Service and Storage are not simple deployments outside the network
After configuring a VM-based web server, we also deployed App Service and Storage-based image upload apps. The resource group was created as rg-myManagedPlatform, the storage account as imgstorkty00, and the container as images. The back-end Web App is composed of imgapikty, and the front-end Web App is composed of imgwebkty.
In our initial lab, we opened public access to Storage for a quick check and placed the connection string in the App Service environment variable.
StorageConnectionString=DefaultEndpointsProtocol=https;AccountName=imgstorkty00;AccountKey=...;EndpointSuffix=core.windows.net
APIUrl=https://imgapikty.azurewebsites.net/For deployment, we used the zip deploy of Azure CLI.
az login
az group list -o table
az webapp list --resource-group rg-myManagedPlatform -o table
az webapp deployment source config-zip \
--resource-group rg-myManagedPlatform \
--src api.zip \
--name imgapikty
az webapp deployment source config-zip \
--resource-group rg-myManagedPlatform \
--src web.zip \
--name imgwebktyOn the API side, the blob container was imported and image list retrieval and upload were processed. The code was simple, but from an operational perspective, it was more important to know where to place the connection string, how to limit the upload file size, whether to restrict storage access to public or private endpoints, and how to control App Service's outbound communication.
[HTTPGet]
public async Task<IEnumerable<string>> Get()
{
var container = GetCloudBlobContainer();
var blobs = await container.ListBlobsSegmentedAsync(null);
return blobs.Results.Select(blob => blob.Uri.ToString());
}
[HTTPPost]
public async Task<IActionResult> Post(IFormFile file)
{
var container = GetCloudBlobContainer();
var blob = container.GetBlockBlobReference(file.FileName);
await blob.UploadFromStreamAsync(file.OpenReadStream());
return Ok(blob.Uri.ToString());
}If you separate this configuration from the VM-based structure, it ends up being “deployed to a managed service.” But in reality, you have to ask the same question again. Where do users come in, how far is the API exposed, who can read and write blobs, and which log should be viewed first in case of a failure? Even if you use App Service, the network and permission model do not disappear.
Observation is not a function added at the end
What I often felt while creating a network and high availability configuration was that the starting point for failure analysis was the log and status, not the resource screen. Application Gateway must check backend health, and Load Balancer must check probe status. NSG must view Flow Logs or diagnostic logs, and VM must view /var/log/apache2/access.log, Windows Event Log, and service status.
Azure Firewall or NSG logs can be sent to Log Analytics and checked using KQL.
AzureDiagnostics
| where Category == "AzureFirewallNetworkRule"
| project TimeGenerated, msg_s, action_s, protocol_s
| order by TimeGenerated desc
| take 10Application Gateway or App Service can also leave Access Log, Performance Log, and Firewall Log through diagnostic settings. In an operating system, “where can I check if a problem occurs” is more important than “what was created.”
Cleanup
The biggest change in perspective as I followed Azure's security network and high availability configuration step by step was to no longer view resources as individual products. VNet is a boundary, not an address space, NSG is a communication intent, not a port list, and Bastion is not a connection convenience function but a device that separates management paths.
The same goes for Application Gateway, Internal Load Balancer, VMSS, Traffic Manager, App Service, and Storage. Each can be learned separately, but their meaning becomes clear when placed within a flow where service requests are received, processed, stored, and observed.
Next time I redesign the same structure, I will first draw the request path rather than creating the resource. It is much less shaky to first divide the user entry point, administrator entry point, calls between internal services, data storage access, outbound communication, and log collection paths, and then deploy Azure resources.
Unauthorized copying, redistribution, and automated scraping are not permitted. Please cite the original URL when referencing this article.