Back to blog
AzureBest PracticesCloud SecurityNetworking

NSG Allows Public SMTP: Closing Port 25 Exposure on Azure

Learn why an Azure NSG rule allowing public SMTP on port 25 is risky, how to remediate it with CLI and Terraform, and how to prevent it with Azure Policy.

TL;DR

This check flags Azure Network Security Group rules that allow inbound SMTP (port 25) from the public internet. Exposed SMTP turns your VMs into spam relay targets and often leads to outbound mail being blocked by Azure. Restrict the source to known mail relays or remove the rule entirely.

Port 25 is one of those ports that sticks around in network rules long after anyone remembers why it was opened. Maybe a VM ran Postfix during a migration, or someone copied an NSG template from a Stack Overflow answer. Either way, an NSG rule that allows SMTP from 0.0.0.0/0 (or Azure's Internet service tag) is a liability, and the nsg_opensmtp check exists to catch it before someone else does.


What this check detects

The check scans every Network Security Group in your subscription and looks for inbound security rules that meet all of these conditions:

  • Direction is Inbound
  • Access is Allow
  • Protocol is TCP (or *)
  • Destination port includes 25, either directly, as part of a range, or via a wildcard
  • Source is a public address, meaning 0.0.0.0/0, *, or the Internet service tag

If a rule matches, Lensix raises a finding against that NSG. SMTP on port 25 is plaintext by default and carries no authentication at the network layer, so any host on the internet that can reach the port can attempt to talk to whatever is listening behind it.

Note: Port 25 is the classic server-to-server mail transfer port. Modern mail submission from clients uses port 587 (with STARTTLS) or 465 (implicit TLS). If you see port 25 open to the internet on a general-purpose VM, it is almost always a mistake rather than a real mail server.


Why it matters

An open SMTP port is not just a checkbox failure. It maps to concrete, expensive problems.

1. Open relay and spam abuse

If a misconfigured mail daemon is listening behind that port, attackers will find it through automated scanning and use it to relay spam. Spammers actively scan IPv4 space for open port 25 because a working relay is a free, disposable sending platform. Once your VM's public IP starts sending spam, it lands on blocklists like Spamhaus, and any legitimate mail from that IP stops being delivered.

2. Reputation damage on shared Azure IP space

Azure public IPs are recycled. If your VM gets blocklisted while abusing port 25, the next tenant who inherits that IP starts with a poisoned reputation. Microsoft takes this seriously, which is why outbound port 25 is throttled or blocked on most Azure subscriptions by default.

Warning: Azure blocks outbound TCP 25 for most subscription types created after late 2017, and for many MSDN and trial subscriptions entirely. Even if you intend to run a real mail server, you generally cannot send mail directly on port 25 without going through a managed relay like SendGrid, Mailgun, or Amazon SES. An open inbound port 25 rule with no working outbound path is almost always pure exposure with zero benefit.

3. Reconnaissance and exploitation

Even without an open relay, an exposed SMTP service leaks information. Banner grabbing reveals the mail software and version, which helps attackers fingerprint your stack and look for known CVEs. VRFY and EXPN commands on older configs can enumerate valid user accounts. None of this is information you want handed to anyone who runs a port scan.


How to fix it

The fix is to stop allowing port 25 from the public internet. You have three realistic options, in order of preference.

Step 1: Find the offending rule

List inbound rules across an NSG and spot anything touching port 25 from a public source:

az network nsg rule list \
  --resource-group my-rg \
  --nsg-name my-nsg \
  --query "[?direction=='Inbound' && access=='Allow' && (destinationPortRange=='25' || destinationPortRange=='*')].{name:name, port:destinationPortRange, source:sourceAddressPrefix}" \
  --output table

Step 2a: Delete the rule (best option)

If nothing on this VM is supposed to receive internet mail, just remove the rule.

Danger: Deleting an NSG rule changes live network behavior immediately. Confirm no production traffic depends on inbound port 25 before running this. For a true mail server, deleting this rule will stop inbound mail delivery.

az network nsg rule delete \
  --resource-group my-rg \
  --nsg-name my-nsg \
  --name allow-smtp-inbound

Step 2b: Restrict the source to known relays

If you genuinely need to receive mail from a specific upstream relay or a partner system, narrow the source from Internet to that explicit address range.

az network nsg rule update \
  --resource-group my-rg \
  --nsg-name my-nsg \
  --name allow-smtp-inbound \
  --source-address-prefixes 203.0.113.0/24 \
  --destination-port-ranges 25

Step 2c: Use a managed mail service instead

For outbound mail, route through a managed provider so you never expose or rely on raw port 25. This removes the need for the inbound rule entirely in most architectures and sidesteps Azure's outbound port 25 block.

Tip: If your application only needs to send email, you do not need any inbound SMTP rule at all. Use Azure Communication Services Email, SendGrid, or another API-based provider over HTTPS (port 443), and close port 25 completely.

Fixing it in Terraform

If your NSGs are managed as code, fix the source there so the change does not get reverted on the next apply. Replace the wildcard source with a specific prefix, or remove the rule block:

resource "azurerm_network_security_rule" "smtp" {
  name                        = "allow-smtp-inbound"
  priority                    = 200
  direction                   = "Inbound"
  access                      = "Allow"
  protocol                    = "Tcp"
  source_port_range           = "*"
  destination_port_range      = "25"
  # Before: source_address_prefix = "Internet"   # flagged
  source_address_prefix       = "203.0.113.0/24"  # known relay only
  destination_address_prefix  = "*"
  resource_group_name         = azurerm_resource_group.main.name
  network_security_group_name = azurerm_network_security_group.main.name
}

How to prevent it from happening again

A one-time fix is worthless if the next Terraform module or manual change reopens the port. Bake the guardrail into your pipeline and platform.

Azure Policy as a deny gate

Azure Policy can block the creation of NSG rules that allow port 25 from the internet. Use a policy with a deny effect that matches inbound allow rules on port 25 with broad sources:

{
  "if": {
    "allOf": [
      { "field": "type", "equals": "Microsoft.Network/networkSecurityGroups/securityRules" },
      { "field": "Microsoft.Network/networkSecurityGroups/securityRules/direction", "equals": "Inbound" },
      { "field": "Microsoft.Network/networkSecurityGroups/securityRules/access", "equals": "Allow" },
      { "field": "Microsoft.Network/networkSecurityGroups/securityRules/destinationPortRange", "equals": "25" },
      { "anyOf": [
        { "field": "Microsoft.Network/networkSecurityGroups/securityRules/sourceAddressPrefix", "equals": "*" },
        { "field": "Microsoft.Network/networkSecurityGroups/securityRules/sourceAddressPrefix", "equals": "Internet" },
        { "field": "Microsoft.Network/networkSecurityGroups/securityRules/sourceAddressPrefix", "equals": "0.0.0.0/0" }
      ]}
    ]
  },
  "then": { "effect": "deny" }
}

Catch it in CI before deploy

Run a static scan against your IaC in the pull request so the bad rule never reaches Azure. Tools like Checkov, tfsec, and Terrascan all flag public SMTP exposure. A minimal pipeline step:

# Fails the build if any high-severity NSG misconfig is found
checkov -d ./infra --framework terraform --compact

Tip: Pair the deny policy with continuous detection in Lensix. Policy stops new bad rules, and Lensix catches drift, rules created outside your IaC, and anything that predates the policy. The two layers cover each other's gaps.


Best practices

  • Default deny on inbound. Start NSGs from a closed posture and open only the specific ports and sources each workload requires.
  • Never use Internet or * as a source for service ports. Reserve broad sources for genuinely public services like HTTPS load balancers, and even then prefer fronting them with a WAF.
  • Prefer managed email over self-hosted SMTP. Running your own mail server on Azure means fighting the outbound port 25 block, managing TLS, SPF, DKIM, and DMARC, and owning IP reputation. A managed provider removes all of that.
  • Use service tags and ASGs. Application Security Groups let you scope rules to workloads by identity rather than IP ranges, which keeps rules readable and harder to misconfigure.
  • Audit NSGs on a schedule. Rules accumulate. Periodic review, ideally automated, catches the orphaned port 25 rule from a project that shipped two years ago.
  • Enable NSG flow logs. If port 25 is exposed, flow logs tell you whether anything is actually hitting it, which informs how urgently you respond.

The bottom line: there is almost no scenario where a general-purpose Azure VM should accept SMTP from the entire internet. Close the port, route mail through a managed service, and put a policy gate in front of your pipeline so the rule cannot come back.