Edit

Add private connectivity to Azure IoT Operations

This article describes how to add private connectivity to an existing Azure IoT Operations deployment. 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 Azure Storage, Azure Key Vault, and Azure Event Grid
3 Update Azure Arc connectivity Update the existing Arc connection with Arc gateway. Choose between Arc gateway only or Arc gateway + explicit proxy
4 Configure data flow destinations with private endpoints Route data flow traffic to cloud destinations like Event Grid through Azure 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 existing Azure IoT Operations deployment on an Arc-connected Kubernetes cluster. The cluster must be healthy with all Azure IoT Operations pods running.
  • An Azure subscription with sufficient permissions to create private endpoints, private DNS zones, and role assignments (typically Owner or Contributor + User Access Administrator).
  • Azure CLI and kubectl installed on your admin or jump machine.
  • An Azure VNet with network connectivity from your cluster. If your cluster runs on Azure VMs within the same VNet or a peered VNet, this connectivity is already in place.
  • (Optional) An Azure Event Grid namespace with MQTT enabled. Needed only if you route data flow traffic to Event Grid in Configure data flow 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.

The Azure CLI examples in this article use environment variables so that you can set each value once and then copy and paste the commands as-is. If you're using the Azure IoT Operations Codespaces environment from the quickstart, these variables are already set for you and you can skip this step. Otherwise, set the following environment variables in your shell before you run the commands.

The following scripts set the most commonly used environment variables:

Environment variable Description
SUBSCRIPTION_ID The ID of the subscription that contains your Azure IoT Operations instance.
RESOURCE_GROUP The name of the resource group that contains your Azure IoT Operations instance.
AIO_INSTANCE_NAME The name of your Azure IoT Operations instance. To list your instances, run az iot ops list -o table.
CLUSTER_NAME The name of the Azure Arc-enabled Kubernetes cluster that hosts your instance.
LOCATION The Azure region to use for new resources, for example eastus.
SUBSCRIPTION_ID=<subscription-id>
RESOURCE_GROUP=<resource-group-name>
AIO_INSTANCE_NAME=<instance-name>
CLUSTER_NAME=<cluster-name>
LOCATION=<region>

You only need to set the variables that this article uses. This article might use additional environment variables for resource names that you choose. The article explains how to set them where they're introduced.

This article also uses environment variables for the network and Azure resources that you choose, including VNET_RESOURCE_GROUP, VNET_NAME, SUBNET_NAME, STORAGE_ACCOUNT_NAME, KEY_VAULT_NAME, EVENT_GRID_NAMESPACE, ARC_GATEWAY_RESOURCE_ID, CUSTOM_LOCATIONS_OID, FIREWALL_POLICY_NAME, CLUSTER_SUBNET_CIDRS, FIREWALL_PRIVATE_IP, PROXY_PORT, TOPIC_SPACE_NAME, AIO_IDENTITY_PRINCIPAL_ID, DATAFLOW_NAME, CONFIG_FILE, and CLUSTER_HOST_IP. Set each one before you run the related commands.

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.

Screenshot of the Azure portal showing an Arc Gateway resource with its Gateway URL and resource properties.

Note

You can have up to five Arc Gateway resources per subscription.

For the list of FQDNs that you must allow 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 you use when connecting the cluster requires the Object ID (OID) of the Azure Arc Custom Locations service principal.

To find it:

  1. Go to Microsoft Entra ID in the Azure portal.
  2. Select Enterprise applications.
  3. Search for Azure Arc Kubernetes Custom Locations.
  4. Open the application, go to Properties, and copy the Object ID.

Create private endpoints and DNS zones

Azure IoT Operations uses a storage account (schema registry) and Key Vault (secret sync) at runtime. Create private endpoints and DNS zones for these resources so all traffic routes privately. Once you link the private DNS zones to your VNet, your cluster automatically resolves these services to their private IPs.

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 $LOCATION \
  --subnet "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$VNET_RESOURCE_GROUP/providers/Microsoft.Network/virtualNetworks/$VNET_NAME/subnets/$SUBNET_NAME" \
  --private-connection-resource-id "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.Storage/storageAccounts/$STORAGE_ACCOUNT_NAME" \
  --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 $LOCATION \
  --subnet "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$VNET_RESOURCE_GROUP/providers/Microsoft.Network/virtualNetworks/$VNET_NAME/subnets/$SUBNET_NAME" \
  --private-connection-resource-id "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.KeyVault/vaults/$KEY_VAULT_NAME" \
  --group-id vault \
  --connection-name pe-conn-keyvault

Note

You create the Event Grid private endpoint in this step so it's ready for Configure data flow destinations with private endpoints, which routes data flow traffic to Event Grid over Private Link.

Event Grid namespace

az network private-endpoint create \
  --name pe-eventgrid \
  --resource-group $RESOURCE_GROUP \
  --location $LOCATION \
  --subnet "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$VNET_RESOURCE_GROUP/providers/Microsoft.Network/virtualNetworks/$VNET_NAME/subnets/$SUBNET_NAME" \
  --private-connection-resource-id "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.EventGrid/namespaces/$EVENT_GRID_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/$VNET_RESOURCE_GROUP/providers/Microsoft.Network/virtualNetworks/$VNET_NAME" \
  --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/$VNET_RESOURCE_GROUP/providers/Microsoft.Network/virtualNetworks/$VNET_NAME" \
  --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/$VNET_RESOURCE_GROUP/providers/Microsoft.Network/virtualNetworks/$VNET_NAME" \
  --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.

Update Arc connectivity

With private endpoints and DNS in place, update your existing Arc connection to use Arc gateway. 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: Update the Arc connection with Arc gateway

Update your existing Arc connection to associate it with the Arc gateway:

az connectedk8s update \
  --name $CLUSTER_NAME \
  --resource-group $RESOURCE_GROUP \
  --gateway-resource-id $ARC_GATEWAY_RESOURCE_ID

Tip

For new clusters not yet Arc-enabled: If your cluster isn't connected to Azure Arc yet, use az connectedk8s connect instead:

az connectedk8s connect \
  --name $CLUSTER_NAME \
  --resource-group $RESOURCE_GROUP \
  --location $LOCATION \
  --custom-locations-oid $CUSTOM_LOCATIONS_OID \
  --enable-oidc-issuer \
  --enable-workload-identity \
  --disable-auto-upgrade \
  --gateway-resource-id $ARC_GATEWAY_RESOURCE_ID

Step 2: Verify connectivity

  1. Confirm the Arc agents and Arc Proxy pod are running:

    kubectl get pods -n azure-arc
    
  2. Verify DNS resolves to private IPs:

    nslookup $STORAGE_ACCOUNT_NAME.blob.core.windows.net
    nslookup $KEY_VAULT_NAME.vault.azure.net
    nslookup $EVENT_GRID_NAMESPACE.ts.eventgrid.azure.net
    

    Each result should return an IP in your private address range (for example, 10.x.x.x), not a public IP.

  3. 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.

Configure data flow destinations with private endpoints

Azure IoT Operations data flows 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, data flows 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 data flow 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 port 5671) because Azure IoT Operations data flow connects to Event Hubs via Kafka.
  • Data Lake Storage Gen2 supports two group IDs: use blob for flat namespace access and dfs for hierarchical namespace (HNS-enabled) accounts. Choose the one that matches your storage account configuration.

The steps in the following section use Azure Event Grid as the example. The same pattern applies to every destination, substitute the values from the preceding 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 $EVENT_GRID_NAMESPACE \
  --resource-group $RESOURCE_GROUP \
  --location $LOCATION \
  --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 $EVENT_GRID_NAMESPACE \
  --topic-templates "#"

Note

In the Event Grid namespace, set Maximum client sessions per authentication name to 3 or more so data flow 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 data flow 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 Publisher and EventGrid TopicSpaces Subscriber.

For a typical data flow that publishes telemetry to Event Grid:

az role assignment create \
  --assignee $AIO_IDENTITY_PRINCIPAL_ID \
  --role "EventGrid TopicSpaces Publisher" \
  --scope "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.EventGrid/namespaces/$EVENT_GRID_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 data flow 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 Azure IoT Operations Arc extension's service principal. To find it, go to the Azure portal → your Arc-enabled cluster → Extensionsazure-iot-operationsProperties, 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 tsv
    
  • User-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 managed identity used for SecretSync instead of the Azure IoT Operations extension's system-assigned managed identity), the dataflow receives a NotAuthorized error after CONNACK and enters a reconnect loop.

Step 3: Disable public access on the Event Grid namespace

You already created the Event Grid private endpoint and DNS zone in Create private endpoints and DNS zones. Now, disable public access:

az eventgrid namespace update \
  --name $EVENT_GRID_NAMESPACE \
  --resource-group $RESOURCE_GROUP \
  --public-network-access Disabled

Verify that public access is disabled:

az eventgrid namespace show --name $EVENT_GRID_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 $EVENT_GRID_NAMESPACE.$LOCATION-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 data flow endpoint for Event Grid

Create an Event Grid MQTT data flow endpoint. This action creates an endpoint that uses 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 data flow resolves the FQDN through DNS, which returns the private endpoint IP if your DNS zones are configured correctly.

  1. Go to the Azure IoT Operations experience.
  2. Create an Event Grid MQTT data flow endpoint with the host set to <namespace>.<region>-1.ts.eventgrid.azure.net.

For more information, see Configure MQTT data flow endpoints for Event Grid.

Step 6: Create a data flow to test

Create a data flow that routes MQTT broker messages to the Event Grid destination.

  1. Go to the Azure IoT Operations experience.
  2. Select Dataflows > Create dataflow.
  3. Set the source to the default MQTT broker endpoint.
  4. Set the destination to the eventgrid-private-endpoint you created.
  5. Set the destination topic to a topic that matches your topic space template.
  6. Apply the data flow.

Step 7: Validate telemetry arrives at Event Grid

Publish a test message to the MQTT broker by using any MQTT client. For example, use 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 data flow is working:

  1. Go to your Event Grid namespace in the Azure portal.

  2. Check Metrics for incoming MQTT messages.

    Screenshot of Event Grid namespace metrics showing successful MQTT published messages.

  3. Verify the data flow pod logs show successful message delivery:

    kubectl logs -n azure-iot-operations -l app=dataflow --tail=50
    

If messages are flowing, the data flow is successfully routing through the private endpoint with managed identity auth. If messages don't arrive, see Data flow 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 aren't independently validated.
  • Schema registry RBAC: Use the --skip-ra flag during schema registry creation if you don't have owner-level permissions. Schema registry accesses storage through the trusted Azure services bypass (AzureServices), so no public access is needed.
  • 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: You can have up to five Arc Gateway resources per subscription.
  • Explicit Proxy: Only Azure Firewall Explicit Proxy is 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.