Automate Outbound Traffic Blocking
Use Azure Logic Apps to automatically inject NSG deny rules and block outbound VM traffic.
Introduction
30 Second Summary
When a server in your network starts sending data to a suspicious destination, every second counts. The faster you can block that outbound traffic, the less damage is done.
In this project, you will build an automated security response using Azure Logic Apps. You will create a workflow that blocks outbound internet traffic from a virtual machine to a specific website by injecting a deny rule into a Network Security Group.
What You'll Build
An automated workflow that uses Azure Logic Apps to call the Azure Resource Manager API, creating an outbound deny rule in an NSG to block a VM's traffic to a specific external IP address.
By the end of this project, you'll have:
- A resource group containing a virtual network, subnet, NSG, and Linux VM.
- A Logic App with a managed identity and Network Contributor permissions to modify network security rules.
- Before-and-after connectivity test results proving the deny rule blocked outbound traffic from your VM to a specific website.
- Secret Mission: Make the workflow event-driven by wiring up Azure Monitor alerts to trigger the Logic App automatically when suspicious outbound traffic is detected.
Are there any prerequisites?
You need an Azure account with an active subscription and Azure CLI installed on your machine. All other setup is done through the Azure Portal and CLI during the project.
Before We Start
Before We Start...
Create the Network Infrastructure
Your automated security workflow needs a network to protect. Before you can block outbound traffic from a virtual machine, you need the networking foundation in place.
In this step, you'll use the Azure CLI to create a resource group, virtual network, subnet, and Network Security Group. These are the core building blocks of Azure networking that will host your test VM.
In this step, get ready to:
- Create a resource group and virtual network with a subnet.
- Create an NSG and associate it with the subnet.
- Verify the NSG has only default outbound rules.
Create a resource group and virtual network with a subnet
Every Azure project starts with a resource group. This is a container that holds related resources so you can manage and delete them as a single unit.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal and press Enter to open it.
- Create a resource group in the East US region by running this command:
az group create --name nsg-automation-rg --location eastus
What does this command do?
- `--name nsg-automation-rg` sets the name of your resource group.
- `--location eastus` places it in the East US Azure region.
- You should see JSON output with "provisioningState": "Succeeded" confirming the resource group was created.
Seeing an authorization error?
Make sure you're logged in to the Azure CLI. Run az login and follow the browser prompts to authenticate.
If you have multiple subscriptions, set the correct one by running az account set --subscription followed by your subscription name or ID.
Help me fix my Azure CLI authentication issue
Now you need a virtual network (VNet) with a subnet inside it. The VNet is your private network in Azure, and the subnet is a segment within it where your VM will live.
- Create a virtual network and subnet by running this command:
az network vnet create \
--resource-group nsg-automation-rg \
--name nsg-automation-vnet \
--address-prefix 10.0.0.0/16 \
--subnet-name nsg-automation-subnet \
--subnet-prefixes 10.0.1.0/24
What does this command do?
- `--address-prefix 10.0.0.0/16` defines the overall IP range for the virtual network (65,536 addresses).
- `--subnet-name nsg-automation-subnet` creates a subnet inside the VNet.
- `--subnet-prefixes 10.0.1.0/24` gives the subnet 256 addresses within the larger VNet range.
- You should see JSON output showing the VNet and subnet details, with "provisioningState": "Succeeded".
Seeing a resource group not found error?
Double-check that the --resource-group value matches exactly: nsg-automation-rg. Azure resource names are case-sensitive.
Help me troubleshoot my VNet creation error
Create an NSG and associate it with the subnet
An NSG acts as a virtual firewall for your subnet. It controls inbound and outbound traffic using security rules that allow or deny connections based on IP address, port, and protocol.
- Create an NSG by running this command:
az network nsg create \
--resource-group nsg-automation-rg \
--name nsg-automation-nsg
What does this command do?
This creates an empty NSG named nsg-automation-nsg in your resource group. Azure automatically adds a set of default rules to every new NSG. You'll inspect those rules in the next substep.
- You should see JSON output confirming the NSG was created with "provisioningState": "Succeeded".
The NSG exists, but it is not protecting anything yet. You need to attach it to your subnet so it filters traffic for any VM placed inside that subnet.
- Associate the NSG with your subnet by running this command:
az network vnet subnet update \
--resource-group nsg-automation-rg \
--vnet-name nsg-automation-vnet \
--name nsg-automation-subnet \
--network-security-group nsg-automation-nsg
What does this command do?
The --network-security-group flag links nsg-automation-nsg to nsg-automation-subnet. Every VM deployed into this subnet will now have its traffic filtered by the NSG's rules.
- You should see JSON output showing the subnet details, with a networkSecurityGroup field now referencing nsg-automation-nsg.
Subnet update failed?
Make sure all four names match exactly: resource group nsg-automation-rg, VNet nsg-automation-vnet, subnet nsg-automation-subnet, and NSG nsg-automation-nsg. A single typo will cause the command to fail.
Help me fix my subnet update error
Verify the setup
Before moving on, confirm that your NSG is working correctly by listing its current rules. A fresh NSG starts with three default outbound rules that Azure adds automatically.
- List all NSG rules, including the defaults, by running this command:
az network nsg rule list \
--resource-group nsg-automation-rg \
--nsg-name nsg-automation-nsg \
--include-default \
--output table
What should I see?
You should see a table with six default rules. The three outbound rules you care about are:
- AllowVnetOutBound (priority 65000) allows traffic between resources in the same VNet.
- AllowInternetOutBound (priority 65001) allows outbound internet access. This is the rule your Logic App will override later.
- DenyAllOutBound (priority 65500) is a catch-all that blocks everything not explicitly allowed.
The lower the priority number, the higher the precedence. Your Logic App will create a custom rule at priority 100, which Azure evaluates before any of these defaults.
Why does priority matter?
Azure evaluates NSG rules from lowest priority number to highest. The first matching rule wins. A custom deny rule at priority 100 will be evaluated long before AllowInternetOutBound at priority 65001, effectively overriding it for the targeted IP.
Seeing an empty table?
Make sure you included the --include-default flag. Without it, the command only shows custom rules, and a fresh NSG has none.
Help me see my NSG default rules
Your network infrastructure is ready. Next up, you'll deploy a VM into this subnet and confirm it can reach the internet before you block it.
Deploy a Test VM and Verify Outbound Access
Your network is built, but an empty network doesn't prove anything. You need a machine inside it to generate real traffic.
In this step, you'll deploy a Linux VM and test its outbound connectivity to an external website. This baseline is essential because it proves your network currently allows the traffic that the Logic App will block later.
In this step, get ready to:
- Deploy a Linux VM into your subnet.
- Resolve the target website's IP address.
- Confirm the VM can reach the website over HTTPS.
Create a B1s Linux VM in your subnet
The Standard_B1s is Azure's smallest burstable VM size, costing about $0.01 per hour. If your Azure account still has free tier hours, this VM is completely free for up to 750 hours per month.
- Create the VM by running this command:
az vm create \
--resource-group nsg-automation-rg \
--name nsg-automation-vm \
--image Ubuntu2204 \
--size Standard_B1s \
--vnet-name nsg-automation-vnet \
--subnet nsg-automation-subnet \
--nsg "" \
--generate-ssh-keys \
--admin-username azureuser
What does this command do?
- --image Ubuntu2204 deploys an Ubuntu 22.04 Linux VM.
- --vnet-name and --subnet place the VM inside the network you already built.
- --nsg "" tells Azure NOT to create a second NSG on the VM's network interface. Without this flag, Azure creates one automatically, and you'd have two NSGs filtering the same traffic. That makes testing confusing.
- --generate-ssh-keys creates SSH keys for you if they don't already exist on your machine.
This command takes about 1-2 minutes to complete.
- When the command finishes, look for the publicIpAddress field in the JSON output.
- Note this IP address down. You'll use it to SSH into the VM in the next substep.
VM creation failed?
- If you see a quota error, your subscription may not have enough vCPU quota in the eastus region for the B1s size. Try a different region by adding --location westus2 to the command.
- If you see a "resource group not found" error, make sure you completed Step 1 and created the nsg-automation-rg resource group.
- If the command hangs for more than 5 minutes, press Ctrl+C and try running it again.
Help me troubleshoot my az vm create error.
Resolve the target website IP and test connectivity
NSGs filter traffic by IP address, not by domain name. Before you can block traffic to httpbin.org, you need to resolve its actual IP address.
Why IP addresses instead of domain names?
NSGs only support IP addresses, CIDR ranges, and service tags as source and destination filters. Filtering by domain name (FQDN) requires Azure Firewall, which starts at about $30/day.
Resolving the IP first with nslookup is a common real-world technique. The tradeoff is that IP addresses can change over time, but for a short testing window this works perfectly.
- In your local terminal (not inside the VM), resolve the IP address of httpbin.org by running this command:
nslookup httpbin.org
- Look for the Address line under Non-authoritative answer. If multiple IP addresses appear, use the first one listed.
- Note this IP address down. You will need it when building the Logic App workflow in Step 4.
Now that you know the target IP, connect to your VM and test whether it can reach httpbin.org.
- SSH into your VM by running this command:
ssh azureuser@[[VM_PUBLIC_IP="your VM's public IP address"]]
Where do I find the public IP?
This is the publicIpAddress value from the JSON output of the az vm create command you ran in the previous substep. It looks something like 20.123.45.67.
The first time you connect, your terminal asks you to verify the server's fingerprint.
- Type yes and press Enter to continue.
Your terminal prompt changes to show azureuser@nsg-automation-vm. You are now running commands inside the VM, not on your local machine.
- Test outbound HTTPS connectivity by running this command inside the VM:
curl -s -o /dev/null -w "%{http_code}" https://httpbin.org/get
What does this curl command do?
- -s runs curl in silent mode so you don't see a progress bar.
- -o /dev/null throws away the response body because you only care about the status code.
- -w "%{http_code}" tells curl to print only the HTTP status code after the request finishes.
You should see 200 printed in your terminal. This confirms the VM can reach httpbin.org over HTTPS.
Not seeing 200?
- If you see 000 or the command hangs, the VM might not have outbound internet access. Check that your NSG's default AllowInternetOutBound rule is still in place with priority 65001.
- If you see curl: command not found, make sure you are running the command inside the VM (your prompt should show azureuser@nsg-automation-vm), not on your local machine.
- If httpbin.org is temporarily down, try curl -s -o /dev/null -w "%{http_code}" https://ifconfig.me as an alternative. A 200 from any external site confirms outbound access works.
Help me troubleshoot my curl test from the VM.
Record your baseline results
You now have two pieces of data that matter for the rest of this project.
- Write down the IP address of httpbin.org from your nslookup result. This IP goes into the Logic App's deny rule in Step 4.
- Write down the HTTP status code 200 from your curl test. This is your proof that outbound traffic is currently allowed. You will compare this against the result after the deny rule is applied.
- Type exit to disconnect from the VM and return to your local terminal.
Your VM is live and the baseline is set. Next up, you'll create the Logic App that injects a deny rule to block this exact traffic.
Build the Logic App with Managed Identity
In the previous step, you deployed a VM and confirmed it can freely reach httpbin.org over HTTPS.
Now you need something that can automatically inject a deny rule into your NSG on demand. That something is an Azure Logic App. A Logic App can call Azure APIs to modify resources, but only if it has permission to do so. In this step, you will create a Logic App, give it an identity, and grant that identity the right to change NSG rules.
In this step, get ready to:
- Create a Logic App (Consumption) and enable its system-assigned managed identity.
- Assign the Network Contributor role to the managed identity on your resource group.
- Verify the role assignment in the IAM blade.
Create the Logic App and enable managed identity
A managed identity is an Azure-managed credential that lets a resource authenticate to other Azure services without you storing passwords or API keys. You will enable a system-assigned managed identity on your Logic App so it can later call the Azure Resource Manager API to create NSG rules.
Why Consumption over Standard?
The Consumption plan is pay-per-execution with a generous free tier. Standard requires an App Service plan, adding unnecessary cost. For a single-trigger workflow like this one, Consumption is the right choice.
- In the Azure Portal, type Logic apps in the search bar at the top and select **Logic apps** from the results.
- Click Add on the Logic apps page toolbar.
- On the Create Logic App page, select Consumption (Multi-tenant), then click Select.
On the Basics tab, fill in the following:
- Set Subscription to your Azure subscription.
- Set Resource Group to nsg-automation-rg.
- Set Logic App name to nsg-automation-logicapp.
- Set Region to East US.
- Leave Enable log analytics set to No.
- Click Review + create, then click Create.
- Wait for the deployment to complete, then click Go to resource.
You should now see the Logic App overview page for nsg-automation-logicapp.
Next, enable the system-assigned managed identity on this Logic App.
- On the Logic App's sidebar menu, scroll down and click Identity (under the Settings section).
- On the System assigned tab, toggle the Status switch to On.
- Click Save.
- A confirmation dialog appears asking if you want to enable the system-assigned managed identity. Click Yes.
After saving, an Object (principal) ID appears on the Identity blade. This confirms the managed identity is now active.
What is a system-assigned managed identity?
A system-assigned managed identity is tied to the lifecycle of the resource it belongs to. When the Logic App is deleted, Azure automatically deletes the identity too.
This identity lets the Logic App authenticate to Azure APIs without any stored credentials. You will use it in the next step to call the Azure Resource Manager API and create NSG rules.
Don't see the Identity option in the sidebar?
Make sure you opened the Logic App resource itself, not the Logic Apps service page. Click Go to resource from the deployment notification to go directly to the Logic App.
If you still cannot find it, try scrolling down in the sidebar or searching for Identity in the search box at the top of the sidebar.
Still stuck? Help me find the Identity blade on my Logic App.
Assign the Network Contributor role to the managed identity
Your Logic App now has an identity, but it has zero permissions. Azure uses role-based access control (RBAC) to decide what each identity can do. You need to grant the Network Contributor role so the Logic App can create and modify NSG rules.
- In the Azure Portal search bar, type Resource groups and select Resource groups.
- Click on nsg-automation-rg to open your resource group.
- On the resource group sidebar, click Access control (IAM).
- Click Add, then select Add role assignment.
The Add role assignment page opens with a Role tab.
- On the Role tab, type Network Contributor in the search box.
- Select Network Contributor from the results list.
- Click Next.
Now you need to assign this role to the Logic App's managed identity.
- On the Members tab, select Managed identity under Assign access to.
- Click + Select members.
- In the Select managed identities pane, set Managed identity to Logic app.
- Select nsg-automation-logicapp from the list.
- Click Select.
- Click Review + assign, then click Review + assign again to confirm.
Why Network Contributor?
Network Contributor is a built-in Azure role that grants permission to manage network resources including NSGs and security rules. It follows the principle of least privilege. The Logic App only needs to create and modify NSG rules, so Network Contributor is the right fit.
Add role assignment option is disabled?
You need Owner or User Access Administrator permissions on the resource group to assign roles. If the button is grayed out, ask your Azure account administrator to grant you the required permissions.
Having trouble with role assignments? Help me troubleshoot Azure RBAC permissions.
Verify the role assignment
Before moving on, confirm that the role assignment is correctly applied. A missing or incorrect role assignment will cause the Logic App workflow to fail silently in the next step.
- Navigate back to your resource group nsg-automation-rg in the Azure Portal.
- Click Access control (IAM) on the sidebar.
- Click the Role assignments tab.
You should see nsg-automation-logicapp listed with the Network Contributor role. This confirms your Logic App has the permission it needs to modify NSG rules.
Don't see the role assignment?
Role assignments can take a minute or two to propagate. Refresh the page and check the Role assignments tab again.
If it still does not appear, go back through the assignment steps and make sure you selected Managed identity (not User, group, or service principal) and chose Logic app as the managed identity type.
Still not seeing it? Help me verify my role assignment for the Logic App managed identity.
Your Logic App is created, has an identity, and has permission to modify your NSG. Next up, you will design the actual workflow that creates a deny rule to block outbound traffic.
Design the Logic App Workflow
Your Logic App now has a managed identity with permissions to modify network resources. The next step is to tell it exactly what to do when triggered.
You'll design a two-part workflow inside the Logic App Designer: an HTTP trigger that kicks off the automation, and an Azure Resource Manager action that creates the outbound deny rule in your NSG.
In this step, get ready to:
- Add an HTTP Request trigger to the Logic App.
- Configure an Azure Resource Manager action that creates an outbound deny rule.
- Copy the trigger URL for testing.
Add a manual HTTP trigger
Every Logic App workflow starts with a trigger. The Request trigger creates an HTTPS endpoint that you can call to start the workflow on demand.
- In the Azure Portal, navigate to your Logic App nsg-automation-logicapp.
- On the sidebar menu, under Development Tools, select Logic app designer.
- Select Add a trigger on the designer surface.
- In the search box, type request and select the built-in trigger named When a HTTP request is received.
What is the Request trigger?
The Request trigger creates a callable HTTPS endpoint on your Logic App. When any caller sends an HTTP POST to that endpoint, the trigger fires and the workflow runs.
You do not need to add a JSON schema for the request body. This workflow does not need any input data to run.
- Leave the Request Body JSON Schema box empty.
- On the designer toolbar, click Save.
After saving, the trigger generates an HTTP URL field containing the callback URL. You will copy this URL later.
Don't see the HTTP URL?
The URL is only generated after you save the workflow for the first time. Click Save on the designer toolbar, then click the trigger to expand its details.
Still stuck? Why isn't my Logic App Request trigger generating a URL after saving?
Add the Azure Resource Manager action
The ARM connector lets your Logic App create, update, or delete any Azure resource. You will use its "Create or update a resource" action to inject an NSG security rule.
- Below the trigger on the designer, click the + icon to add a new step.
- Select Add an action.
- In the search box, type Azure Resource Manager and select the Azure Resource Manager connector.
- Select the action named Create or update a resource.
What happens next: the connection prompt
After selecting the action, a Create connection pane appears. This is where you tell the Logic App how to authenticate with Azure Resource Manager.
You will use the managed identity you set up in the previous step. This is the recommended approach because the Logic App authenticates automatically without storing any credentials.
- In the Create connection pane, select Managed identity as the authentication type.
- Enter a connection name (e.g. arm-managed-identity).
- Confirm that System-assigned managed identity is selected.
- Click Create.
The action form appears with fields for specifying the Azure resource you want to create. Fill in each field as follows.
- In the Subscription dropdown, select your Azure subscription.
- In the Resource Group dropdown, select nsg-automation-rg.
- In the Resource Provider field, enter Microsoft.Network.
- In the Short Resource Id field, enter networkSecurityGroups/nsg-automation-nsg/securityRules/DenyOutboundToWebsite.
- In the Client Api Version field, enter 2024-05-01.
What is the Short Resource Id?
The Short Resource Id tells Azure Resource Manager the exact resource path after the resource group. In this case, networkSecurityGroups/nsg-automation-nsg/securityRules/DenyOutboundToWebsite means: inside the NSG named nsg-automation-nsg, create (or update) a security rule named DenyOutboundToWebsite.
Now you need to provide the rule definition as a JSON request body. This JSON tells Azure exactly what kind of NSG rule to create.
- In the Properties field (the request body), paste the following JSON:
{
"properties": {
"access": "Deny",
"direction": "Outbound",
"priority": 100,
"protocol": "Tcp",
"sourceAddressPrefix": "*",
"sourcePortRange": "*",
"destinationAddressPrefix": "[[TARGET_IP=\"the httpbin.org IP you recorded earlier\"]]",
"destinationPortRanges": ["80", "443"],
"description": "Block outbound HTTP/HTTPS to target website"
}
}
What does this JSON do?
- access: Deny blocks matching traffic instead of allowing it.
- direction: Outbound targets traffic leaving the VM, not traffic coming in.
- priority: 100 gives this rule the highest possible custom priority. It is evaluated before the default AllowInternetOutBound rule at priority 65001.
- destinationAddressPrefix targets the specific IP of httpbin.org that you recorded earlier.
- destinationPortRanges: ["80", "443"] limits the block to HTTP (port 80) and HTTPS (port 443) traffic.
- On the designer toolbar, click Save.
Your designer should now show two blocks: the When a HTTP request is received trigger at the top, and the Create or update a resource action below it.
Seeing an authorization or permission error?
Make sure the connection was created with Managed identity authentication, not a personal sign-in. Also verify that the Logic App's managed identity has the Network Contributor role on resource group nsg-automation-rg (from the previous step).
Need more help? Help me troubleshoot my Logic App ARM connector permission error.
Copy the trigger URL
The HTTP trigger generated a callback URL when you saved the workflow. You need this URL to fire the Logic App from the terminal in the next step.
- On the designer, click the When a HTTP request is received trigger to expand it.
- Find the HTTP URL field.
- Click the copy icon next to the URL to copy it.
- Save this URL somewhere safe (a text file or sticky note). You will use it in the next step to trigger the workflow.
Keep this URL private
The trigger URL includes a Shared Access Signature (SAS) key. Anyone with this URL can trigger your Logic App. Do not share it publicly.
Your workflow is fully designed and ready to fire. Next up, you will trigger the Logic App and verify that the NSG deny rule actually blocks outbound traffic from your VM.
Test the Automation
Your Logic App workflow is saved and has a live trigger URL. Now it's time to see if the automation actually works.
You'll fire the trigger, verify the new NSG deny rule appears, and test whether the VM's outbound traffic to httpbin.org is truly blocked.
In this step, get ready to:
- Trigger the Logic App and confirm the run succeeded.
- Verify the new deny rule appears in the NSG.
- Test that the VM's outbound traffic to httpbin.org is now blocked.
Trigger the Logic App
The Logic App's HTTP trigger gives you a callback URL that accepts POST requests. Sending a POST to that URL kicks off the entire workflow.
- Make sure you're in your local terminal, not SSH'd into the VM.
- In the command below, replace your-logic-app-trigger-url with the callback URL you copied in the previous step.
- Trigger the Logic App by running:
curl -X POST "your-logic-app-trigger-url" -d "" -H "Content-Type: application/json"
What does this command do?
- curl -X POST sends an HTTP POST request to the trigger URL.
- -d "" sends an empty request body. The trigger doesn't need any input data to start the workflow.
- -H "Content-Type: application/json" tells the Logic App the request is JSON-formatted.
Now verify that the Logic App run succeeded.
- In the Azure Portal, navigate to your Logic App nsg-automation-logicapp.
- Click Overview.
- Scroll down to the Runs history section.
- Confirm the latest run shows Status: Succeeded.
Run shows Status: Failed?
- Click the failed run to expand its details. The error message in the Azure Resource Manager action step will tell you what went wrong.
- Verify the Logic App's managed identity has the Network Contributor role on the resource group nsg-automation-rg.
- Double check that the NSG name, resource group name, and subscription in the ARM action fields all match your actual resources exactly.
- Make sure the request body JSON is valid. A missing comma or bracket will cause the action to fail.
Why is my Logic App run failing?
Verify the NSG rule was created
A succeeded run means the Logic App called the Azure Resource Manager API. Now confirm the deny rule actually landed in your NSG.
- List all rules in your NSG by running this command:
az network nsg rule list --resource-group nsg-automation-rg --nsg-name nsg-automation-nsg --output table
You should see a rule named DenyOutboundToWebsite with priority 100, direction Outbound, and access Deny.
What do these values mean?
- Priority 100 means this rule is evaluated before the default AllowInternetOutBound rule at priority 65001. Lower numbers are evaluated first.
- Access Deny blocks matching traffic instead of allowing it. Because the first matching rule wins, the default allow rule at priority 65001 never gets a chance to fire for this traffic.
- The destinationAddressPrefix column shows the httpbin.org IP you resolved earlier. The rule only blocks traffic to that specific IP address.
Don't see the DenyOutboundToWebsite rule?
- Go back and check the Logic App's run history. If the run status is not Succeeded, the rule was never created.
- Verify the Short Resource Id in the ARM action targets nsg-automation-nsg. A typo in the NSG name would create the rule on the wrong resource (or fail).
Why isn't my NSG deny rule showing up?
Test that outbound traffic is blocked
In Step 2, you confirmed the VM could reach httpbin.org with a 200 response. With the deny rule now in place, the same request should fail.
- SSH into your VM by running this command in your local terminal:
ssh [[VM_SSH_ADDRESS="your-admin-username@your-vm-public-ip"]]
Find your VM connection details
Use the same admin username and public IP address you used to SSH in Step 2. If you need to look up the public IP again, run az vm show -d --resource-group nsg-automation-rg --name nsg-automation-vm --query publicIps -o tsv in a new local terminal window.
- Test whether the VM can still reach httpbin.org by running this command inside the VM:
curl -s -o /dev/null -w "%{http_code}" --connect-timeout 10 https://httpbin.org/get
The command should return 000 or time out entirely. You will not see the 200 response you got in Step 2.
Why does the request fail now?
Your deny rule at priority 100 is evaluated before the default AllowInternetOutBound rule at priority 65001. The NSG sees the outbound TCP traffic heading to the httpbin.org IP on port 443, matches it against your deny rule first, and drops the packet.
The --connect-timeout 10 flag tells curl to give up after 10 seconds instead of waiting indefinitely. Without it, the command would hang until the default TCP timeout.
- Type exit to leave the SSH session and return to your local terminal.
Still getting a 200 response?
- NSG rule changes can take a minute or two to propagate. Wait 60 seconds and try the curl command again.
- Verify the IP address in the deny rule matches what nslookup httpbin.org returns. httpbin.org could resolve to a different IP than the one in your rule.
- Confirm the rule direction is Outbound, not Inbound. An inbound rule would not affect outgoing traffic from the VM.
Why is my VM still reaching httpbin.org after I created the deny rule?
Secret mission
Make It Event-Driven with Azure Monitor
Your Logic App works, but someone has to manually trigger it. What if Azure could detect suspicious outbound traffic and fire the Logic App on its own? Wire up VNet flow logs, Log Analytics, and Azure Monitor to make your security response fully automatic.
Before you go
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project uses a pay-as-you-go VM that incurs costs while running, so review the options below.
Cost warning
Your B1s Linux VM costs approximately $0.0104 per hour while running. If your Azure free account still has remaining B1s free hours, VM compute is covered. However, the public IP address and OS disk still incur small charges.
If you are done with this project, deleting the entire resource group is the safest way to stop all charges immediately.
Resources you used:
- Resource group (contains all other resources).
- Virtual network and subnet.
- Network Security Group (NSG) with your custom deny rule.
- Linux VM (B1s) with public IP address and OS disk.
- Logic App (Consumption) with system-assigned managed identity.
- Network Contributor role assignment on the resource group.
Keep everything running
No action needed. Choose this if you are still actively building or plan to extend the project with the Secret Mission.
- Your VM will continue running at approximately $0.0104 per hour (free if you have remaining B1s free hours).
- The NSG, VNet, subnet, and Logic App (Consumption) are free and will not incur charges.
- The public IP address and OS disk incur small ongoing charges.
Pause - I'll come back to this later
Deallocate the VM to stop compute charges while keeping all your resources and configuration intact.
- Stop and deallocate the VM by running this command:
az vm deallocate --resource-group [[RG_NAME="your resource group name"]] --name [[VM_NAME="your VM name"]]
- This stops VM compute charges immediately. Disk storage charges still apply, but they are minimal.
- Your NSG rules, Logic App, managed identity, and role assignments are all preserved.
- When you are ready to return, start the VM again by running:
az vm start --resource-group [[RG_NAME="your resource group name"]] --name [[VM_NAME="your VM name"]]
Delete - I don't want to use this again
Remove all project resources and stop all charges by deleting the entire resource group.
- Delete the resource group and everything inside it by running this command:
az group delete --name [[RG_NAME="your resource group name"]] --yes --no-wait
- The --yes flag skips the confirmation prompt. The --no-wait flag returns control of your terminal immediately while deletion continues in the background.
- This removes the VM, VNet, subnet, NSG, Logic App, managed identity, public IP, OS disk, and all role assignments.
- This action is irreversible. Make sure you have saved any notes or screenshots you need before running it.
Mission accomplished
Nice Work!
You just built an automated security response system on Azure. When your Logic App fires, it injects a deny rule into your NSG and cuts off outbound traffic to a specific website, no manual portal work required.
You've learned how to:
- Set up Azure network infrastructure (VNet, subnet, and NSG) and deploy a Linux VM with verified outbound internet access.
- Configure a Logic App with a managed identity and RBAC permissions so it can modify network resources on your behalf.
- Design and trigger a workflow that injects an outbound deny rule via the Azure Resource Manager API, blocking a VM's traffic to a specific website.
- Secret Mission: Made the workflow event-driven using NSG flow logs, Log Analytics, and Azure Monitor alerts to automatically trigger the block when suspicious outbound traffic is detected.
Ready to quiz yourself?