How to Fix Azure Database for PostgreSQL Flexible Server Stuck in Starting State: Step-by-Step Guide
How to Fix Azure Database for PostgreSQL Flexible Server Stuck in Starting State: Step-by-Step Guide
The server has been in Starting for hours. The portal Stop button is greyed out. Every CLI command returns ServerBusyWithOtherOperation. Nothing changed in your automation. This is a platform-side control-plane lock — here is exactly what to do, in order, right now.
Why This Happens — The Control-Plane Lock Explained
Azure Database for PostgreSQL Flexible Server is a managed PaaS service. Behind every start, stop, scale, or configuration change visible in the portal is a workflow job managed by the Azure control plane. These jobs are sequenced — only one management operation can run on a server at a time. When a job completes successfully or fails cleanly, the server transitions to a stable state (Ready, Stopped) and the next operation can be accepted.
The problem occurs when an internal operation fails in a way that does not trigger a clean state transition. The job is neither completed nor properly cancelled — it remains in a running state internally, even though it has clearly stalled. The server's provisioning state is whatever it was when the job failed to transition: in your case, Starting. The Resource Manager layer sees the server as busy and rejects all subsequent operations with SeverBusyWithOtherOperation.
A daily stop/start cycle is particularly susceptible to this because it means the server goes through a full start and stop workflow every day. Any maintenance window, background patching, or storage housekeeping that Azure runs on the server can collide with the automation's start attempt. When both operations try to execute around the same time and one of them fails to complete, the lock is held and the next morning's start attempt is blocked before it even begins — which is why the error appears immediately rather than after a delay.
Documented underlying causes that have been found during Microsoft backend investigations include: a 100% CPU utilisation state that prevented the PostgreSQL process from starting; inactive logical replication slots that consumed all disk space with WAL accumulation; a corrupt reserved space file that required backend removal; an expired internal SAS key that prevented storage mount; and a stalled sidecar agent process. None of these are visible or fixable from the customer side — they require direct access to the underlying infrastructure.
Phase 1: What to Do Right Now — Triage and Evidence Collection
The temptation when a server is stuck is to keep retrying stop and start commands. Resist this. Each retry adds a new failed operation entry to the Activity Log, which makes the timeline harder for support to read and can extend the lock in some scenarios. Do the following checks once, capture the output, and then move directly to opening a support ticket.
Check for an active regional Azure platform incident
Before anything else, check the Azure Status page at azure.status.microsoft.com and look for any active incidents affecting Azure Database for PostgreSQL in your region. Also check Service Health within the Azure portal (search for "Service Health" in the portal) — this shows incidents specific to your subscription and region that may not appear on the public status page. If there is an active incident, your server will recover when the incident is resolved. Document the incident details for your records.
Also check the Resource Health blade on your PostgreSQL Flexible Server resource (left navigation → Help → Resource Health). This shows platform-initiated events including unplanned downtime, maintenance windows, and any PlatformInitiated events that align with the time the server got stuck.
Collect the Activity Log — get the correlation ID before opening the ticket
Navigate to your PostgreSQL Flexible Server in the portal → Monitoring → Activity log. Look at the entries around the time the server got stuck. Find the operation that was in progress (likely "Start server" or "Update server") and click on it. Copy the following exactly — these are the items that allow the backend team to locate and cancel the specific stuck internal job instantly:
- Operation name (e.g. "Start server", "Update server")
- Correlation ID — a GUID, visible in the operation details pane
- Operation ID (sometimes listed separately)
- Timestamp of when the operation started
- Status (In Progress / Failed / Accepted)
If the Activity Log shows no in-progress operations for the server, note this explicitly — it means the stuck internal operation is not being reflected in the customer-visible logs, which is additional evidence of a backend control-plane issue.
RESOURCE_GROUP="your-resource-group"
SUBSCRIPTION_ID="your-subscription-id"
# Get the full resource ID for Activity Log queries RESOURCE_ID=$(az postgres flexible-server show \
--name "${SERVER_NAME}" \
--resource-group "${RESOURCE_GROUP}" \
--query id -o tsv)
# Get current server state az postgres flexible-server show \
--name "${SERVER_NAME}" \
--resource-group "${RESOURCE_GROUP}" \
--query "{State:state, SKU:sku.name, Version:version, HAMode:highAvailability.mode}" \
--output json
# Get Activity Log events for this server (last 24 hours) # COPY THE CORRELATION ID from the output — it's the most critical item for support az monitor activity-log list \
--resource-id "${RESOURCE_ID}" \
--max-events 30 \
--query "[].{Time:eventTimestamp, Operation:operationName.value, Status:status.value, CorrelationId:correlationId, Caller:caller}" \
--output table
# Attempt ONE stop command to check if any management operations work # Run this ONCE only — do not retry if it fails az postgres flexible-server stop \
--name "${SERVER_NAME}" \
--resource-group "${RESOURCE_GROUP}" 2>&1
# If this returns SeverBusyWithOtherOperation, capture the full error output # Do NOT run again — move to opening a support ticket
Run the built-in diagnostics
Navigate to your server → Help → Diagnose and solve problems. Search for Long-Running Operations and run it if available — this sometimes surfaces hidden ARM or control-plane operations that are not visible in the Activity Log. Also run Availability and connectivity diagnostics. Screenshot or save the results. Even if the diagnostics do not resolve the issue, they provide additional evidence for the support ticket and may identify a secondary factor (such as storage utilisation or CPU exhaustion) that contributed to the stuck state.
Check storage utilisation — rule out a disk-full contributing factor
If you can get any metrics from the server (some metrics continue to be collected even when the server is stuck), check storage utilisation. Storage at or near 100% is a confirmed root cause in documented cases — the server cannot complete the start sequence if it cannot write to disk. If your server was near its storage limit before getting stuck, this is important context for the support ticket and the backend team may need to address it before forcing the server back to a stable state.
Phase 2: Opening the Support Ticket — What to Include for the Fastest Resolution
When a Flexible Server is stuck in a state that cannot be cleared from the portal or CLI, only the Microsoft Azure Database for PostgreSQL engineering team can intervene. The fastest path is a support ticket with the right information included from the start — every missing detail means a follow-up request and hours of additional delay. The template below includes everything the backend team needs to locate the stuck operation and act on it immediately.
Open a Severity A / P1 support request via the Azure portal
Navigate to aka.ms/azuresupport or search for Help + support in the Azure portal. Click New support request. Set the following fields: Issue type: Technical. Subscription: the subscription containing the stuck server. Service: Azure Database for PostgreSQL. Problem type: Server Administration. Problem subtype: Server is stuck in Starting/Stopping state. Severity: A (Critical — production system unavailable, no workaround). If this is a recurring issue on the same server, explicitly state that in the subject line: this escalates the ticket to the team that can investigate the recurrence pattern as well as clear the immediate stuck state.
Include the complete information in the ticket description — use this template
Use the template below. Every field matters — the Correlation ID in particular allows the backend team to locate the specific stuck workflow immediately, without needing to search manually through internal logs. Copy it exactly and fill in your values.
## SEVERITY: A — Production database unavailable, no workaround available
## SERVER DETAILS
- Server Name: [your-server-name]
- Resource Group: [your-resource-group]
- Subscription ID: [your-subscription-id]
- Region: [e.g. West Europe, UK South]
- SKU / Tier: [e.g. Standard_D4ds_v5, General Purpose]
- PostgreSQL Version: [e.g. 16]
- High Availability: [Enabled / Disabled]
- Read Replicas: [Yes / No]
## CURRENT STATE
- Server provisioning state: Starting (stuck)
- Duration stuck: [e.g. since 08:12 UTC on 8 July 2026]
- Impact: Database unavailable, all application connections failing
## THIS IS A RECURRING ISSUE
- First occurrence: [date] — resolved by backend/platform team
- Second occurrence: [date — today]
- Root cause provided for first occurrence: [if known]
- Pattern: server is stopped daily and started again via automation
- Nothing changed in our configuration between occurrences
## CORRELATION IDs FROM ACTIVITY LOG
- Start operation Correlation ID: [PASTE GUID HERE]
- Start operation timestamp: [e.g. 2026-07-08T08:12:43Z]
- Any subsequent failed operation Correlation ID: [PASTE IF AVAILABLE]
## WHAT WE TRIED
- Automation start attempt: failed with SeverBusyWithOtherOperation
- Manual az postgres flexible-server stop: failed with SeverBusyWithOtherOperation
- Did NOT retry further to avoid additional lock conflicts
- Portal Stop button: greyed out
- Resource Health: [paste any PlatformInitiated events shown]
- Azure Status: [no active incident / active incident reference]
## EXACT CLI ERROR
(SeverBusyWithOtherOperation) Cannot perform 'Start' server operation because
server '[server-name]' is busy processing other operation.
## REQUEST
1. Clear/cancel the stuck backend control-plane operation holding the server
2. Force the server back to a stable state (Stopped or Ready)
3. Investigate root cause of the recurring nature (2nd occurrence same server)
4. Advise on automation pattern changes to reduce recurrence probability
## AVAILABLE FOR ESCALATION
We can provide full CLI output, automation logs, and Activity Log exports
on request. Available for a Teams/call session if needed for backend investigation.
Phase 3: What to Do While Waiting for Support to Respond
Once the support ticket is open, there is nothing additional you can do to accelerate the backend operation resolution — retrying commands will not help. Use this time productively to prepare for recovery and to document the incident for the recurrence investigation.
Option A — Restore from PITR to a new server (if downtime is unacceptable)
If your business cannot tolerate the downtime until support resolves the stuck state, and you have point-in-time restore (PITR) enabled, you can restore the database to a new server from a recent backup. This gives you a working database quickly while Microsoft resolves the original stuck server on the backend. Note: the restored server will have a different hostname and connection string — all applications will need to be updated to point to the new server.
# Get available restore points (earliest + latest) az postgres flexible-server show \
--name "${SERVER_NAME}" \
--resource-group "${RESOURCE_GROUP}" \
--query "{EarliestRestore:earliestRestoreDate, CreateTime:createTime}" \
--output json
# Restore to a new server from a point just before the server got stuck # Adjust --restore-time to a timestamp known to be before the issue started az postgres flexible-server restore \
--name "${SERVER_NAME}-restored" \
--resource-group "${RESOURCE_GROUP}" \
--source-server "${SERVER_NAME}" \
--restore-time "2026-07-08T07:00:00Z" \
--sku-name "Standard_D4ds_v5" \
--tier "GeneralPurpose"
# IMPORTANT: if the restore itself fails with LocationRestricted or management errors, # include this failure in your support ticket — it means the lock is affecting PITR too # and the backend team must restore on your behalf
Phase 4: After Recovery — Hardening the Daily Stop/Start Automation
Once Microsoft support clears the stuck state and the server returns to a stable state (Stopped or Ready), do not simply restart the automation and carry on. A recurring occurrence on the same server — as described in this incident — means something in the stop/start cycle is contributing to the control-plane lock. The following changes reduce the probability of recurrence significantly.
1 — Add a state verification step before and after each stop/start
The automation should verify the server is in the expected state before issuing the next command. Starting a server that is already in a transitional state is a primary cause of lock collisions.
# Hardened PostgreSQL Flexible Server daily start automation # Includes: pre-start state check, post-start verification, single-attempt guard
SERVER_NAME="your-server-name"
RESOURCE_GROUP="your-resource-group"
MAX_WAIT_SECONDS=300 # 5 minutes max wait for state confirmation
POLL_INTERVAL=15 # check every 15 seconds
# Get current server state get_server_state() {
az postgres flexible-server show \
--name "${SERVER_NAME}" \
--resource-group "${RESOURCE_GROUP}" \
--query state -o tsv 2>/dev/null
}
CURRENT_STATE=$(get_server_state)
echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) — Server state before start attempt: ${CURRENT_STATE}"
# Only attempt start if server is in Stopped state # If stuck in Starting/Stopping: do NOT attempt — alert and stop if [[ "${CURRENT_STATE}" == "Starting" ]] || [[ "${CURRENT_STATE}" == "Stopping" ]]; then
echo "ERROR: Server is stuck in ${CURRENT_STATE}. Do NOT attempt start."
echo "ACTION REQUIRED: Open a support ticket — backend intervention needed."
exit 1
fi
if [[ "${CURRENT_STATE}" == "Ready" ]]; then
echo "Server is already Ready. No start needed."
exit 0
fi
if [[ "${CURRENT_STATE}" != "Stopped" ]]; then
echo "ERROR: Unexpected server state: ${CURRENT_STATE}. Aborting."
exit 1
fi
# Issue start ONCE — never retry automatically on SeverBusyWithOtherOperation echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) — Issuing start command"
START_OUTPUT=$(az postgres flexible-server start \
--name "${SERVER_NAME}" \
--resource-group "${RESOURCE_GROUP}" 2>&1)
if echo "${START_OUTPUT}" | grep -q "SeverBusyWithOtherOperation"; then
echo "ERROR: SeverBusyWithOtherOperation — platform-side lock detected"
echo "DO NOT RETRY — open a support ticket immediately"
echo "${START_OUTPUT}"
exit 2 # Exit code 2 = needs support escalation
fi
# Wait for server to reach Ready state ELAPSED=0
while [[ $ELAPSED -lt $MAX_WAIT_SECONDS ]]; do
STATE=$(get_server_state)
echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) — Current state: ${STATE} (${ELAPSED}s elapsed)"
if [[ "${STATE}" == "Ready" ]]; then
echo "Server is Ready. Start complete."
exit 0
fi
sleep $POLL_INTERVAL
ELAPSED=$((ELAPSED + POLL_INTERVAL))
done
echo "ERROR: Server did not reach Ready state within ${MAX_WAIT_SECONDS}s"
echo "Current state: $(get_server_state)"
echo "ACTION REQUIRED: Check Azure portal and open support ticket if still Starting"
exit 3 # Exit code 3 = timeout waiting for Ready state
2 — Add a delay between the stop and the next day's start
If the stop command is issued at 18:00 and the start command the next morning is at 08:00, that is a 14-hour gap which is usually sufficient. However, if your automation stops the server and then starts it again within a short window (as a test, or due to a retry), the stop may not have fully completed on the backend before the start is issued. Add a minimum 5-minute delay after a stop completes before considering any start attempt.
3 — Monitor storage utilisation proactively
Set up an Azure Monitor alert on your PostgreSQL Flexible Server for storage_percent greater than 80%. Storage filling to 100% is a confirmed root cause of stuck starts. The alert gives you time to increase storage allocation before the server gets stuck.
4 — Consider moving from a burstable (B-series) SKU to General Purpose for daily stop/start workloads
Burstable instances accumulate CPU credits when idle. After an extended stop period, the credit balance may be sufficient. However, if the server was stopped while under load (credits depleted), the next start attempt may run into CPU throttling before the PostgreSQL startup sequence completes. General Purpose (D-series) instances have dedicated vCores and are not subject to the credit model — they are more reliable for scheduled stop/start cycles.