Storage Must Stay Private? Create a Private Endpoint

Published on:

An Azure Storage account has a public endpoint by default. Authentication still protects the data, but the endpoint remains reachable from the Internet. A workload that must use private connectivity needs a different boundary.

Create a private-only storage account and connect its Blob service to snet-data through Azure Private Link:

WEB02 in snet-app
    |
    | storage-account.blob.core.windows.net resolves privately
    v
Private Endpoint NIC in snet-data
    |
    `-- Azure Storage Blob service

Internet client ---> Public endpoint ---> Blocked

This trip builds on Private Service Needs a Private Name? Create a Private DNS Zone. Keep vnet-cloudtrips-test-weu, snet-app, snet-data, and WEB02. The custom internal.cloudtrips.dev zone is not used by Storage; Azure Storage requires its service-specific Private Link zone.

Understand the Three Controls

A complete private Storage path combines three independent controls:

Control Purpose
Private endpoint Gives the Blob service a private IP in the VNet
Private DNS zone Resolves the normal Blob hostname to that private IP inside the VNet
Disabled public network access Rejects data-plane traffic through the public endpoint

The private endpoint does not replace authentication. Clients still need an access key, SAS, or Microsoft Entra data-plane role to read and write data.

This trip creates an endpoint for the blob subresource only. File shares, queues, tables, and Data Lake Storage dfs operations require their own private endpoints and DNS zones.

Generate a Storage Account Name

Storage account names are globally unique. In Cloud Shell, select Bash and generate a stable name from the current subscription:

STORAGE_ACCOUNT="stctpe$(az account show --query id --output tsv | tr -d '-' | cut -c1-10)"
printf 'Storage account: %s\n' "$STORAGE_ACCOUNT"

Copy the displayed value. It contains only lowercase letters and numbers and fits the storage-account naming limit.

Create Private-Only Storage

In the Azure portal, search for Storage accounts, select + Create, and configure the Basics tab:

Subscription: CloudTrips TEST
Resource group: rg-cloudtrips-network-test-weu
Storage account name: the STORAGE_ACCOUNT value
Region: West Europe
Performance: Standard
Redundancy: Locally-redundant storage (LRS)

On Networking, set:

Public network access: Disable
Network routing: Microsoft network routing

Select Review + create > Create, then open the storage account. Disabling public access blocks the Storage data plane; it does not prevent the Azure control plane from managing the account.

Azure Storage account creation with public network access disabled

The equivalent CLI command is:

az storage account create \
  --resource-group rg-cloudtrips-network-test-weu \
  --name "$STORAGE_ACCOUNT" \
  --location westeurope \
  --sku Standard_LRS \
  --kind StorageV2 \
  --https-only true \
  --min-tls-version TLS1_2 \
  --allow-blob-public-access false \
  --public-network-access Disabled

Use either the portal or the CLI, not both.

Create the Blob Private Endpoint

In the storage account, open Security + networking > Networking > Private endpoint connections, then select + Private endpoint.

On Basics, configure:

Subscription: CloudTrips TEST
Resource group: rg-cloudtrips-network-test-weu
Name: pep-cloudtrips-storage-blob-test-weu
Network interface name: nic-pep-cloudtrips-storage-blob-test-weu
Region: West Europe

On Resource, keep Connect to an Azure resource in my directory and select:

Resource type: Microsoft.Storage/storageAccounts
Resource: the STORAGE_ACCOUNT value
Target sub-resource: blob

On Virtual Network, configure:

Virtual network: vnet-cloudtrips-test-weu
Subnet: snet-data
Private IP configuration: Dynamically allocate IP address
Application security group: None

On DNS, keep Integrate with private DNS zone set to Yes. Confirm:

Private DNS zone: privatelink.blob.core.windows.net
Resource group: rg-cloudtrips-network-test-weu

Select Review + create > Create. Because you own both resources, Azure normally approves the private endpoint connection automatically.

Approved Blob private endpoint connected to snet-data with private DNS integration

Verify the Endpoint and DNS Zone

Return to the storage account’s Private endpoint connections page. Confirm that pep-cloudtrips-storage-blob-test-weu has status Approved.

Open Private DNS zones > privatelink.blob.core.windows.net. Confirm that:

  • an A record named after the storage account points to a 10.20.2.x address
  • a virtual network link connects the zone to vnet-cloudtrips-test-weu

The VM requests the normal <storage-account>.blob.core.windows.net hostname, which DNS resolves to <storage-account>.privatelink.blob.core.windows.net inside the linked VNet. The A record in the private DNS zone then resolves that private name to the private endpoint’s 10.20.2.x IP address.

Azure manages the A record through the private endpoint’s DNS zone group. Do not replace it with a manually maintained record.

Storage Private DNS zone showing the managed A record and VNet link

Resolve and Connect from the VNet

Start WEB02 if it is deallocated:

az vm start \
  --resource-group rg-cloudtrips-network-test-weu \
  --name vm-cloudtrips-web02-test-weu

Run DNS and HTTPS checks from that VM:

az vm run-command invoke \
  --resource-group rg-cloudtrips-network-test-weu \
  --name vm-cloudtrips-web02-test-weu \
  --command-id RunShellScript \
  --scripts "getent ahostsv4 ${STORAGE_ACCOUNT}.blob.core.windows.net | head -1; curl --silent --show-error --output /dev/null --write-out 'remote_ip=%{remote_ip} http_code=%{http_code}\n' https://${STORAGE_ACCOUNT}.blob.core.windows.net/"

Both results should show the same 10.20.2.x private endpoint address. An HTTP 400 or 403 response is acceptable here: it proves that HTTPS reached the Blob service, while the unauthenticated request was not authorized.

WEB02 resolving and connecting to the Storage Blob endpoint through its private IP

Prove Public Access Is Blocked

Cloud Shell is outside vnet-cloudtrips-test-weu. Run:

curl --silent --show-error --include \
  "https://${STORAGE_ACCOUNT}.blob.core.windows.net/?comp=list" \
  | head -20

The request should be rejected with HTTP 403. Public DNS may still return a public Azure address; private-only means that the public data endpoint refuses the connection, not that its public hostname disappears.

Cloud Shell showing that public access to the Storage Blob endpoint is blocked

Confirm the account setting and private endpoint connection explicitly:

az storage account show \
  --resource-group rg-cloudtrips-network-test-weu \
  --name "$STORAGE_ACCOUNT" \
  --query publicNetworkAccess \
  --output tsv

az network private-endpoint show \
  --resource-group rg-cloudtrips-network-test-weu \
  --name pep-cloudtrips-storage-blob-test-weu \
  --query 'privateLinkServiceConnections[].{Name:name,Status:privateLinkServiceConnectionState.status}' \
  --output table

Expect Disabled and a private endpoint connection with status Approved.

Cloud Shell showing disabled public network access and the approved private endpoint connection

Keep or Remove the Private Endpoint Lab

Keep the resources if the next trip will use private Storage. Deallocate WEB02 when you no longer need it:

az vm deallocate \
  --resource-group rg-cloudtrips-network-test-weu \
  --name vm-cloudtrips-web02-test-weu

To remove only this lab, delete the private endpoint, storage account, and the Storage-specific Private DNS zone. Keep the VNet, subnets, VMs, and internal.cloudtrips.dev zone for other networking trips.