This check flags Azure Network Security Group rules that expose MySQL (port 3306) to the public internet. An open database port invites brute-force attacks and data theft, so lock the rule down to known private IP ranges or remove public access entirely.
Databases are not meant to face the internet. Yet it happens constantly: someone spins up a MySQL server, opens 3306 to test a connection from their laptop, and the rule never gets tightened. Lensix raises nsg_openmysql when it finds an Azure Network Security Group (NSG) rule that allows inbound traffic to port 3306 from public sources. This post explains what that means, why it is dangerous, and how to fix and prevent it.
What this check detects
The check inspects the inbound security rules attached to your NSGs. It flags any rule that meets all of the following conditions:
- Direction is Inbound
- Access is Allow
- The destination port range includes 3306 (the default MySQL port)
- The source address prefix is a public range, such as
*,0.0.0.0/0,Internet, or any broad CIDR that resolves to public IP space
Note: An NSG is Azure's stateful packet filter. It attaches to a subnet or a network interface and controls which traffic can reach your resources. A single overly permissive rule on a subnet can expose every VM and managed instance inside it.
It does not matter whether you actually run MySQL behind that port. If the rule says 3306 is open to the world, the check fires. That is intentional, because an open port is an invitation regardless of what is listening today.
Why it matters
Port 3306 is one of the most heavily scanned ports on the internet. Automated bots sweep public IP ranges around the clock looking for exactly this: a MySQL endpoint with no firewall in front of it. Once they find one, the attack is cheap and well understood.
Brute-force and credential stuffing
MySQL authentication is reachable the moment the port is open. Attackers run dictionary attacks against the root account and common usernames. If you have a weak or reused password, they are in. Tools like hydra and custom scripts can attempt thousands of logins per minute.
Exploitation of unpatched servers
MySQL and its forks have had authentication bypass and remote code execution vulnerabilities over the years. An exposed instance that is even slightly behind on patches becomes a direct entry point into your network.
Data exfiltration and ransom
Once inside, an attacker can dump entire schemas, drop tables, or encrypt data and leave a ransom note. The "MySQL ransom" pattern, where databases are wiped and replaced with a payment demand, has hit thousands of exposed instances.
Danger: An internet-facing MySQL port combined with default credentials or a missing patch is one of the fastest paths to a full data breach. Treat any finding from this check as high priority.
The business impact is concrete: regulatory exposure under GDPR, HIPAA, or PCI-DSS, breach notification costs, and reputational damage. Most compliance frameworks explicitly prohibit unrestricted inbound access to database ports.
How to fix it
The goal is to stop allowing 3306 from public sources. You have a few options depending on how clients need to reach the database.
Step 1: Find the offending rule
List the rules on the NSG and look for the one allowing 3306:
az network nsg rule list \
--resource-group myResourceGroup \
--nsg-name myNsg \
--query "[?destinationPortRange=='3306' || contains(destinationPortRanges, '3306')]" \
--output table
Step 2: Restrict the source to known IPs
If a specific office, VPN, or jump host needs access, scope the source to that CIDR instead of the internet:
az network nsg rule update \
--resource-group myResourceGroup \
--nsg-name myNsg \
--name Allow-MySQL \
--source-address-prefixes 203.0.113.10/32 \
--destination-port-ranges 3306
Warning: Tightening this rule will immediately drop any active connections from sources outside the new range. Confirm which clients legitimately use the database before you change it, so you do not break an application or scheduled job.
Step 3: Or remove public access entirely
If nothing outside Azure should reach the database, delete the public rule:
Danger: Deleting an NSG rule takes effect right away. Make sure no production traffic depends on it before running this command.
az network nsg rule delete \
--resource-group myResourceGroup \
--nsg-name myNsg \
--name Allow-MySQL
From the Azure Portal, the same change is: Network security groups → select your NSG → Inbound security rules → select the MySQL rule → change the Source to IP Addresses with your specific range, or click Delete.
Step 4: Prefer private connectivity
The most durable fix is to not expose the port at all. Reach MySQL through one of these instead:
- Private Endpoint for Azure Database for MySQL, which gives the service a private IP inside your VNet
- VNet peering or VPN/ExpressRoute so on-premises clients connect over private network paths
- Azure Bastion or a jump host for administrative access, rather than opening 3306 broadly
If you run Azure Database for MySQL Flexible Server, disable public network access on the server itself:
az mysql flexible-server update \
--resource-group myResourceGroup \
--name myMysqlServer \
--public-network-access Disabled
Fixing it in infrastructure as code
If you manage NSGs with Terraform, the corrected rule should reference a variable for allowed sources rather than a wildcard:
resource "azurerm_network_security_rule" "mysql" {
name = "Allow-MySQL"
priority = 200
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "3306"
source_address_prefixes = var.allowed_mysql_cidrs # e.g. ["203.0.113.10/32"]
destination_address_prefix = "*"
resource_group_name = azurerm_resource_group.main.name
network_security_group_name = azurerm_network_security_group.main.name
}
Never set source_address_prefix = "*" or "Internet" on a database rule. Keep the allowed list small and reviewed.
How to prevent it from happening again
One-off fixes drift back over time. Build guardrails so a public MySQL rule cannot ship in the first place.
Azure Policy
Azure Policy can audit or deny NSG rules that expose sensitive ports. Use a policy that inspects securityRules and flags any rule where source is */Internet and the destination port covers 3306. Set the effect to Deny in production subscriptions so the rule is rejected at deploy time.
Policy as code in CI/CD
Scan Terraform plans before they apply. Tools like Checkov, tfsec, or Open Policy Agent can catch the wildcard source on a database port:
checkov -d . --framework terraform
Wire this into a pull request check so a non-compliant rule blocks the merge.
Tip: Pair a deploy-time policy with continuous monitoring. CI gates only catch what flows through your pipeline. Resources created manually in the portal or by another team slip past them, so Lensix scanning your live environment closes that gap.
Continuous scanning
Keep nsg_openmysql and related port-exposure checks running on a schedule against every subscription. New rules, copied templates, and emergency changes all introduce risk between deployments. Continuous scanning surfaces them within minutes rather than at the next audit.
Best practices
- Default deny. Start with no inbound access and open only what a specific, documented need requires.
- Never expose database ports to the internet. This applies to MySQL (3306), PostgreSQL (5432), SQL Server (1433), MongoDB (27017), Redis (6379), and others.
- Use private endpoints. For managed databases, private connectivity removes the public attack surface completely.
- Scope sources tightly. A
/32for a jump host beats a broad CIDR. Avoid*andInterneton anything sensitive. - Layer your defenses. NSG rules are one control. Add strong authentication, TLS in transit, and patched servers behind them.
- Review rules regularly. Temporary "test" rules have a way of becoming permanent. Audit them on a cadence and delete what is no longer needed.
- Log and alert. Enable NSG flow logs so you can see who is connecting and spot probing attempts early.
An exposed MySQL port is a small misconfiguration with an outsized blast radius. Tighten the rule, move to private connectivity where you can, and put automated checks in place so the next open port gets caught before an attacker finds it.

