Back to blog
AzureBest PracticesCloud SecurityDatabasesMonitoring & Logging

SQL Server Email Audit Alerts Disabled on Azure: Why It Matters and How to Fix It

Azure SQL Servers without email threat alerts let attacks fire silently. Learn why this matters and how to enable Defender for SQL alerts via CLI, Portal, and Terraform.

TL;DR

This check flags Azure SQL Servers that have no email alerts wired into their Advanced Threat Protection settings, which means SQL injection or anomalous login attempts can fire silently with nobody watching. Fix it by enabling threat detection alerts and adding admin and security team email recipients on the server.

Azure SQL Server ships with a threat detection engine that can spot SQL injection, brute force logins, and access from unusual locations. The catch is that detection without notification is close to useless. If the alerts never reach a human inbox or a monitoring pipeline, a flagged threat just sits in a log that nobody reads until an incident review weeks later.

The sql_noauditalerts check looks at each Azure SQL Server and confirms that security alert policies are configured to email someone when a threat is detected. When no recipients are set, the check fails.


What this check detects

Every Azure SQL logical server has a Microsoft Defender for SQL configuration (formerly called Advanced Threat Protection) and an associated security alert policy. That policy controls:

  • Whether threat detection is enabled at all
  • Which threat types to alert on (SQL injection, anomalous client login, data exfiltration, and more)
  • Where alert emails go: a list of explicit addresses, and optionally the subscription's account admins

The check fails when the alert policy has no email recipients configured. That means even if Defender catches something, the notification goes nowhere actionable.

Note: There are two layers here that people often confuse. Auditing writes database events to a storage account, Log Analytics workspace, or Event Hub. Threat detection alerts are the proactive notifications built on top of Defender for SQL. This check is about the alert email recipients, not the audit destination itself.


Why it matters

Database servers are a high-value target. They hold the data attackers actually want, and they sit behind application code that frequently has injection flaws. Defender for SQL can flag the early signs of an attack, but only if a person or system acts on that signal.

Consider a realistic chain of events:

  1. An attacker probes a public-facing web app and finds a search field vulnerable to SQL injection.
  2. Defender for SQL detects the malformed queries and raises a "Potential SQL injection" alert.
  3. Because no email recipients are configured, that alert is recorded but never surfaced.
  4. Over the next several days the attacker refines the payload, dumps user tables, and exfiltrates the data.
  5. The breach is discovered later from a third party, long after the window to contain it has closed.

The technical control was already in place. The missing piece was a notification path. That is the gap this check closes.

There is a compliance angle too. Frameworks like PCI DSS, SOC 2, and ISO 27001 expect that security-relevant events generate alerts that responsible parties review. An auditor will ask who gets notified when the database flags suspicious activity. "Nobody" is not an answer that passes.

Warning: Defender for SQL is a paid feature billed per server. Enabling alert recipients on a server where Defender is off will not generate alerts on its own. Make sure Defender for SQL is enabled, and budget for the per-server cost before turning it on across a large fleet.


How to fix it

You need two things in place: Defender for SQL enabled on the server, and a security alert policy with at least one email recipient. Here is how to do both.

Using the Azure CLI

First enable Microsoft Defender for SQL on the server:

az sql server advanced-threat-protection-setting update \
  --resource-group myResourceGroup \
  --name mySqlServer \
  --state Enabled

Then configure the security alert policy with email recipients:

az sql server threat-policy update \
  --resource-group myResourceGroup \
  --server mySqlServer \
  --state Enabled \
  --email-addresses [email protected] [email protected] \
  --email-account-admins Enabled \
  --retention-days 30 \
  --storage-account mystorageaccount

Key flags:

  • --email-addresses takes one or more explicit recipients. Use a distribution list or your SOC inbox, not a single person.
  • --email-account-admins Enabled also notifies subscription account administrators.
  • --storage-account sets where the underlying threat data is retained.

Tip: Point --email-addresses at a shared mailbox or alerting alias that routes into your incident workflow (PagerDuty, Opsgenie, a Teams channel) rather than individual humans. People leave, distribution lists do not, and routing through a tool gives you escalation and acknowledgement out of the box.

Using the Azure Portal

  1. Open your SQL Server (the logical server, not an individual database).
  2. Under Security, select Microsoft Defender for Cloud.
  3. Click Configure next to the Defender for SQL status.
  4. Set Microsoft Defender for SQL to ON.
  5. In Send alerts to, enter your security team email addresses.
  6. Enable Email subscription admins and owners if you want admins notified too.
  7. Save.

Using Terraform

If you manage infrastructure as code, set both the alert policy and Defender on the server resource:

resource "azurerm_mssql_server_security_alert_policy" "sql_alerts" {
  resource_group_name        = azurerm_resource_group.main.name
  server_name                = azurerm_mssql_server.main.name
  state                      = "Enabled"
  email_account_admins       = true
  email_addresses            = [
    "[email protected]",
    "[email protected]",
  ]
  retention_days             = 30
  storage_endpoint           = azurerm_storage_account.audit.primary_blob_endpoint
  storage_account_access_key = azurerm_storage_account.audit.primary_access_key
}

resource "azurerm_mssql_server_microsoft_support_auditing_policy" "defender" {
  # ensure Defender for SQL is enabled alongside the alert policy
}

Note: The alert policy is configured at the server level and applies to all databases on that server. You do not need to repeat it per database, though you can add database-level policies that override the server defaults if a specific database needs different recipients.


How to prevent it from happening again

Manual fixes drift. A new server gets provisioned by a different team next quarter, and the alert recipients never get set. Bake the requirement into your provisioning and review process.

Enforce with Azure Policy

Azure has a built-in policy definition that requires SQL server threat detection to be enabled. Assign it at the subscription or management group level so every new server is evaluated automatically:

az policy assignment create \
  --name 'require-sql-threat-detection' \
  --scope '/subscriptions/<subscription-id>' \
  --policy 'abfb7388-5bf4-4ad7-ba99-2cd2f41cebb9' \
  --params '{ "effect": { "value": "AuditIfNotExists" } }'

The abfb7388 definition is "Advanced Threat Protection types should be set to 'All' in SQL managed instance/server advanced data security settings". Pair it with the audit policies that check for configured email recipients so you catch both the engine and the notification gap.

Warning: Start policy assignments with AuditIfNotExists rather than DeployIfNotExists or Deny. Auditing first lets you see how many existing servers are non-compliant before you risk blocking deployments or auto-deploying settings that conflict with a team's existing configuration.

Gate it in CI/CD

If your SQL servers come from Terraform or Bicep, add a policy-as-code check to the pipeline. With OPA or Conftest you can fail any plan that defines an azurerm_mssql_server without a matching azurerm_mssql_server_security_alert_policy that has email_addresses set. Catching it at the pull request stage is cheaper than catching it in a quarterly audit.

Continuous monitoring

Run the sql_noauditalerts check in Lensix on a schedule so configuration drift is caught within hours, not at the next manual review. A server that had alerts removed during a troubleshooting session, and never restored, is exactly the kind of silent regression continuous scanning is built to catch.


Best practices

  • Route alerts to a system, not a person. Use a shared alias that feeds your incident tooling so notifications survive staff changes and get proper escalation.
  • Enable Defender for SQL fleet-wide, then standardize recipients. Inconsistent recipient lists across servers create blind spots. Define one or two canonical addresses and apply them everywhere.
  • Test the alert path. Configuring recipients does not prove emails arrive. Run a benign anomalous login or use a test query to confirm the notification reaches its destination and is acted on.
  • Keep auditing and threat detection both on. Alerts tell you something happened; the audit log tells you what. You need both for a useful investigation.
  • Review recipients during offboarding. When someone leaves, individual addresses on alert policies become dead ends. Distribution lists avoid this entirely.
  • Set a sensible retention window. Match retention_days to your compliance requirements so threat data is available when you need to investigate after the fact.

Enabling threat detection without a notification path is a common and quiet failure. The fix takes one CLI command, and the prevention takes one policy assignment. Do both, confirm the alert actually lands somewhere a human will see it, and you have closed a gap that attackers count on staying open.