Blog Post

Azure Database for MySQL Blog
9 MIN READ

Preventing and recovering from accidental deletion of an Azure Database for MySQL flexible server

ramkumarchan's avatar
ramkumarchan
Icon for Microsoft rankMicrosoft
Mar 05, 2025

Accidental deletion of critical Azure resources, such as Azure Database for MySQL flexible servers, can disrupt operations. To help avoid such accidental deletions, you can use a couple of options, including Azure Resource Locks and Azure Policy. This post explains how to implement these mechanisms, and how to revive a dropped MySQL flexible server by using the Azure CLI

Note: You can set the default subscription for all Azure CLI commands mentioned in this article by using the following command az account set --subscription <name or id>.

Preventing accidental deletions 

You can help to prevent the accidental deletion of an Azure Database for MySQL flexible server by using Azure Resource Locks or Azure Policy. 

Using Azure Resource Locks 

To protect your Azure resources, you can use Resource Locks, which you can apply at both the resource and resource group levels. When you lock a resource group, you add an additional layer of protection by ensuring that all resources within the group are safeguarded against deletion.

Note: A resource group lock applies to all resources in the group, including virtual machines, storage accounts, and other services. In addition, new resources added to the resource group are automatically protected by the delete lock.

Protecting a MySQL flexible server

To lock a specific MySQL flexible server, run the following command: 

az lock create \  
    --name "PreventDeleteLock" \  
    --resource-group <RESOURCE_GROUP_NAME> \  
    --resource-name <MYSQL_SERVER_NAME> \  
    --resource-type "Microsoft.DBforMySQL/flexibleServers" \  
    --lock-type CanNotDelete 

To verify locks on a MySQL flexible server, run the following command: 

az lock list \  
    --resource-group <RESOURCE_GROUP_NAME> \  
    --resource-name <MYSQL_SERVER_NAME> \  
    --resource-type "Microsoft.DBforMySQL/flexibleServers" -o table 

To remove a lock on a MySQL flexible server, run the following command: 

az lock delete \  
    --name "PreventDeleteLock" \  
    --resource-group <RESOURCE_GROUP_NAME> \  
    --resource-name <MYSQL_SERVER_NAME> \  
    --resource-type "Microsoft.DBforMySQL/flexibleServers" 

Protecting a resource group containing a MySQL flexible server 

To lock the entire resource group, run the following command: 

az lock create \ 
    --name "PreventDeleteGroupLock" \ 
    --resource-group <RESOURCE_GROUP_NAME> \ 
    --lock-type CanNotDelete 

To verify locks on a resource group, run the following command: 

az lock list --resource-group <RESOURCE_GROUP_NAME> -o table 

To remove a lock on a resource group, run the following command: 

az lock delete \  
    --name "PreventDeleteGroupLock" \  
    --resource-group <RESOURCE_GROUP_NAME>


Note that if you attempt to delete a flexible server that has a CanNotDelete lock, the following error message appears:
 

The scope '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/sampleRG/providers/Microsoft.DBforMySQL/flexibleServers/sampleMySQL' cannot perform delete operation because following scope(s) are locked: '/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/sampleRG/providers/Microsoft.DBforMySQL/flexibleServers/sampleMySQL'. Please remove the lock and try again. 

If you attempt to delete a resource group that has a CanNotDelete lock, the following error message appears:

The resource group sampleRG is locked and can't be deleted. Click here to manage locks for this resource group. 

Best practices for using Resource Locks 

When working with resource locks, be sure to keep the following best practices in mind. 

  • Apply locks strategically: Lock critical resources individually and use group-level locks for comprehensive protection. 
  • Use Role-Based Access Control (RBAC): Ensure only authorized personnel can remove or modify locks. 
  • Automate lock management: Incorporate lock creation into deployment scripts or pipelines to enforce consistency. 
  • Document locks: Maintain an updated inventory of locks you've applied to prevent confusion among team members. 

Applying delete locks to individual MySQL flexible servers and their resource groups helps ensure that critical Azure resources are not accidentally deleted. While locks provide protection, implement them carefully to avoid disruptions in resource management workflows

Note: Creating or deleting a lock requires having access to the Microsoft.Authorization/* or Microsoft.Authorization/locks/* actions. The Owner and User Access Administrator roles are the two built-in roles granted those actions.

You can also implement delete locks using the Azure portal, as part of an ARM template, PowerShell script, or REST API. For more information, see the article Lock your resources to protect your infrastructure.

Using Azure Policy

Azure Policy provides a governance framework to enforce rules, including preventing the deletion of resources. Tags allow you to categorize Azure resources based on metadata, such as Environment, Project, or Owner. By combining tags with Azure Policy, you can enforce governance selectively, targeting only resources with specific tags. This section explains how to create and assign an Azure Policy that blocks deletion of MySQL flexible servers by using specific tags.

Creating and assigning the policy

To create and assign the Policy, use the following guidance.

1. Define a custom Azure policy for tagged resources

To block deletion for MySQL flexible servers that have specific tags, create a custom policy definition. The policy should check for a specific tag key-value pair (action:DONOTDELETE) and deny delete operations if the resource is a MySQL flexible server. To block the deletion of the parent resource group, set the cascadeBehaviors parameter to Deny.

To define a custom policy for tagged resources, create a JSON file (mysql-policy.json) with the following content: 

{
  "if": { 
    "allOf": [ 
      { 
        "field": "type", 
        "equals": "Microsoft.DBforMySQL/flexibleServers" 
      }, 
      { 
        "field": "tags.action", 
        "equals": "DONOTDELETE" 
      } 
    ] 
  }, 
  "then": { 
    "effect": "denyAction", 
    "details": { 
      "actionNames": [ 
        "delete" 
      ], 
      "cascadeBehaviors": { 
        "resourceGroup": "deny" 
      } 
    } 
  } 
}

2. Create the policy definition

To create the policy definition, at the Azure CLI, run the following command: 

az policy definition create \ 
    --name "PreventDeletionOfTaggedMySQLFlexibleServers" \ 
    --description "Prevents deletion of MySQL Flexible Servers with specific tags" \ 
    --display-name "Prevent Deletion of Tagged MySQL Flexible Servers" \ 
    --rules "mysql-policy.json" \ 
    --mode Indexed 

3. Assign the policy

Next, you need to assign the policy to a subscription or resource group and specify the target tag. To assign the Policy at the subscription level, run the following command

az policy assignment create \ 
    --name "PreventDeletionOfTaggedMySQLFlexibleServersAssignment" \ 
    --policy "PreventDeletionOfTaggedMySQLFlexibleServers" \ 
    --scope "/subscriptions/<SUBSCRIPTION_ID>"

To assign the policy at the resource group level, run the following command: 

az policy assignment create \ 
    --name "PreventDeletionOfTaggedMySQLFlexibleServersAssignment" \ 
    --policy "PreventDeletionOfTaggedMySQLFlexibleServers" \ 
    --scope "/subscriptions/<SUBSCRIPTION_ID>/resourceGroups/<RESOURCE_GROUP_NAME>"

To exclude specific resources from the policy, during policy assignment, run the command with the --not-scopes parameter: 

az policy assignment create \ 
    --name "PreventDeletionOfTaggedMySQLFlexibleServersAssignment" \ 
    --policy "PreventDeletionOfTaggedMySQLFlexibleServers" \ 
    --scope "/subscriptions/<SUBSCRIPTION_ID>/resourceGroups/<RESOURCE_GROUP_NAME>" \ 
    --not-scopes "/subscriptions/<SUBSCRIPTION_ID>/resourceGroups/<RESOURCE_GROUP_NAME>/providers/Microsoft.DBforMySQL/flexibleServers/<SERVER_NAME>" 

4. Verify the policy

To verify the policy, run the following command: 

az policy assignment list --scope "/subscriptions/<SUBSCRIPTION_ID>/resourceGroups/<RESOURCE_GROUP_NAME>" 

To ensure that the policy is correctly assigned and working, attempt to delete a tagged MySQL flexible server. An error message similiar to the following should appear:

Deletion of resource ‘sampleMySQL’ was disallowed by policy.

To check policy compliance via the Azure portal, navigate to Azure Portal > Policy > Compliance, and then review the compliance state for the assigned policy.

5. Delete the policy (if necessary)

If you need to delete the policy, run the following commands: 

az policy assignment delete –-name "PreventDeletionOfTaggedMySQLFlexibleServersAssignment" --scope "/subscriptions/<SUBSCRIPTION_ID>/resourceGroups/<RESOURCE_GROUP_NAME>" 
az policy definition delete –-name "PreventDeletionOfTaggedMySQLFlexibleServers"  

Advantages of using tag-based policies 

Using tag-based policies offers the following advantages. 

  • Selective Enforcement: Protect only resources with specific tags, minimizing unintended restrictions. 
  • Flexibility: Easily manage resources by updating their tags. 
  • Scalability: Apply policies across subscriptions or resource groups without affecting untagged resources. 

Tag-based Azure policies provide a powerful mechanism to prevent accidental deletion of MySQL Flexible Servers. By leveraging tags, you can enforce governance in a targeted and scalable manner. This approach ensures that your critical resources remain protected while maintaining flexibility for your operations.

For more information, see the Azure Policy documentation. 

Recovering an accidentally deleted MySQL flexible server 

An Azure Database for MySQL flexible server takes snapshot backups of data files and stores them in local redundant storage. You can use these backups to restore a server to any point-in-time during your configured backup retention period. The default backup retention period is seven days. You can optionally configure the database backup retention period from 1 to 35 days.  

Note: All backups are encrypted using AES 256-bit encryption for the data stored at rest. 

You can only access and restore the server backup from the Azure subscription in which the server initially resided. To recover a deleted Azure Database for MySQL flexible server resource within the backup retention period, take the following recommended steps.

Important: You can only access and restore a deleted MySQL flexible server if the server backup has not been deleted from the system. 

Before you restore a deleted Azure Database for MySQL Flexible Server instance, collect the following information: 

  • The Azure Subscription Id and Resource group hosting the original server. 
  • The location in which the original server was created. 
  • The timestamp showing when the original server was dropped. To get this information, query the Activity Log of the subscription by running the following command: 
az monitor activity-log list \
--subscription "<SUBSCRIPTION_ID>" \
--start-time "<StartTimeStampInUTC>" \
--end-time "<EndTimeStampInUTC>" \
--query "[?operationName.value=='Microsoft.DBforMySQL/flexibleServers/delete'].{ResourceId : resourceId, DeleteTimeStamp : submissionTimestamp}" \
--status "Succeeded" \
--output table 

Note: Set StartTimeStampInUTC and EndTimeStampInUTC with approximate values of when you might have dropped the server. StartTimeStampInUTC and EndTimeStampInUTC should be in ISO 8601 format

After you have this information, trigger a Point-in-Time Restore by running the following command: 

az rest --method put \
--url "https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.DBforMySQL/flexibleServers/{OriginalServerName}?api-version=2024-06-01-preview" \
--headers '{"Content-Type": "application/json"}' \
--body ' { 
    "location": "<Dropped Server Location>", 
    "properties": 
        { 
            "restorePointInTime": "<DeleteTimeStamp> - 15 minutes", 
            "createMode": "PointInTimeRestore", 
            "sourceServerResourceId": "/subscriptions/<SubscriptionId>/resourceGroups/<ResourceGroup>/providers/Microsoft.DBforMySQL/flexibleServers/<OriginalServerName>" 
        } 
}' 

If the restore request is submitted successfully, the following response appears:  

{ 
  "operation": "RestoreSnapshotManagementOperation", 
  "startTime": "2024-12-03T22:27:45.937Z" 
}   

Server creation time varies depending on the database size and compute resources provisioned on the original server. To monitor the restore status, run the following command: 

az monitor activity-log list \
--subscription "<SUBSCRIPTION_ID>" \
--resource-group <RESOURCE_GROUP_NAME> \
--offset 1h \
--query "[?operationName.value=='Microsoft.DBforMySQL/flexibleServers/write']" 

Restoring a dropped virtual network enabled server involves specifying additional network properties such as the delegated subnet resource ID and the private DNS zone Azure Resource Manager resource ID. Here is an example request to restore your server with the necessary network configurations.  

az rest --method put \
–url " https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/sampleRG/providers/Microsoft.DBforMySQL/flexibleServers/samplemysql?api-version=2024-06-01-preview" \
--headers '{"Content-Type": "application/json"}' \
--body ' 
{ 
"location": "Canada Central",  
"properties":  
    {  
        "restorePointInTime": "2024-12-03T21:59:29Z",  
        "createMode": "PointInTimeRestore",  
        "sourceServerResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/sampleRG/providers/Microsoft.DBforMySQL/flexibleServers/samplemysql", 
        "network": { 
            "delegatedSubnetResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/sampleRG/providers/Microsoft.Network/virtualNetworks/azure_mysql_vnet/subnets/azure_mysql_subnet", 
            "privateDnsZoneResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/sampleRG/providers/Microsoft.Network/privateDnsZones/ samplemysql.private.mysql.database.azure.com", 
            "publicNetworkAccess": "Disabled" 
        } 
    } 
}' 

Be sure to try an earlier timestamp for “restorePointInTime” if the following message appears in the Activity Log:  

{ 
    "status": "Failed", 
    "error": { 
        "code": "ResourceOperationFailure", 
        "message": "The resource operation completed with terminal provisioning state 'Failed'.", 
        "details": [ 
            { 
                "code": "RestoreSourceServerNotExist", 
                "message": "Restore source server 'sampleMySQL' does not exist for requested point in time '12/3/2024 9:59:29 PM' of location 'canadacentral'." 
            } 
        ] 
    } 
} 

Open a support ticket for further troubleshooting if the following message appears in the Activity Log: 

{ 
    "status": "Failed", 
    "error": { 
        "code": "ResourceOperationFailure", 
        "message": "The resource operation completed with terminal provisioning state 'Failed'.", 
        "details": [ 
            { 
                "code": "InternalServerError", 
                "message": "An unexpected error occured while processing the request. Tracking ID: '48e52499-3857-4ea6-aaf1-75586a572601'" 
            } 
        ] 
    } 
} 

Summary 

You should now have all the information you need to prevent and recover from the accidental deletion of an Azure Database for MySQL flexible server. If you have any queries or suggestions, please let us know by leaving a comment below or by contacting directly us at AskAzureDBforMySQL@service.microsoft.com. 

Please provide any feedback related to the product in the Azure Database for MySQL Community. 

Updated Mar 05, 2025
Version 1.0
No CommentsBe the first to comment