Home / Azure

AzureHow-toAdvanced

Packet capture on Azure VMs with Network Watcher: prove where a connection fails

When NSGs, routes and firewall logs all look fine but connections still reset or time out, capture packets on both VMs with Network Watcher and read the answer in Wireshark.

Deepak Bhardwaj7 min read

Applies toAzure Network Watcher packet capture, Azure VMs and VM scale sets (Windows and Linux), Azure CLI 2.x, Wireshark / tshark

When to reach for packet capture

Use it when the configuration says the traffic should work but it doesn't:

  • Connections time out intermittently, and NSG flow logs show the flow as allowed.
  • Clients see connection reset and the app team says "it's the network".
  • TLS handshakes fail between two VMs, or through an NVA or Azure Firewall.
  • A vendor asks for a capture to prove packets left your side.

A capture answers three questions that config checks can't: did the SYN leave the client, did it reach the server, and who sent the RST or stopped answering.

Run the quick checks first. If one of them already explains the failure, you don't need a capture:

RG=rg-prod-app
VM=vm-app-01

# Can the VM reach the destination at all, and where does it stop?
az network watcher test-connectivity -g $RG --source-resource $VM \
  --dest-address 10.20.1.4 --dest-port 443

# Where is the traffic routed?
az network watcher show-next-hop -g $RG --vm $VM \
  --source-ip 10.10.1.4 --dest-ip 10.20.1.4

Prerequisites

Requirement Check or fix
Network Watcher enabled in the VM's region az network watcher list -o table
Network Watcher agent extension on the VM Install it as shown below
Storage account to receive the .cap file Use an account in the VM's region that the VM can reach outbound
VM is running and its guest agent is healthy az vm get-instance-view -g $RG -n $VM --query "instanceView.vmAgent.statuses"
RBAC Network Contributor (or equivalent) on the VM's resource group, plus blob read access on the storage account

Install the extension. Use NetworkWatcherAgentWindows for Windows VMs:

az vm extension set -g $RG --vm-name $VM \
  --publisher Microsoft.Azure.NetworkWatcher \
  --name NetworkWatcherAgentLinux

Tip

If the storage account has a firewall or only allows private endpoints, the capture upload from the VM can fail even though the capture itself ran. Test with a storage account the VM can definitely reach, or write to a local file with --file-path and pull it off the VM afterwards. On Linux the path must start with /var/captures.

Step 1: Start a filtered capture on both ends

If you capture on one side only, you learn half the story. Start a session on the client VM and on the server VM at the same time, with the same filter.

SA=stnetcapprod01           # storage account in the same region
LOC=westeurope

az network watcher packet-capture create -g $RG -n cap-client-$(date +%H%M) \
  --vm vm-app-01 --storage-account $SA \
  --time-limit 900 --capture-size 256 \
  --filters '[{"protocol":"TCP","remoteIPAddress":"10.20.1.4","remotePort":"443"}]'

az network watcher packet-capture create -g rg-prod-data -n cap-server-$(date +%H%M) \
  --vm vm-db-01 --storage-account $SA \
  --time-limit 900 --capture-size 256 \
  --filters '[{"protocol":"TCP","remoteIPAddress":"10.10.1.4","localPort":"443"}]'

Key parameters:

Parameter Default What to set
--time-limit 18000 s (5 h) Just long enough to reproduce the problem, e.g. 600–1800 s
--capture-size 0 (full packet) 128–256 bytes keeps headers and the TLS handshake but drops most payload
--capture-limit 1 GiB Lower it on busy VMs so the session can't fill the quota
--filters none (everything) Always filter by remote IP and port on production VMs

For a scale set, add --target-type AzureVMSS --target <vmss-name> and limit the instances with --include "0" "1".

Caution

Captures can hold credentials, tokens and customer data from unencrypted protocols. Truncate packets with --capture-size, keep the storage account private, and delete the files when the incident is closed.

Step 2: Reproduce, then stop

Reproduce the failure while both sessions run, and write down the exact client-side timestamp of a failed attempt. Then stop both sessions so the files are written:

az network watcher packet-capture list -l $LOC -o table

az network watcher packet-capture show-status -l $LOC -n cap-client-1412 \
  --query "{status:packetCaptureStatus, reason:stopReason}"

az network watcher packet-capture stop -l $LOC -n cap-client-1412
az network watcher packet-capture stop -l $LOC -n cap-server-1412

Step 3: Download the files

Unless you set --storage-path, files go to the network-watcher-logs container:

az storage blob list --account-name $SA -c network-watcher-logs \
  --auth-mode login --query "[].name" -o tsv | grep -i vm-app-01

az storage blob download --account-name $SA -c network-watcher-logs \
  --auth-mode login -n "<blob name from the list>" -f ./client.cap

Step 4: Read the capture

Open both files in Wireshark, or use tshark for a quick scan:

# All connection attempts (SYN without ACK)
tshark -r client.cap -Y "tcp.flags.syn==1 && tcp.flags.ack==0" \
  -T fields -e frame.time -e ip.src -e ip.dst -e tcp.dstport

# Who sends resets?
tshark -r client.cap -Y "tcp.flags.reset==1" -T fields -e frame.time -e ip.src -e ip.dst

# Retransmissions and zero windows
tshark -r server.cap -Y "tcp.analysis.retransmission || tcp.analysis.zero_window"

Compare the two sides for the failed timestamp:

Client capture Server capture Most likely cause
SYN sent, retransmitted, no reply No SYN seen Dropped on the path: NSG, UDR to an NVA or firewall, firewall rule, wrong route
SYN sent, no SYN-ACK SYN seen, SYN-ACK sent Return path broken: asymmetric routing through an NVA, or SYN-ACK dropped on the way back
SYN sent, RST received SYN seen, RST sent Nothing listening on the port, or the host firewall rejects the connection
SYN sent, RST received No SYN seen A device in the middle (NVA, firewall, proxy) is sending the reset
Handshake OK, TLS Client Hello, then RST or silence Client Hello not seen, or seen and answered TLS inspection or an MTU issue in the middle; check SNI and certificate settings
Long idle connection, then RST on next use No RST sent by server Idle timeout on a load balancer, NAT gateway or firewall; set TCP keepalives below that timeout
Many retransmissions, zero window Server window full Server or app overloaded, not the network

The SYN-without-a-reply and reset rows are where you get proof: the same packet appears in one file and not the other, so you know which hop dropped or answered it.

Quick alternative: tcpdump through Run Command

When you can't install the extension (for example, a change freeze on extensions), a short tcpdump through Run Command can still show whether packets arrive. Run Command has an execution time limit, so keep the capture short:

az vm run-command invoke -g $RG -n vm-db-01 --command-id RunShellScript \
  --scripts "timeout 60 tcpdump -i eth0 -nn -c 200 'host 10.10.1.4 and port 443'"

The output comes back as text, which is enough to see whether SYNs arrive. Use the Network Watcher capture when you need a file to analyse.

Clean up

az network watcher packet-capture delete -l $LOC -n cap-client-1412
az network watcher packet-capture delete -l $LOC -n cap-server-1412

Deleting the session doesn't delete the capture file. Remove the blobs yourself, or let a lifecycle rule delete them.

Prevent it next time

  • Pre-install the Network Watcher agent in your VM baseline (image or IaC), so a capture during an incident doesn't wait on an extension install.
  • Keep one capture storage account per region with no public access, RBAC-only access and a lifecycle rule that deletes network-watcher-logs blobs after 7–14 days.
  • Turn on flow logs (VNet flow logs) for production VNets. They show whether a flow was allowed or denied before you need a capture.
  • Document the paths for each critical flow (client, NVA or firewall, server) in the runbook, so you know which VMs to capture on at 3 a.m.
  • Concurrent capture sessions per region are limited. Check the current Network Watcher limits before you script captures across a whole fleet.