You add a user-defined route for one external prefix, point it at the VPN gateway, and the traffic still leaves through Azure's internet path. Or it leaves correctly and the reply never comes back. Both outcomes come from the same mistake, which is treating the Azure next hop as the whole design. The route table only decides which exit a packet takes inside Azure. What happens to it after the tunnel, and how the answer returns, is a second half you have to build.

The next hop is a gateway, not an appliance IP

A UDR sends a matching prefix to a next-hop type, and VirtualNetworkGateway is a type rather than an address. People paste the gateway's private IP into a VirtualAppliance route and then wonder why nothing matches, because that type expects a NIC or an internal load balancer instead. Microsoft's routing documentation also limits the gateway next hop to a virtual network whose gateway is a VPN gateway. ExpressRoute, Route Server and a Virtual WAN hub are not addressed by that option.

Check what the virtual network actually has before you write the route. If the design routes through a hub, the route belongs to the hub's routing owner rather than to the spoke you happen to be editing.

Read the effective route before editing anything

Azure combines system routes, BGP-learned prefixes and UDRs into an effective route table per NIC, and the longest matching prefix wins regardless of where it came from. For the same prefix, a UDR beats a BGP route. That ordering is why a stale UDR from an earlier experiment can quietly override the prefix you just learned over the tunnel.

Look at the effective table first, then decide whether you are adding a route or replacing one:

az network nic show-effective-route-table \
  --resource-group rg-net --name vm-web-nic -o table

Read the source column as well as the prefix. A route with the right prefix and the wrong source tells you which object to edit, and it saves you from adding a second route that loses to the first one.

Write the route against the real prefix and subnet

Use the destination's real prefix, taken from the service you are actually reaching, instead of a public range copied from a forum post. Start with one approved service before you consider a default route that moves all egress. Then attach the route table to the workload subnet. The gateway subnet has its own requirements, and attaching your table there breaks the gateway rather than steering the workload.

Create the route, then bind the table to the subnet that hosts the workload:

az network route-table route create --resource-group rg-net --route-table-name rt-egress \
  --name to-partner --address-prefix 203.0.113.0/24 --next-hop-type VirtualNetworkGateway
az network vnet subnet update --resource-group rg-net --vnet-name vnet-app \
  --name snet-web --route-table rt-egress

Propagation matters too. With BGP propagation enabled on that table, learned prefixes appear alongside the ones you wrote, and a more specific learned prefix can win over a broader UDR. Disable propagation only when you intend the UDR set to be authoritative for that subnet.

Finish the on-premises half or the flow dies there

The on-premises gateway has to accept the Azure source, forward it to the destination and permit the application. Its onward path can be an internet exit or another private hop, but that decision belongs in the plan rather than in the Azure route table. The usual failure is the return. An internet destination will not route a private Azure source back to you, so the reply goes to its own default gateway and dies, or never leaves at all.

Two designs solve that and they are not interchangeable. Translate the source at the exit, so the destination replies to a public address the on-premises network owns. Or preserve the source and build a routed return relationship so replies come back over the tunnel. Pick one and write down which address the application will see.

Selectors and allowlists are separate from all of this. Choosing an Azure next hop does not widen the VPN traffic selectors, and it does not change an external service's source allowlist.

Verify with the destination's observed source

Send the request from the real workload rather than from a jump host in another subnet, then read the source address the destination observed. That single value tells you which of the two return designs is in place. Check the reply path next, and confirm that ordinary management traffic to the VM still works, because a default route added by accident shows up as a broken SSH session long before it shows up as a broken application.

A selected-prefix transit is a far narrower change than forced tunneling. Keep it narrow and the rollback is deleting one route.

Ngaahi tokoni fekau'aki