Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
This article describes how to configure private connectivity for Azure IoT Operations. Follow the sections in order:
| Step | Section | What it does |
|---|---|---|
| 1 | Set up Arc Gateway | Create the Arc Gateway resource and retrieve the custom locations OID |
| 2 | Create private endpoints and DNS zones | Create Private Endpoints and DNS zones for the dataplane resources (Storage, Key Vault, Event Grid) you created in the prerequisites |
| 3 | Connect the cluster to Azure Arc | Arc-enable the cluster. Choose between Arc Gateway only or Arc Gateway + Explicit Proxy |
| 4 | Deploy Azure IoT Operations | Deploy Azure IoT Operations. Traffic already routes privately via DNS |
| 5 | Disable public access on storage and Key Vault | Lock down the storage account and Key Vault after Azure IoT Operations is healthy |
| 6 | Configure dataflow destinations with private endpoints | Route dataflow traffic to cloud destinations like Event Grid through Private Link |
These scenarios apply to environments with a single Arc-enabled Kubernetes cluster. There's no Purdue-style network segmentation, no proxy chaining across layers, and no Envoy deployment. If you have a layered network topology, see Tutorial: Deploy Azure IoT Operations in a layered network with private connectivity instead.
Prerequisites
- An Azure subscription with sufficient permissions to create Private Endpoints, Private DNS Zones, and role assignments (typically Owner or Contributor + User Access Administrator). If you don't have a subscription, create a free account before you begin.
- Azure CLI and kubectl installed on your admin or jump machine.
- A Kubernetes cluster deployed and ready to Arc-enable. See Prepare your cluster for supported configurations and setup steps.
- An Azure VNet with network connectivity from your cluster (ExpressRoute, VPN Gateway, VNet peering, or other private routing). If your cluster runs on Azure VMs within the same VNet or a peered VNet, this connectivity is already in place.
- An Azure Storage account in the same resource group — used by Schema Registry for schema storage. If you encounter a RequestDisallowedByPolicy error during creation, add
--allow-shared-key-access falseto theaz storage account createcommand. - An Azure Key Vault in the same resource group — used for secret synchronization with your cluster.
- (Optional) An Azure Event Grid namespace with MQTT enabled. Needed only if you route dataflow traffic to Event Grid in Configure dataflow destinations with private endpoints.
- (Optional) An Azure Firewall with explicit proxy enabled in your VNet, reachable from your cluster. Required only if you follow the Arc Gateway + Explicit Proxy tab for fully private connectivity with no public internet exposure.
Set up Arc Gateway
Azure Arc Gateway consolidates the ~200+ Azure endpoints that Arc agents and extensions require into a single gateway URL. This significantly simplifies your firewall allow list, instead of allowing 200+ individual FQDNs, you allow approximately 9.
Step 1: Create an Arc Gateway resource
If you don't already have an Arc Gateway resource, create one. You need the gateway resource ID when you connect the cluster in the next section. For creation steps, see Create the Arc Gateway resource.
Note
A maximum of five Arc Gateway resources are supported per subscription.
For the list of FQDNs that must be allowed through your firewall when using Arc Gateway, see Allowed endpoints with Arc Gateway.
Step 2: Retrieve the custom locations Object ID
The --custom-locations-oid parameter used when connecting the cluster requires the Object ID (OID) of the Azure Arc Custom Locations service principal.
To find it:
- Go to Microsoft Entra ID in the Azure portal.
- Select Enterprise applications.
- Search for Azure Arc Kubernetes Custom Locations.
- Open the application, go to Properties, and copy the Object ID.
Create private endpoints and DNS zones
The storage account, Key Vault, and Event Grid namespace you created in the prerequisites are the dataplane resources that Azure IoT Operations uses at runtime. Create Private Endpoints and DNS zones for these resources now so all traffic routes privately from the start, before you connect the cluster or deploy Azure IoT Operations.
Step 1: Create Private Endpoints
Create Private Endpoints for the storage account, Key Vault, and Event Grid so all traffic to these services routes privately.
Azure Blob Storage
az network private-endpoint create \
--name pe-storage-blob \
--resource-group <resource-group> \
--location <region-of-vnet> \
--subnet "/subscriptions/<subscription-id>/resourceGroups/<rg-vnet>/providers/Microsoft.Network/virtualNetworks/<vnet>/subnets/<subnet>" \
--private-connection-resource-id "/subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.Storage/storageAccounts/<account>" \
--group-id blob \
--connection-name pe-conn-storage-blob
Azure Key Vault
az network private-endpoint create \
--name pe-keyvault \
--resource-group <resource-group> \
--location <region-of-vnet> \
--subnet "/subscriptions/<subscription-id>/resourceGroups/<rg-vnet>/providers/Microsoft.Network/virtualNetworks/<vnet>/subnets/<subnet>" \
--private-connection-resource-id "/subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.KeyVault/vaults/<keyvault-name>" \
--group-id vault \
--connection-name pe-conn-keyvault
Note
The Event Grid Private Endpoint is created here so it's ready for Configure dataflow destinations with private endpoints, which routes dataflow traffic to Event Grid over Private Link.
Event Grid namespace
az network private-endpoint create \
--name pe-eventgrid \
--resource-group <resource-group> \
--location <region-of-vnet> \
--subnet "/subscriptions/<subscription-id>/resourceGroups/<rg-vnet>/providers/Microsoft.Network/virtualNetworks/<vnet>/subnets/<subnet>" \
--private-connection-resource-id "/subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.EventGrid/namespaces/<namespace>" \
--group-id topicspace \
--connection-name pe-conn-eventgrid
Step 2: Configure Private DNS Zones
Create Private DNS Zones so Azure service FQDNs resolve to Private Endpoint IPs. Link each zone to your VNet and create DNS zone groups so the Private Endpoint A records are registered automatically.
Azure Blob Storage
az network private-dns zone create \
--resource-group <resource-group> \
--name privatelink.blob.core.windows.net
az network private-dns link vnet create \
--resource-group <resource-group> \
--zone-name privatelink.blob.core.windows.net \
--name storage-dns-link \
--virtual-network "/subscriptions/<subscription-id>/resourceGroups/<rg-vnet>/providers/Microsoft.Network/virtualNetworks/<vnet>" \
--registration-enabled false
az network private-endpoint dns-zone-group create \
--resource-group <resource-group> \
--endpoint-name pe-storage-blob \
--name storage-zone-group \
--private-dns-zone "/subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.Network/privateDnsZones/privatelink.blob.core.windows.net" \
--zone-name blob
Azure Key Vault
az network private-dns zone create \
--resource-group <resource-group> \
--name privatelink.vaultcore.azure.net
az network private-dns link vnet create \
--resource-group <resource-group> \
--zone-name privatelink.vaultcore.azure.net \
--name keyvault-dns-link \
--virtual-network "/subscriptions/<subscription-id>/resourceGroups/<rg-vnet>/providers/Microsoft.Network/virtualNetworks/<vnet>" \
--registration-enabled false
az network private-endpoint dns-zone-group create \
--resource-group <resource-group> \
--endpoint-name pe-keyvault \
--name keyvault-zone-group \
--private-dns-zone "/subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.Network/privateDnsZones/privatelink.vaultcore.azure.net" \
--zone-name vault
Event Grid
az network private-dns zone create \
--resource-group <resource-group> \
--name privatelink.ts.eventgrid.azure.net
az network private-dns link vnet create \
--resource-group <resource-group> \
--zone-name privatelink.ts.eventgrid.azure.net \
--name eventgrid-dns-link \
--virtual-network "/subscriptions/<subscription-id>/resourceGroups/<rg-vnet>/providers/Microsoft.Network/virtualNetworks/<vnet>" \
--registration-enabled false
az network private-endpoint dns-zone-group create \
--resource-group <resource-group> \
--endpoint-name pe-eventgrid \
--name eventgrid-zone-group \
--private-dns-zone "/subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.Network/privateDnsZones/privatelink.ts.eventgrid.azure.net" \
--zone-name eventgrid
For the full list of private DNS zone names, see Azure Private DNS Zone values.
Connect the cluster to Azure Arc
With Private Endpoints and DNS in place, connect your cluster to Azure Arc. Choose the tab that matches your connectivity approach:
- Arc Gateway only — The cluster connects through Arc Gateway with a simplified firewall allow list (~9 FQDNs), but outbound traffic still uses public internet paths.
- Arc Gateway + Explicit Proxy — All outbound traffic routes through Azure Firewall Explicit Proxy over your private network with no public internet exposure.
Both tabs build on Set up Arc Gateway. Complete that section first to create the Arc Gateway resource and retrieve the custom locations OID.
Step 1: Connect the cluster with Arc Gateway
Connect your cluster and associate it with the Arc Gateway:
az connectedk8s connect \
--name <cluster-name> \
--resource-group <resource-group> \
--location <region> \
--custom-locations-oid <OID> \
--enable-oidc-issuer \
--enable-workload-identity \
--disable-auto-upgrade \
--gateway-resource-id <gateway-resource-id>
Tip
For existing Arc-enabled clusters: If your cluster is already connected to Azure Arc, use az connectedk8s update instead of az connectedk8s connect:
az connectedk8s update \
--name <cluster-name> \
--resource-group <resource-group> \
--gateway-resource-id <gateway-resource-id>
Step 2: Verify connectivity
Confirm the Arc agents and Arc Proxy pod are running:
kubectl get pods -n azure-arcVerify DNS resolves to private IPs:
nslookup <storage-account>.blob.core.windows.net nslookup <keyvault-name>.vault.azure.net nslookup <eventgrid-namespace>.ts.eventgrid.azure.netEach result should return an IP in your private address range (for example,
10.x.x.x), not a public IP.Verify the cluster appears as Connected in the Azure portal under Azure Arc > Kubernetes clusters.
If any FQDN resolves to a public IP, see DNS resolves to a public IP instead of a private IP.
Deploy Azure IoT Operations
With Private Endpoints, DNS zones, and Arc connectivity in place, deploy Azure IoT Operations to your cluster. The dataplane resources (Storage, Key Vault) already resolve to private IPs through the DNS configuration from Create private endpoints and DNS zones.
For deployment instructions, see Deploy Azure IoT Operations. During deployment, Arc agent traffic routes through the connectivity options you configured (Arc Gateway, Explicit Proxy, or both).
Warning
The storage account and Key Vault must have public access enabled during deployment. Schema Registry requires public access on the storage account at creation time, and the Secret Store Extension (secret sync) needs to reach Key Vault. This means these resources are publicly reachable until you complete Disable public access on storage and Key Vault. Complete that section as soon as Azure IoT Operations pods are healthy to minimize the exposure window.
To further reduce exposure, you can restrict public access to your admin machine's IP only:
Optional: restrict access to your IP during deployment
az storage account network-rule add \
--account-name <storage-account> \
--ip-address <your-public-ip>
az storage account update \
--name <storage-account> \
--resource-group <resource-group> \
--default-action Deny
az keyvault network-rule add \
--name <keyvault-name> \
--ip-address <your-public-ip>/32
az keyvault update \
--name <keyvault-name> \
--resource-group <resource-group> \
--default-action Deny
After Azure IoT Operations is healthy, switch to --public-network-access Disabled as described in the next section.
Disable public access on storage and Key Vault
After Azure IoT Operations is deployed, disable public access on the storage account and Key Vault to complete the lockdown.
Prerequisites
Before disabling public access, confirm the following:
- Azure IoT Operations is deployed and healthy. Run
az iot ops checkand verify all pods in theazure-iot-operationsnamespace are running. See Deploy Azure IoT Operations. - Secret sync is configured and working. Verify that SecretSync and SecretProviderClass resources exist and that secrets are syncing from Azure Key Vault. See Manage secrets for your Azure IoT Operations deployment.
- Schema Registry is functional. Confirm the schema registry pods (
adr-schema-registry-*) are running and can reach the storage account. For more information, see Understand message schemas. - Cluster nodes can resolve Azure DNS. If your cluster uses custom DNS, configure DNS forwarding to Azure DNS (
168.63.129.16) so that Private DNS Zone records resolve correctly. For more information, see Azure Private Endpoint DNS integration.
Step 1: Disable public access and assign RBAC
Disable public network access on the storage account and Key Vault. For the storage account, enable the trusted Azure services bypass so Schema Registry (Microsoft.DeviceRegistry/schemaRegistries) can still access it:
az storage account update \
--name <storage-account> \
--resource-group <resource-group> \
--public-network-access Disabled \
--bypass AzureServices
Verify that public access is disabled:
az storage account show --name <storage-account> --resource-group <resource-group> --query "publicNetworkAccess"
az keyvault update \
--name <keyvault-name> \
--resource-group <resource-group> \
--public-network-access Disabled
Verify that public access is disabled:
az keyvault show --name <keyvault-name> --resource-group <resource-group> --query "properties.publicNetworkAccess"
Note
Schema Registry continues to function correctly through the trusted service bypass (AzureServices). Use the --skip-ra flag during Schema Registry creation if you don't have Owner-level permissions.
Assign the Schema Registry's managed identity access to the storage account:
az role assignment create \
--assignee <identity-client-id> \
--role "Storage Blob Data Contributor" \
--scope <storage-account-resource-id>
Step 2: Verify private connectivity
From your cluster node, confirm the storage FQDN resolves to a private IP:
nslookup <storage-account>.blob.core.windows.netConfirm the Key Vault FQDN resolves to a private IP:
nslookup <keyvault-name>.vault.azure.netBoth should return IPs in your private address range (for example,
10.x.x.x), not public IPs.Verify that secret sync is still working after disabling public access:
kubectl get secretsync -n azure-iot-operationsAll SecretSync resources should show a status of
Synced. If any show errors, see Troubleshoot private connectivity.
Configure dataflow destinations with private endpoints
Azure IoT Operations dataflows send telemetry to cloud destinations like Azure Event Grid, Azure Event Hubs, Azure Data Explorer, Data Lake Storage Gen2, and Microsoft Fabric OneLake. By default, dataflows connect to these services over their public endpoints. To keep traffic private, create Private Endpoints for each destination and ensure DNS resolves to the private IPs.
Note
If you created an Event Grid Private Endpoint and DNS zone in Create private endpoints and DNS zones, Event Grid is already configured for private access. Skip ahead to Step 2: Assign RBAC for Event Grid for that destination.
The following table shows supported dataflow destinations and the Private DNS Zone, group ID, and port for each:
| Destination | Private DNS Zone | Group ID | Port |
|---|---|---|---|
| Azure Event Grid (MQTT) | privatelink.ts.eventgrid.azure.net |
topicspace |
8883 |
| Azure Event Hubs | privatelink.servicebus.windows.net |
namespace |
9093 (Kafka) |
| Azure Data Explorer | privatelink.<region>.kusto.windows.net |
cluster |
443 |
| Data Lake Storage Gen2 | privatelink.blob.core.windows.net or privatelink.dfs.core.windows.net |
blob or dfs |
443 |
| Microsoft Fabric OneLake | privatelink.dfs.fabric.microsoft.com |
onelake |
443 |
Note
- Event Hubs uses Kafka protocol port
9093(not the standard AMQP port5671) because Azure IoT Operations dataflows connect to Event Hubs via Kafka. - Data Lake Storage Gen2 supports two group IDs: use
blobfor flat namespace access anddfsfor hierarchical namespace (HNS-enabled) accounts. Choose the one that matches your storage account configuration.
The steps below use Azure Event Grid as the example. The same pattern applies to every destination, substitute the values from the table.
Step 1: Create an Event Grid namespace
If you don't already have one, create an Event Grid namespace with MQTT (topic spaces) enabled:
az eventgrid namespace create \
--name <namespace> \
--resource-group <resource-group> \
--location <region> \
--topic-spaces-configuration state=Enabled \
--sku name=Standard capacity=1
Then create a topic space. For testing, you can use the wildcard # as the topic template:
az eventgrid namespace topic-space create \
--name <topic-space-name> \
--resource-group <resource-group> \
--namespace-name <namespace> \
--topic-templates "#"
Note
In the Event Grid namespace, set Maximum client sessions per authentication name to 3 or more so dataflows can scale up. See Event Grid MQTT multi-session support.
Step 2: Assign RBAC for Event Grid
Grant the Azure IoT Operations managed identity the Event Grid role that matches your dataflow direction:
- One-way (source → Event Grid): Assign
EventGrid TopicSpaces Publisher. - One-way (Event Grid → destination): Assign
EventGrid TopicSpaces Subscriber. - Bidirectional bridge: Assign both
EventGrid TopicSpaces PublisherandEventGrid TopicSpaces Subscriber.
For a typical dataflow that publishes telemetry to Event Grid:
az role assignment create \
--assignee <aio-managed-identity-principal-id> \
--role "EventGrid TopicSpaces Publisher" \
--scope "/subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.EventGrid/namespaces/<namespace>"
Note
If you create a bidirectional MQTT bridge (both source and destination use Event Grid), you need both Publisher and Subscriber roles. See Tutorial: Configure MQTT bridge between Azure IoT Operations and Event Grid for an example.
Important
Assign RBAC to the correct identity. The dataflow endpoint's authentication method determines which identity you must grant the Event Grid role to:
System-assigned managed identity (default): Assign the role to the AIO Arc extension's service principal. To find it, go to the Azure portal → your Arc-enabled cluster → Extensions → azure-iot-operations → Properties, and copy the Principal ID. Or use the CLI:
az rest --method get \ --url "https://management.azure.com/subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.Kubernetes/connectedClusters/<cluster-name>/extensions/azure-iot-operations?api-version=2024-11-01-preview" \ --query "identity.principalId" -o tsvUser-assigned managed identity: Assign the role to that identity's principal ID.
If you assign the role to the wrong identity (for example, a user-assigned MI used for SecretSync instead of the AIO extension's system-assigned MI), the dataflow receives a NotAuthorized error after CONNACK and enters a reconnect loop.
Step 3: Disable public access on the Event Grid namespace
The Event Grid Private Endpoint and DNS zone were already created in Create private endpoints and DNS zones. Now disable public access:
az eventgrid namespace update \
--name <namespace> \
--resource-group <resource-group> \
--public-network-access Disabled
Verify that public access is disabled:
az eventgrid namespace show --name <namespace> --resource-group <resource-group> --query "publicNetworkAccess"
Step 4: Verify DNS resolves to a private IP
From your cluster node (or a VM in the same VNet), confirm the FQDN resolves to the Private Endpoint IP:
nslookup <namespace>.<region>-1.ts.eventgrid.azure.net
The result should return an IP in your private address range (for example, 10.x.x.x), not a public IP. If it returns a public IP, check your Private DNS Zone linkage.
Step 5: Create the dataflow endpoint for Event Grid
Create an Event Grid MQTT dataflow endpoint. This creates an endpoint using system-assigned managed identity authentication. The host uses the Event Grid namespace's MQTT hostname on port 8883. No special configuration is needed for Private Link — the dataflow resolves the FQDN through DNS, which returns the Private Endpoint IP if your DNS zones are configured correctly.
- Go to the Azure IoT Operations experience.
- Create an Event Grid MQTT dataflow endpoint with the host set to
<namespace>.<region>-1.ts.eventgrid.azure.net.
For more information, see Configure MQTT dataflow endpoints for Event Grid.
Step 6: Create a dataflow to test
Create a dataflow that routes MQTT broker messages to the Event Grid destination.
- Go to the Azure IoT Operations experience.
- Select Dataflows > Create dataflow.
- Set the source to the default MQTT broker endpoint.
- Set the destination to the
eventgrid-private-endpointyou created. - Set the destination topic to a topic that matches your topic space template.
- Apply the dataflow.
Step 7: Validate telemetry arrives at Event Grid
Publish a test message to the MQTT broker using any MQTT client. For example, with mosquitto_pub:
mosquitto_pub -h <cluster-host-ip> -p 1883 -t "test/eventgrid" -m '{"temperature": 25.5}'
Note
This example uses port 1883 (non-TLS) for quick validation. If your MQTT broker listener is configured with TLS, use port 8883 and supply the appropriate --cafile, --cert, and --key arguments. For production, always use TLS-enabled listeners.
Then check the dataflow is working:
Navigate to your Event Grid namespace in the Azure portal.
Check Metrics for incoming MQTT messages.
Verify the dataflow pod logs show successful message delivery:
kubectl logs -n azure-iot-operations -l app=dataflow --tail=50
If messages are flowing, the dataflow is successfully routing through the Private Endpoint with managed identity auth. If messages don't arrive, see Dataflow messages don't arrive at Event Grid.
After disabling public access on any Azure resource, verify Azure IoT Operations is still healthy. See Verify Azure IoT Operations health after lockdown.
Known limitations
- Platform validation: The private connectivity patterns described here are based on validated K3s on Ubuntu Server 24.04 scenarios. Other Kubernetes distributions or operating systems haven't been independently validated.
- Schema Registry creation: Schema Registry might require public access enabled at creation time. After creation, you can disable public access and rely on Private Endpoints plus trusted service bypass. Use the
--skip-raflag when creating the Schema Registry to avoid requiring Owner-level permissions. - TLS inspection: Arc Gateway doesn't support TLS termination or inspection. If your firewall performs TLS inspection, you must exclude the Arc Gateway endpoint from inspection. See Arc Gateway and TLS inspection.
- Arc Gateway limits: A maximum of five Arc Gateway resources are supported per subscription.
- Explicit Proxy: Only Azure Firewall Explicit Proxy has been validated. Third-party proxies (for example, Palo Alto) or transparent proxies aren't supported in validated scenarios. Azure IoT Operations doesn't support proxy servers that require a trusted certificate.
Related content
- Simplify network configuration requirements with Azure Arc Gateway
- Access Azure services over Azure Firewall Explicit Proxy
- Configure a dataflow endpoint
- Schema Registry
- Tutorial: Deploy Azure IoT Operations in a layered network with private connectivity
- Azure IoT Operations networking
- Deploy Azure IoT Operations
- Azure Private DNS Zone values
- Troubleshoot private connectivity for Azure IoT Operations