If you want to use Azure with a corporate network, you SHOULD have an Azure Landing Zone. An Azure Landing Zone provides the infrastructure for a safe hybrid network between Azure and your network. Well in a test environment (like home) you do not have a very complex network configuration or the need of for example an Azure Firewall. But to create the hybrid connection between your tenant and your home setup it is possible to do it with a s2s vpn. For this landingzone we use Azure BICEP to deploy it.
How to Setup
Well within Azure you need to have:
- ResourceGroups
- LogAnalyticsWorkspace
- Virtual Network
- Public IP Address
- Virtual Network Gateway
- Local network gateway
- VPN Connection
- Private DNS zones (for private endpoints)
Azure Verified Modules
In this codebase, I consistently use Azure Verified Modules (AVM) to deploy Azure resources in a standardized, reliable way.
Azure Verified Modules are Microsoft-supported, community-backed Infrastructure-as-Code modules (for example Bicep modules) that implement Azure resources following established best practices. They are designed to be:
- Reusable: deploy the same building block (e.g., VNet, Storage Account, Key Vault) across projects without rewriting code.
- Consistent: naming, tagging, outputs, diagnostics, and patterns are aligned, making environments predictable.
- Quality-checked: modules follow defined requirements (testing, documentation, versioning, and validation) to reduce deployment errors.
- Secure by design: common security and governance options (RBAC, private endpoints, diagnostics, encryption) are typically built in or supported.
- Easier to maintain: versioned modules enable controlled upgrades, and changes are managed centrally rather than per project.
By using AVMs throughout the code, deployments become more repeatable, auditable, and scalable. Instead of custom one-off templates, AVMs provide proven building blocks, so adding new resources or extending environments is faster and less error-prone, while still keeping governance and best practices intact.
Resource Groups
Resource Groups in Azure organize related resources so they can be managed, secured, and monitored together. They simplify deployments, lifecycle management, and access control by grouping items with the same purpose or environment. They also help with cost tracking, applying policies, and keeping environments clean and consistent. Using Resource Groups makes your Azure environment easier to maintain, automate, and govern as it grows.
Parameters
param resourceGroups = [
{
name: 'rsg-lz-network-01'
location: location
lock: {
kind: 'CanNotDelete'
name: 'rsg-lz-network-01-lockdel'
}
}
{
name: 'rsg-lz-platform-01'
location: location
lock: {
kind: 'CanNotDelete'
name: 'rsg-lz-platform-01-lockdel'
}
}
{
name: 'rsg-lz-arc-01'
location: location
lock: {
kind: 'CanNotDelete'
name: 'rsg-lz-arc-01-lockdel'
}
}
{
name: 'rsg-lz-privateendpoint-01'
location: location
lock: {
kind: 'CanNotDelete'
name: 'rsg-lz-privateendpoint-01-lockdel'
}
}
{
name: 'rsg-lz-privatednszones-01'
location: location
lock: {
kind: 'CanNotDelete'
name: 'rsg-lz-privatednszones-01-lockdel'
}
}
{
name: 'rsg-lz-monitoring-01'
location: location
lock: {
kind: 'CanNotDelete'
name: 'rsg-lz-monitoring-01-lockdel'
}
}
]As shown, the current naming convention does not include any reference to the deployment region. To ensure consistency, clarity, and easier resource identification across multiple Azure regions, the naming standard should be updated to include a region identifier as part of each resource name.
Code
module resourceGroup 'br/public:avm/res/resources/resource-group:0.4.3' = [
for rg in resourceGroups: {
name: 'rg-deploy-${rg.Name}'
params: {
name: rg.Name
location: rg.Location
lock: rg.Lock
}
}
]Some resources must be deployed at a different scope than the subscription-level deployment, often at a specific resource group scope. Therefore, we explicitly pass the required scope to those modules. After deploying the resource groups, we collect resource IDs from multiple groups and use them to correctly scope deployments and reference shared components across the environment.
resource resRsgMonitoring 'Microsoft.Resources/resourceGroups@2025-04-01' existing = {
dependsOn: [resourceGroup]
name: '${prefix}-${region}-rsg-lz-monitoring-01'
}
resource resRsgNetwork 'Microsoft.Resources/resourceGroups@2025-04-01' existing = {
dependsOn: [resourceGroup]
name: '${prefix}-${region}-rsg-lz-network-01'
}
resource resRsgDnszones 'Microsoft.Resources/resourceGroups@2025-04-01' existing = {
dependsOn: [resourceGroup]
name: '${prefix}-${region}-rsg-lz-privatednszones-01'
}LogAnalytics Workspace
A Log Analytics Workspace is a central Azure service used to collect, store, and query log and telemetry data from various Azure resources, applications, and on‑premises systems. It serves as the data backend for Azure Monitor, enabling insights, alerting, troubleshooting, and security analysis. Multiple services, such as Azure Monitor, Defender for Cloud, Sentinel, and Diagnostics, send their data to a workspace, making it the core component for observability and operational monitoring in Azure.
Parameter
param logAnalyticsWorkspaces = [
{
name: 'law-lz-01'
location: location
}
]For a Log Analytics Workspace, only two parameters are required to deploy it: the name and the region. The workspace does not need additional configuration values during creation because Azure automatically applies the default settings for retention, SKU, and diagnostics. More advanced configurations, such as data retention policies, solutions, or diagnostic integrations, can be added afterward, but the minimal deployment requires just these two properties.
Code
module modLogAnalyticsWorkspace 'br/public:avm/res/operational-insights/workspace:0.15.0' = [
for law in logAnalyticsWorkspaces: {
name: 'law-deploy-${law.name}'
dependsOn: [resourceGroup]
params: {
name: law.name
location: law.location
}
scope: resRsgMonitoring
}
]
Virtual Network
In Azure‑based network architectures, it is considered a best practice to implement a hub‑and‑spoke topology. This involves creating a dedicated Virtual Network (VNet) that functions as the central hub, while all other VNets that host workloads or application resources are deployed as spokes.
The hub VNet typically contains shared services, such as firewalls, VPN gateways, ExpressRoute circuits, or connectivity appliances, that provide secure access to on‑premises environments. By routing all external and hybrid connectivity through the hub, you create a single, centralized entry and exit point to your on‑premises network. This significantly simplifies management, improves security governance, and ensures that traffic flows remain predictable and auditable.
Spoke VNets, on the other hand, contain the actual Azure resources such as virtual machines, databases, application services, and platform components. These spokes connect to the hub using VNet peering, which allows for high‑bandwidth, low‑latency communication without exposing them directly to the on‑premises network. As a result, the environment becomes easier to scale, maintain, and secure, while still enabling controlled inter‑network communication where required.
Parameters
param virtualNetworks = [
{
name: 'vnet-hub-01'
addressPrefixes: [
'10.190.0.0/16'
]
subnets: [
{
name: 'default'
addressPrefix: '10.190.1.0/24'
}
{
name: 'GatewaySubnet'
addressPrefix: '10.190.2.0/24'
}
]
}
{
name: 'vnet-spoke-01'
addressPrefixes: [
'10.200.0.0/16'
]
subnets: [
{
name: 'default'
addressPrefix: '10.200.1.0/24'
}
{
name: 'endpoints'
addressPrefix: '10.200.2.0/24'
}
]
}
] When defining configuration values or deployment settings, I consistently place all parameters inside an array. This approach provides a clear and scalable structure that simplifies future expansion. By organizing parameters in this way, it becomes significantly easier to introduce additional resources or configuration items without disrupting the existing structure.
Using an array‑based parameter model also improves maintainability. It ensures that parameters follow a predictable pattern, reduces duplication, and enables automation tools to iterate over the items in a consistent manner. As a result, adding a new resource, such as another virtual machine, storage account, subnet, or policy assignment, requires only the insertion of an additional element in the array, rather than modifying multiple sections of a template or script.
This method supports cleaner code, reduces the likelihood of misconfigurations, and enhances the flexibility of deployment pipelines, especially in environments where infrastructure evolves frequently.
Code
module modVirtualNetworks 'br/public:avm/res/network/virtual-network:0.7.2' = [
for vnet in virtualNetworks: {
name: 'vnet-deploy-${vnet.name}'
dependsOn: [resourceGroup, modLogAnalyticsWorkspace]
scope: resRsgNetwork
params: {
name: vnet.name
addressPrefixes: vnet.addressPrefixes
subnets: [
for subnet in vnet.subnets: {
name: subnet.name
addressPrefix: subnet.addressPrefix
}
]
tags: vnet.tags
diagnosticSettings: [
{
metricCategories: [
{
category: 'AllMetrics'
}
]
name: 'customSetting'
workspaceResourceId: modLogAnalyticsWorkspace[0].outputs.resourceId
}
]
}
}
]Public IP Address
A VPN gateway requires an external Public IP Address to act as the entry point for secure connections into the Azure environment. Azure supports several types of Public IP Addresses, each designed for different scenarios. You can choose between Basic and Standard SKUs, as well as static or dynamic allocation methods. Selecting the appropriate Public IP type ensures the correct availability, security, and routing behavior for your VPN setup.
Parameters
param publicIpAddresses = [
{
Name: 'pip-vgw-hub-01'
Location: location
Sku: 'Standard'
}
]The type of Public IP Address you want to deploy is determined by the SKU. Azure uses the SKU to define the capabilities and behavior of the Public IP, such as whether it is Basic or Standard, and what level of availability, security, and features it supports. By selecting the correct SKU, you ensure that the Public IP matches the requirements of your workload, for example, VPN Gateways generally require a Standard SKU Public IP for production‑grade reliability and security.
Code
module modpublicIpAddresses 'br/public:avm/res/network/public-ip-address:0.5.1' = [for pip in publicIpAddresses: {
name: 'public-ip-deploy-${pip.name}'
scope: resRsgNetwork
params: {
name: pip.Name
location: pip.Location
publicIPAllocationMethod: 'Static'
skuName: pip.Sku
}
}] Virtual Network Gateway
The Virtual Network Gateway serves as the central connectivity component of the landing zone. It acts as the primary secure entry and exit point between Azure and external networks, such as on‑premises datacenters or other cloud environments. Through VPN or ExpressRoute connections, the gateway enables encrypted, reliable communication and ensures that all hybrid network traffic flows through a controlled, monitored, and highly available path.
Because it provides the foundational hybrid connectivity layer, the Virtual Network Gateway is often considered the heart of the landing zone, enabling workloads inside Azure to communicate seamlessly and securely with systems outside the cloud.
Parameter
param virtualNetworkGateways = [
{
name: 'vnet-gw-01'
virtualNetworkName: virtualNetworks[0].name
publicIpAddressName: 'vpnGatewayPublicIP'
gatewayType: 'Vpn'
vpnType: 'RouteBased'
sku: 'VpnGw1AZ'
clusterSettings: {}
}
]As you can see, this parameter file contains several references to other parameters. These references allow values to be dynamically reused throughout the deployment, ensuring consistency and reducing duplication. By linking parameters together, the configuration becomes easier to maintain and update, especially when multiple resources depend on shared values such as names, locations, or resource IDs.
Code
module modVirtualNetworkGateways 'br/public:avm/res/network/virtual-network-gateway:0.10.1' = [
for vnetGw in virtualNetworkGateways: {
name: 'vnet-gw-deploy-${vnetGw.name}'
dependsOn: [modVirtualNetworks,modpublicIpAddresses]
scope: resRsgNetwork
params: {
name: vnetGw.name
gatewayType: vnetGw.gatewayType
vpnType: vnetGw.vpnType
skuName: vnetGw.sku
existingPrimaryPublicIPResourceId: modpublicIpAddresses[0].outputs.resourceId
virtualNetworkResourceId: modVirtualNetworks[0].outputs.resourceId
clusterSettings: {
clusterMode: 'activePassiveNoBgp'
}
}
}
]In this design, I have selected the simplest configuration, using a single Public IP Address for the Virtual Network Gateway. This setup is sufficient for standard connectivity scenarios. However, Azure supports multiple, more advanced gateway configurations that can include additional Public IPs or higher availability options. These alternatives allow for greater redundancy, enhanced throughput, or more complex hybrid networking requirements, depending on the needs of the environment.
Local Network Gateway
A Local Network Gateway represents your on‑premises network in Azure. It defines the public IP address of your local VPN device and the internal address spaces used in your datacenter. Azure uses this information to correctly route traffic between your on‑premises environment and the Virtual Network Gateway. Together, they form the basis of a secure, bidirectional hybrid VPN connection between Azure and your local network.
Parameter
param localNetworkGateways = [
{
name: 'lng-home-01'
ipAddress: '8.8.8.8' // your external home ip
addressPrefixes: [
'192.168.1.1/24' // ip range on the other side of the vpn
]
}
]You need to adjust the addressPrefixes and ipAddress values to match your own on‑premises network configuration. These settings define the internal IP ranges and the public IP of your local VPN device, which Azure uses to route traffic correctly. Providing accurate values ensures that the VPN connection functions properly and avoids routing conflicts between your on‑premises environment and the Azure virtual network.
Code
module modLocalNetworkGateways 'br/public:avm/res/network/local-network-gateway:0.4.0' = [
for lng in localNetworkGateways: {
name: 'lng-deploy-${lng.name}'
dependsOn: [modVirtualNetworkGateways]
scope: resRsgNetwork
params: {
name: lng.name
localGatewayPublicIpAddress: lng.ipAddress
localNetworkAddressSpace: {
addressPrefixes: lng.addressPrefixes
}
}
}
]
VPN Connection
With all prerequisite components deployed and configured, the final step is to establish the actual VPN connection to the Local Network Gateway. This connection links the Azure Virtual Network Gateway to your on‑premises environment, enabling encrypted, bidirectional communication. Once created, Azure can route traffic securely between both networks based on the address spaces and settings defined earlier.
Parameter
param vpnConnections = [
{
name: 'vnet-gw-01-to-${localNetworkGateways[0].name}-conn'
vpnGatewayName: 'vnet-gw-01'
localNetworkGatewayName: 'lng-home-01'
sharedKey: 'DitiseenSharedKey01!'
}
// {
// name: 'connection2'
// localNetworkGatewayName: localNetworkGateways[1].name
// vpnGatewayName: 'vpnGateway'
// sharedKey: ''
// }
]Once again, the configuration is defined within an array. This approach ensures that additional connections can be added easily in the future without modifying the overall structure. By simply appending a new entry to the array, you can introduce new VPN connections in a clean, scalable, and maintainable way, keeping the deployment flexible as the environment grows.
Code
module modConnection 'br/public:avm/res/network/connection:0.1.6' = [
for (connection, i) in vpnConnections: {
scope: resRsgNetwork
name: 'connection-deploy-${connection.name}'
dependsOn: [modVirtualNetworkGateways, modLocalNetworkGateways]
params: {
name: connection.name
virtualNetworkGateway1: {
id: modVirtualNetworkGateways[0].outputs.resourceId
}
localNetworkGateway2ResourceId: modLocalNetworkGateways[0].outputs.resourceId
connectionType: 'IPsec'
vpnSharedKey: connection.sharedKey
}
}
]Private DNS Zones
Private DNS Zones provide internal, private name resolution for resources inside an Azure virtual network. They allow Azure services and virtual machines to resolve hostnames to private IP addresses without exposing DNS records to the public internet. By linking Private DNS Zones to one or more virtual networks, resources can automatically register and resolve names, simplifying connectivity in hybrid and hub‑and‑spoke environments. They are essential for services like Private Endpoints, ensuring secure and consistent internal DNS resolution.
Parameter
param dnsZones = [
{
name: 'privatelink.vaultcore.azure.net'
}
{
name: 'privatelink.file.core.windows.net'
}
{
name: 'privatelink.blob.core.windows.net'
}
{
name: 'privatelink.queue.core.windows.net'
}
{
name: 'privatelink.we.backup.windowsazure.com'
}
{
name: 'privatelink.wvd.microsoft.com'
}
]
In this case I have chosen some private dns zones. In the array it is easy to add another zone.
Code
module modDnsZones 'br/public:avm/res/network/private-dns-zone:0.5.0' = [
for dnsZone in dnsZones: {
name: 'dnszone-deploy-${dnsZone.name}'
scope: resRsgDnszones
params: {
name: dnsZone.name
}
}
]Conclusion
Now that the landing zone is fully deployed and all core networking components are in place, we can proceed with configuring the on‑premises internet connection for the IPsec tunnel to Azure. With the Virtual Network Gateway, Local Network Gateway, and Public IP Address already prepared, your firewall or VPN appliance can be configured to establish a secure, encrypted IPsec connection into the Azure environment.

