azureblog.co.uk
← cd ~/learn
// hands-on lab · intermediate

Lock a storage account behind a private endpoint

Build a VNet, a storage account with public access turned off, a private endpoint and its DNS zone with the Azure CLI, prove name resolution from inside and outside, then delete the lot.

time
40 minutes
level
Intermediate
cost
Under £1 if you delete it the same day (private endpoint, small VM, storage)
You'll need
  • An Azure subscription where you can create resource groups
  • Azure CLI, signed in with az login (or use Cloud Shell in Bash mode)
0 of 8 steps done
Ticks are saved in this browser only. Checked 9 Oct 2026: confirm details in Microsoft's documentation before using in production.
  1. 01

    Create the resource group and network

    Everything goes in one resource group so it's easy to delete at the end. These commands are for Bash, such as Cloud Shell.

    shell
    RG=rg-pe-lab; LOC=uksouth
    az group create -n $RG -l $LOC
    az network vnet create -g $RG -n vnet-lab --address-prefixes 10.20.0.0/16 \
      --subnet-name snet-pe --subnet-prefixes 10.20.1.0/24
    az network vnet subnet create -g $RG --vnet-name vnet-lab -n snet-vm --address-prefixes 10.20.2.0/24
  2. 02

    Create a storage account with public access off

    Storage account names must be globally unique, 3 to 24 lowercase letters and numbers.

    shell
    SA=stpelab$RANDOM
    az storage account create -g $RG -n $SA -l $LOC --sku Standard_LRS --public-network-access Disabled
    SAID=$(az storage account show -g $RG -n $SA --query id -o tsv)
  3. 03

    Add the private endpoint

    The group ID picks the sub-resource. Blob, file, queue, table and dfs each need their own endpoint and their own DNS zone.

    shell
    az network private-endpoint create -g $RG -n pe-$SA --vnet-name vnet-lab --subnet snet-pe \
      --private-connection-resource-id $SAID --group-id blob --connection-name blob
  4. 04

    Create the private DNS zone and wire it up

    The zone holds the private IP, the link lets the VNet use the zone, and the DNS zone group makes Azure create and remove the A record for you.

    shell
    az network private-dns zone create -g $RG -n privatelink.blob.core.windows.net
    az network private-dns link vnet create -g $RG -n link-lab -z privatelink.blob.core.windows.net \
      -v vnet-lab -e false
    az network private-endpoint dns-zone-group create -g $RG --endpoint-name pe-$SA -n default \
      --private-dns-zone privatelink.blob.core.windows.net --zone-name blob
  5. 05

    Add a test VM with no public IP

    shell
    az vm create -g $RG -n vm-lab --image Ubuntu2204 --size Standard_B1s \
      --vnet-name vnet-lab --subnet snet-vm --public-ip-address "" \
      --admin-username azureuser --generate-ssh-keys

    If Standard_B1s isn't available in your region, pick another small size.

  6. 06

    Resolve the name from inside the VNet

    Run Command runs a script on the VM through the Azure agent, so you don't need SSH or a public IP.

    shell
    az vm run-command invoke -g $RG -n vm-lab --command-id RunShellScript \
      --scripts "getent hosts $SA.blob.core.windows.net"

    You should see an address from 10.20.1.0/24, usually 10.20.1.4. That's the .

  7. 07

    Resolve it from outside

    Run the same lookup from your own machine or Cloud Shell. You'll see a public IP and a CNAME through privatelink.blob.core.windows.net. Requests to that public address are rejected with HTTP 403, because public network access is off.

    shell
    nslookup $SA.blob.core.windows.net

    This is the pattern behind most private endpoint problems: when something inside your network gets the public IP, its DNS isn't using the private zone. On-premises, that usually means a conditional forwarder to an Azure inbound endpoint is missing.

  8. 08

    Delete everything

    Private endpoints and VMs are charged by the hour, so don't leave them running.

    shell
    az group delete -n $RG --yes --no-wait