Building a Windows Server Failover Cluster for SQL Server: On-Premises and Azure
The request came in on a Monday: build a two-node Windows Server Failover Cluster to host a new SQL Server 2022 Availability Group. Both nodes were joined to the domain. The account used for the build had local administrator rights on both servers. The Failover Clustering feature was installed. Test-Cluster passed with no errors.
New-Cluster failed immediately. The error message was generic and pointed nowhere useful. After forty minutes the cause was found: the account running the cluster creation had never been granted permission to create a computer object in the Active Directory OU where the cluster name was to be registered. Every checklist the team used said “domain admin or equivalent.” That phrase is both inaccurate and broader than necessary.
The answer
A Windows Server Failover Cluster requires specific Active Directory permissions, specific firewall ports, and Windows configuration that must all be in place before the cluster is created. The cluster creation itself is a single PowerShell command. All of the work is in the prerequisites. Quorum configuration determines how the cluster survives node failures, and the right quorum option depends on the number of nodes, the deployment environment, and what witness resources are available. Azure VM clusters require specific adjustments — cloud witness, static IP constraints, NSG rules — that do not apply on-premises.
What you will learn
- What Windows, hardware, network, and Active Directory prerequisites are required before creating a WSFC
- What specific AD permissions are required, which accounts need them, and how to pre-stage the Cluster Name Object (CNO) manually in environments with restrictive AD delegation
- How to open the required Windows Firewall ports on-premises and configure NSG rules on Azure
- How to install the Failover Clustering feature, run cluster validation, and create the cluster on-premises and on Azure VMs
- What quorum options are available, when to use each, how to configure every option via PowerShell, and what permissions the file share witness requires
- How to validate the cluster after creation
Scope: Windows Server 2016, 2019, and 2022 WSFC in the context of hosting SQL Server Always On Availability Groups. Procedures assume all nodes are joined to the same Active Directory domain. On-premises steps assume physical or Hyper-V virtualised servers. Azure VM steps assume standard SQL Server Marketplace images in the same VNet and subnet unless otherwise noted. Behaviour varies by node count, topology, Windows version, and network environment. Validate all steps in a non-production environment before applying to production.
Prerequisites
Operating system and hardware
Every node that will join the cluster must meet the following before any cluster work begins:
- Windows Server 2016 or later. Windows Server 2022 is recommended for SQL Server 2022 deployments.
- All nodes must run the same operating system version and edition. Mixed-version clusters are technically supported in rolling upgrade scenarios but are not a stable steady state.
- All nodes must be members of the same Active Directory domain. Workgroup clusters are supported in Windows Server 2016 and later but require certificate-based authentication and are out of scope here.
- All nodes must have static IP addresses. Do not use DHCP for cluster node NICs. DHCP-assigned IPs can change on renewal, causing the cluster to lose contact with a node.
- All nodes must be able to resolve each other’s hostnames via DNS bidirectionally before cluster validation begins.
- If shared storage is used (for a disk witness or clustered roles), storage must meet the WSFC disk requirements. For SQL Server AG deployments using automatic or manual seeding, shared storage for database files is not required and should not be attached to the cluster unless explicitly needed.
Verify DNS resolution bidirectionally on each node before proceeding:
# Run on SQL-NODE1 — resolves SQL-NODE2
Resolve-DnsName SQL-NODE2.contoso.com
[System.Net.Dns]::GetHostAddresses('SQL-NODE2.contoso.com')
# Run on SQL-NODE2 — resolves SQL-NODE1
Resolve-DnsName SQL-NODE1.contoso.com
[System.Net.Dns]::GetHostAddresses('SQL-NODE1.contoso.com')
Active Directory and DNS
Before cluster creation:
- All nodes must be domain-joined to the same domain.
- The account used to run
New-Clustermust be a member of the local Administrators group on all cluster nodes. - The account used to run
New-Clustermust have permission to create a computer object in the Active Directory OU where the Cluster Name Object (CNO) will be registered — or the CNO must be pre-staged manually before runningNew-Cluster. See Active Directory preparation below. - DNS must allow secure dynamic updates, or the cluster’s DNS records must be pre-created by an administrator.
Network prerequisites
WSFC requires the following ports to be open bidirectionally between all cluster nodes:
| Traffic type | Protocol | Port | Notes |
|---|---|---|---|
| Cluster node communication | TCP | 3343 | Primary WSFC node-to-node traffic |
| Cluster node communication | UDP | 3343 | Required alongside TCP 3343 |
| RPC Endpoint Mapper | TCP | 135 | Required for RPC-based cluster communication |
| Dynamic RPC | TCP | 49152–65535 | Scope the source address to cluster node IPs where policy allows |
| SMB (file share witness) | TCP | 445 | Required only when using a file share witness |
| ICMP | — | — | Cluster health monitoring; do not block ping between cluster nodes |
Verify port connectivity before proceeding:
# Run on each node against every other node
Test-NetConnection SQL-NODE2.contoso.com -Port 3343
Test-NetConnection SQL-NODE2.contoso.com -Port 135
Test-NetConnection SQL-NODE2.contoso.com -Port 445
# ICMP
Test-Connection SQL-NODE2.contoso.com -Count 4
On-premises firewall rules
Open the required ports on the Windows Firewall on every cluster node. Run on each node:
# Cluster node-to-node communication
New-NetFirewallRule `
-DisplayName "WSFC Cluster Communication (TCP 3343)" `
-Direction Inbound `
-Protocol TCP `
-LocalPort 3343 `
-Action Allow
New-NetFirewallRule `
-DisplayName "WSFC Cluster Communication (UDP 3343)" `
-Direction Inbound `
-Protocol UDP `
-LocalPort 3343 `
-Action Allow
# RPC endpoint mapper
New-NetFirewallRule `
-DisplayName "RPC Endpoint Mapper (TCP 135)" `
-Direction Inbound `
-Protocol TCP `
-LocalPort 135 `
-Action Allow
# Dynamic RPC range
New-NetFirewallRule `
-DisplayName "Dynamic RPC (TCP 49152-65535)" `
-Direction Inbound `
-Protocol TCP `
-LocalPort "49152-65535" `
-Action Allow
# SMB for file share witness — omit if not using a file share witness
New-NetFirewallRule `
-DisplayName "SMB File Share Witness (TCP 445)" `
-Direction Inbound `
-Protocol TCP `
-LocalPort 445 `
-Action Allow
# ICMP for cluster health monitoring
New-NetFirewallRule `
-DisplayName "WSFC ICMP Health Check" `
-Direction Inbound `
-Protocol ICMPv4 `
-IcmpType 8 `
-Action Allow
Verify the rules were created and are enabled:
Get-NetFirewallRule |
Where-Object { $_.DisplayName -like 'WSFC*' -or $_.DisplayName -like 'RPC*' -or $_.DisplayName -like 'SMB*' } |
Select-Object DisplayName, Enabled, Direction, Action |
Format-Table -AutoSize
If a rule with the same display name already exists, use Set-NetFirewallRule -DisplayName "<name>" -Enabled True to enable it rather than creating a duplicate. After applying rules, re-run Test-NetConnection to confirm bidirectional access on port 3343 between all nodes.
Azure-specific prerequisites
On Azure VMs, several differences apply compared to on-premises:
- No multicast or broadcast. WSFC requires unicast cluster communication. Azure VMs operate in unicast mode by default; no additional configuration is needed, but split-brain detection behaviour differs from on-premises.
- Cloud witness is preferred. A file share witness hosted on an Azure VM creates a dependency on a VM that may share the same failure domain as the cluster nodes. Use an Azure Storage Account as a cloud witness instead. Requirements: General Purpose v2 or Standard tier, locally redundant storage, in a different Azure region from the cluster nodes for maximum resilience. Every cluster node must reach
<account>.blob.core.windows.netover TCP 443. - Static IP addresses required. Do not use DHCP for cluster node NICs or the cluster IP resource. Assign static private IPs within the VNet subnet. Assign them in the Azure portal or via Azure PowerShell, then configure matching static IPs inside the VM guest OS.
- Accelerated Networking. Enable Accelerated Networking on the SQL Server VM NICs. This reduces latency between nodes and is strongly recommended for synchronous-commit AG replicas. It requires VM SKUs that support it — most current D/E/F series generations do.
- Fault domain separation. Place cluster nodes in an Availability Set (different fault and update domains) or across Availability Zones (separate physical datacentres). Do not leave both nodes on the default placement, which may co-locate them on the same physical host.
- Azure Load Balancer for the AG listener. The cluster itself does not require a load balancer, but the AG listener IP created later will require an Internal Load Balancer (ILB). This is covered in Building a SQL Server Availability Group from Scratch.
Azure NSG rules
Azure VMs require Network Security Group (NSG) inbound rules to allow WSFC traffic. Apply these to the NSG associated with the SQL Server VM subnet. Run from a machine with the Az PowerShell module installed:
$nsg = Get-AzNetworkSecurityGroup `
-ResourceGroupName '<resource-group>' `
-Name '<nsg-name>'
# WSFC node-to-node communication (TCP)
Add-AzNetworkSecurityRuleConfig `
-NetworkSecurityGroup $nsg `
-Name 'Allow-WSFC-TCP-3343' `
-Priority 200 `
-Direction Inbound `
-Access Allow `
-Protocol Tcp `
-SourcePortRange '*' `
-DestinationPortRange '3343' `
-SourceAddressPrefix 'VirtualNetwork' `
-DestinationAddressPrefix '*' | Out-Null
# WSFC node-to-node communication (UDP)
Add-AzNetworkSecurityRuleConfig `
-NetworkSecurityGroup $nsg `
-Name 'Allow-WSFC-UDP-3343' `
-Priority 201 `
-Direction Inbound `
-Access Allow `
-Protocol Udp `
-SourcePortRange '*' `
-DestinationPortRange '3343' `
-SourceAddressPrefix 'VirtualNetwork' `
-DestinationAddressPrefix '*' | Out-Null
# RPC endpoint mapper
Add-AzNetworkSecurityRuleConfig `
-NetworkSecurityGroup $nsg `
-Name 'Allow-RPC-135' `
-Priority 210 `
-Direction Inbound `
-Access Allow `
-Protocol Tcp `
-SourcePortRange '*' `
-DestinationPortRange '135' `
-SourceAddressPrefix 'VirtualNetwork' `
-DestinationAddressPrefix '*' | Out-Null
# Dynamic RPC
Add-AzNetworkSecurityRuleConfig `
-NetworkSecurityGroup $nsg `
-Name 'Allow-Dynamic-RPC' `
-Priority 220 `
-Direction Inbound `
-Access Allow `
-Protocol Tcp `
-SourcePortRange '*' `
-DestinationPortRange '49152-65535' `
-SourceAddressPrefix 'VirtualNetwork' `
-DestinationAddressPrefix '*' | Out-Null
Set-AzNetworkSecurityGroup -NetworkSecurityGroup $nsg
Dynamic RPC scope: Allowing
49152-65535fromVirtualNetworkis broad. Where security policy permits, replaceVirtualNetworkwith the specific private IP addresses of the cluster nodes to restrict the rule to known sources.
Active Directory preparation
When New-Cluster runs, it registers the cluster name in Active Directory as a computer object. This is the Cluster Name Object (CNO). The cluster then uses the CNO to create additional Virtual Computer Objects (VCOs) for cluster roles — including the AG listener created in a later step.
Two approaches are available: grant the setup account permission to create the CNO automatically, or pre-stage the CNO manually before running New-Cluster.
Required privileges
| Account | Permission | Scope | Notes |
|---|---|---|---|
Account running New-Cluster |
Local Administrator | All cluster nodes | Required to configure Windows during cluster creation |
Account running New-Cluster |
Create Computer Objects | Target AD OU | Allows New-Cluster to create the CNO; scoped to one OU |
Account running New-Cluster |
Full Control on CNO | Pre-staged CNO object | Used instead of the OU permission when CNO is pre-staged |
CNO (SQLCLUSTER01$) |
Create Computer Objects | Target AD OU | Required after cluster creation for AG listener VCO creation |
The account does not need Domain Admin. Domain Admin is sufficient but exceeds what is needed. Scope the permissions to exactly what is listed.
Option 1: Grant Create Computer Objects on the OU
This is the simpler path when the setup account can be granted OU-scoped AD permissions. Run on a domain controller or from a machine with RSAT Active Directory tools installed:
Import-Module ActiveDirectory
$ouDN = 'OU=Clusters,DC=contoso,DC=com'
$setupUser = Get-ADUser -Identity 'svc-cluster-setup'
# GUID of the Computer class schema object
$computerGuid = [Guid]'bf967a86-0de6-11d0-a285-00aa003049e2'
$createRight = [System.DirectoryServices.ActiveDirectoryRights]::CreateChild
$allow = [System.Security.AccessControl.AccessControlType]::Allow
$noInherit = [System.DirectoryServices.ActiveDirectorySecurityInheritance]::None
$ou = [ADSI]"LDAP://$ouDN"
$acl = $ou.ObjectSecurity
# Grant the setup account permission to create computer objects in the OU
$rule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule(
$setupUser.SID,
$createRight,
$allow,
$computerGuid,
$noInherit
)
$acl.AddAccessRule($rule)
$ou.CommitChanges()
After the cluster is created, grant the CNO the same permission so it can create VCOs for the AG listener and other cluster roles:
$cno = Get-ADComputer -Identity 'SQLCLUSTER01'
$rule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule(
$cno.SID,
$createRight,
$allow,
$computerGuid,
$noInherit
)
$acl.AddAccessRule($rule)
$ou.CommitChanges()
Option 2: Pre-stage the CNO manually
Pre-staging is required in environments where AD delegation is restrictive and granting OU-level Create Computer Objects is not permitted. An AD administrator creates the CNO before the cluster is built. Microsoft documents this procedure at Pre-stage cluster computer objects in Active Directory.
Step 1: Create the disabled computer account
The account name must match the cluster name that will be passed to New-Cluster. It must be created in the disabled state — the cluster setup process enables it.
# Run on a domain controller or from a machine with RSAT AD tools
New-ADComputer `
-Name 'SQLCLUSTER01' `
-SamAccountName 'SQLCLUSTER01' `
-Path 'OU=Clusters,DC=contoso,DC=com' `
-Enabled $false `
-Description 'WSFC Cluster Name Object — pre-staged'
Verify the account was created in the disabled state:
Get-ADComputer -Identity 'SQLCLUSTER01' |
Select-Object Name, Enabled, DistinguishedName
Step 2: Grant Full Control on the CNO to the setup account
$cno = Get-ADComputer -Identity 'SQLCLUSTER01'
$setupUser = Get-ADUser -Identity 'svc-cluster-setup'
$acl = Get-Acl "AD:$($cno.DistinguishedName)"
$fullControl = [System.DirectoryServices.ActiveDirectoryRights]::GenericAll
$allow = [System.Security.AccessControl.AccessControlType]::Allow
$noInherit = [System.DirectoryServices.ActiveDirectorySecurityInheritance]::None
$rule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule(
$setupUser.SID,
$fullControl,
$allow,
$noInherit
)
$acl.AddAccessRule($rule)
Set-Acl "AD:$($cno.DistinguishedName)" $acl
Step 3: Grant the CNO permission to create VCOs
This must be done after cluster creation but before any cluster role with a network name is created. Grant the CNO permission to create child computer objects in the same OU where the CNO lives:
$cno = Get-ADComputer -Identity 'SQLCLUSTER01'
$computerGuid = [Guid]'bf967a86-0de6-11d0-a285-00aa003049e2'
$ouDN = 'OU=Clusters,DC=contoso,DC=com'
$ou = [ADSI]"LDAP://$ouDN"
$acl = $ou.ObjectSecurity
$createRight = [System.DirectoryServices.ActiveDirectoryRights]::CreateChild
$allow = [System.Security.AccessControl.AccessControlType]::Allow
$noInherit = [System.DirectoryServices.ActiveDirectorySecurityInheritance]::None
$rule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule(
$cno.SID,
$createRight,
$allow,
$computerGuid,
$noInherit
)
$acl.AddAccessRule($rule)
$ou.CommitChanges()
When running New-Cluster with a pre-staged CNO, the -Name parameter must exactly match the pre-staged account name. The cluster setup locates the disabled account by name, enables it, and updates its DNS record.
Install the Failover Clustering feature
Install the Failover Clustering Windows feature on every node. The IncludeManagementTools parameter installs Failover Cluster Manager and the FailoverClusters PowerShell module.
Install on each node individually:
Install-WindowsFeature -Name Failover-Clustering -IncludeManagementTools
Or install on all nodes in parallel using Invoke-Command:
$nodes = @('SQL-NODE1', 'SQL-NODE2')
Invoke-Command -ComputerName $nodes -ScriptBlock {
Install-WindowsFeature -Name Failover-Clustering -IncludeManagementTools
}
A restart is required after installing the feature on each node. Do not proceed to validation until all nodes have restarted.
Confirm the feature is installed and the PowerShell module is available on every node:
Invoke-Command -ComputerName $nodes -ScriptBlock {
Get-WindowsFeature -Name Failover-Clustering |
Select-Object Name, InstallState
Get-Module -ListAvailable -Name FailoverClusters |
Select-Object Name, Version
}
Expected output on each node:
Name InstallState
---- ------------
Failover-Clustering Installed
Validate the cluster
Run Test-Cluster before creating the cluster. Validation tests hardware, storage, network, and system configuration across all intended nodes. Microsoft support requires validation to have been run and passed (or passed with reviewed warnings) before a cluster is used for production workloads.
Run from any node that will be part of the cluster. All intended nodes must be listed:
Test-Cluster -Node @('SQL-NODE1', 'SQL-NODE2')
Save the report to a specific path for later review:
Test-Cluster `
-Node @('SQL-NODE1', 'SQL-NODE2') `
-ReportName 'C:\ClusterValidation\PreCreation'
The report is saved as an HTML file at the specified path. Open it in a browser after validation completes.
Interpreting validation results:
| Result | Meaning |
|---|---|
| Success | Test passed without issue |
| Warning | Potential issue; review the detail and document the decision before proceeding |
| Failed | Test failed; the underlying issue must be resolved before creating the cluster |
Common warnings to evaluate rather than automatically dismiss:
- Non-redundant cluster network: If nodes have a single NIC for cluster communication, this is flagged as a warning. It is a genuine resilience gap but may be acceptable on VMs where the hypervisor provides NIC redundancy. Document the risk.
- No shared storage / no suitable disks: Expected for SQL Server AG deployments that use automatic or manual seeding. Acknowledge and proceed.
- Storage tests skipped: Expected when
-NoStorageis not specified and no shared disks are attached. These warnings are safe to acknowledge for AG deployments.
Do not create the cluster if validation shows Failed results. Resolve every failure before proceeding.
Create the cluster
Create the cluster using New-Cluster. Run from any node that will join the cluster. The cluster name must be unique in the domain and must not exceed 15 characters (NetBIOS limit).
On-premises
Specify a static IP address within the node subnet. This IP becomes the cluster’s IP Address resource and resolves to the cluster name in DNS and Active Directory.
New-Cluster `
-Name 'SQLCLUSTER01' `
-Node @('SQL-NODE1', 'SQL-NODE2') `
-StaticAddress '192.168.1.50' `
-NoStorage
-NoStorage prevents the cluster from automatically claiming any attached shared disks. For AG deployments this is recommended. If a disk witness is planned, it can be added explicitly after cluster creation.
Confirm the cluster and all nodes are online:
Get-Cluster -Name 'SQLCLUSTER01' | Select-Object Name, Domain
Get-ClusterNode | Select-Object Name, State
Expected:
Name Domain
---- ------
SQLCLUSTER01 contoso.com
Name State
---- -----
SQL-NODE1 Up
SQL-NODE2 Up
Azure VMs
On Azure, the static IP must be an unused private address within the VNet subnet. Assign it as a static IP on the VM NIC in both the Azure portal and the guest OS network adapter settings before creating the cluster.
New-Cluster `
-Name 'SQLCLUSTER01' `
-Node @('SQL-NODE1', 'SQL-NODE2') `
-StaticAddress '10.0.1.50' `
-NoStorage
After creation, confirm the cluster IP Address resource is online:
Get-ClusterResource | Select-Object Name, ResourceType, State, OwnerNode
Expected after a clean creation with no additional roles:
Name ResourceType State OwnerNode
---- ------------ ----- ---------
Cluster IP Address IP Address Online SQL-NODE1
Cluster Name Network Name Online SQL-NODE1
Both resources must be Online. If the cluster name resource is offline, DNS registration has not completed. Check that the CNO was created or enabled in Active Directory and that port 53 is reachable between the cluster nodes and the domain controller.
Configure the quorum
Quorum is the voting mechanism WSFC uses to determine whether the cluster has enough healthy members to remain online. When a node loses contact with the rest of the cluster, quorum determines whether the surviving members have sufficient votes to continue — or must stop to prevent split-brain, where two isolated groups each believe they are the authoritative cluster and begin writing to the same data.
How quorum works in Windows Server 2016 and later
Windows Server 2016 introduced two features that change quorum behaviour compared to earlier versions:
Dynamic quorum: The cluster automatically adjusts voting membership as nodes leave and rejoin. When a node fails, its vote is removed from the quorum calculation. This allows the cluster to survive successive node failures that would have exceeded the fixed quorum threshold in earlier versions.
Dynamic witness: The witness vote is only counted when it is needed to break a tie. When the cluster has an odd number of voting nodes, the witness vote is suppressed. When a failure makes the count even, the witness vote is activated. This prevents the witness from being a required voter in healthy conditions.
Both features are enabled by default in Windows Server 2016 and later and should not be disabled without a specific documented reason.
Quorum options
| Option | Votes | When to use |
|---|---|---|
| Node Majority | Each node: 1. No witness. | Odd number of nodes (3, 5, 7). Survives loss of ⌊n/2⌋ nodes. |
| Node and Disk Majority | Each node + shared disk: 1 each. | Even number of nodes with shared storage available for a witness disk. |
| Node and File Share Majority | Each node: 1. File share acts as tiebreaker. | Even number of nodes without shared storage. On-premises or Azure (share hosted off-cluster). |
| Cloud Witness | Each node: 1. Azure Blob Storage acts as tiebreaker. | Any cluster where internet access is available. Recommended for Azure VMs and stretched on-premises clusters. |
| No Majority: Disk Only | Only the shared disk votes. | Not recommended. Single point of failure — if the disk is unavailable the cluster stops regardless of node health. Avoid in new deployments. |
For a 2-node SQL Server cluster: a witness is required. Without one, if one node fails the remaining node has 1 of 2 possible votes — below the majority threshold — and the cluster stops even though the surviving node is healthy. This is the most common quorum misconfiguration seen after cluster creation.
Configure quorum via PowerShell
All commands use Set-ClusterQuorum. Run on any cluster node after the cluster is created.
Node Majority — for clusters with an odd number of nodes:
Set-ClusterQuorum -NodeMajority
Get-ClusterQuorum
Node and Disk Majority — shared disk witness:
# List disks available to the cluster
Get-ClusterAvailableDisk | Select-Object Name, DiskNumber, Size
# Assign the disk witness
Set-ClusterQuorum -DiskWitness 'Cluster Disk 1'
Get-ClusterQuorum
The witness disk must be accessible from all cluster nodes. It must not be used for any cluster role, data files, or log files. A small disk (1 GB) is sufficient.
Node and File Share Majority — file share witness:
Set-ClusterQuorum -FileShareWitness '\\FILESERVER01\ClusterWitness'
Get-ClusterQuorum
The file share must be hosted on a server that is not a cluster node. The cluster computer account (the CNO — for example CONTOSO\SQLCLUSTER01$) must have at minimum Change permission on the share and Modify permission on the underlying file system.
Create the witness share and grant the required permissions (run on the file server):
$witnessFolderPath = 'D:\ClusterWitness'
$clusterAccount = 'CONTOSO\SQLCLUSTER01$'
$shareName = 'ClusterWitness'
New-Item -ItemType Directory -Path $witnessFolderPath -Force
# File system permission
$acl = Get-Acl -Path $witnessFolderPath
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
$clusterAccount,
'Modify',
'ContainerInherit, ObjectInherit',
'None',
'Allow'
)
$acl.SetAccessRule($rule)
Set-Acl -Path $witnessFolderPath -AclObject $acl
# Share permission
New-SmbShare `
-Name $shareName `
-Path $witnessFolderPath `
-FullAccess $clusterAccount `
-Description 'WSFC Cluster Witness Share'
Verify the share is accessible from a cluster node:
# Run from any cluster node
Test-Path "\\FILESERVER01\ClusterWitness"
Cloud Witness — Azure Blob Storage:
A cloud witness uses an Azure Storage Account as the tiebreaker. Every cluster node must be able to reach <account>.blob.core.windows.net on TCP 443. Place the storage account in a different Azure region from the cluster nodes.
Create the storage account:
# Run from a machine with the Az PowerShell module installed
New-AzStorageAccount `
-ResourceGroupName '<resource-group>' `
-Name '<storage-account-name>' `
-Location '<region-different-from-nodes>' `
-SkuName Standard_LRS `
-Kind StorageV2 `
-MinimumTlsVersion TLS1_2
Retrieve the access key and configure the witness on any cluster node:
# Retrieve key
$storageKey = (Get-AzStorageAccountKey `
-ResourceGroupName '<resource-group>' `
-Name '<storage-account-name>')[0].Value
# Configure witness
Set-ClusterQuorum `
-CloudWitness `
-AccountName '<storage-account-name>' `
-AccessKey $storageKey
Get-ClusterQuorum
Verify the witness resource is online:
(Get-ClusterQuorum).QuorumResource | Select-Object Name, State
The witness resource must show State = Online. If it shows Failed, confirm that:
- TCP 443 is reachable from each cluster node to
<account>.blob.core.windows.net - The access key is correct and has not been rotated since the witness was configured
- The storage account tier and replication type meet the requirements (General Purpose, Standard_LRS)
Post-creation validation
After creating the cluster and configuring quorum, run these checks before proceeding to SQL Server and AG configuration.
All nodes online and voting:
Get-ClusterNode | Select-Object Name, State, NodeWeight
All nodes should show State = Up and NodeWeight = 1. A NodeWeight of 0 indicates the node is excluded from voting, which may occur during dynamic quorum adjustment when a node has been offline.
Cluster network configuration:
Get-ClusterNetwork | Select-Object Name, State, Role, Address, AddressMask
All networks should show State = Up. The Role value indicates how the network is used:
| Role | Meaning |
|---|---|
ClusterAndClient |
Used for both cluster node communication and client access |
Cluster |
Used for cluster internal communication only |
Client |
Excluded from internal cluster communication |
None |
Not used by the cluster at all |
For production clusters, a dedicated cluster network (Role = Cluster) separate from the client access network (Role = ClusterAndClient or Client) is recommended to prevent client traffic from saturating the cluster heartbeat network.
Cluster resources:
Get-ClusterResource | Select-Object Name, ResourceType, State, OwnerNode
Both the Cluster Name and Cluster IP Address resources must be Online. If either is offline, client connectivity to the cluster name will fail.
Quorum health:
Get-ClusterQuorum | Select-Object QuorumType, QuorumResource
Cluster event log:
Get-ClusterLog -Destination 'C:\ClusterLogs' -UseLocalTime
This writes the cluster log for all nodes to the specified directory. Review for [ERR] entries that occurred during or after creation.
Final validation pass:
Test-Cluster `
-Node @('SQL-NODE1', 'SQL-NODE2') `
-ReportName 'C:\ClusterValidation\PostCreation'
A clean second validation pass confirms that the built cluster passes the same tests it passed before creation. Any new failures indicate a configuration change that occurred during setup.
On-premises versus Azure: key differences
| Area | On-premises | Azure VMs |
|---|---|---|
| Node IP addresses | Static, any subnet scheme | Static private IP within VNet subnet; configure in Azure portal AND guest OS |
| Cluster IP resource | Standard static or DHCP-managed | Static IP from VNet subnet; must be unused; no DHCP |
| DNS registration | Automatic via AD-integrated DNS | Automatic via Azure internal DNS + AD |
| Quorum witness | Disk, file share, or cloud witness | Cloud witness recommended; disk witness requires shared managed disk |
| File share witness | File server on separate server | On-premises equivalent required; not hosted on Azure cluster nodes |
| Cloud witness | Optional; internet access required | Default recommendation; place storage account in separate region |
| AD CNO creation | Standard AD OU permissions | Same requirements; at least one domain controller must be reachable from nodes |
| Cluster validation | Run before creation | Run before creation; some Azure-specific storage/network warnings expected |
| Accelerated Networking | Not applicable | Enable on all SQL Server VM NICs before cluster creation |
| VM placement | Physical rack/chassis separation | Availability Sets or Availability Zones for fault domain separation |
| AG listener IP | Floating cluster IP | Requires Internal Load Balancer with Floating IP; see AG build article |
Do not do this
- Do not create the cluster with a name longer than 15 characters. The cluster name is a NetBIOS name. Names longer than 15 characters are silently truncated in some contexts, causing DNS registration and AD object names to diverge from the name specified in
New-Cluster. - Do not skip cluster validation before creation.
New-Clusterdoes not run validation internally. Clusters built without passingTest-Clusterhave failed in production when networking or storage issues that validation would have caught surfaced under load. - Do not use the same account for both the SQL Server service and the cluster setup if that account does not have the required AD permissions. Use a dedicated setup account, scoped to exactly the permissions needed, separate from the SQL Server service account.
- Do not leave a 2-node cluster running without a witness. Without a witness, if one node becomes unavailable, the surviving node has 1 of 2 votes and cannot form quorum. The cluster stops. This is not a failure of the cluster — it is the quorum working as designed. It is a configuration gap.
- Do not configure a file share witness where the share is hosted on a cluster node. If that node fails, the witness is also unavailable. The witness must always be independent of the cluster.
- Do not use
No Majority: Disk Onlyquorum. If the disk witness becomes unavailable, the entire cluster stops regardless of node health. This is a single point of failure that negates the purpose of clustering. - Do not skip granting the CNO permission to create VCOs in AD after the cluster is built. The cluster itself runs without this permission. Creating an AG listener or any other cluster role with a network name will fail with an Active Directory access denied error. This permission must be in place before the AG listener step.
- Do not open the dynamic RPC port range (49152–65535) without scoping the source address prefix on production firewalls. Restrict the source to the private IP addresses of the cluster nodes.
- Do not configure a cloud witness using a storage account in the same Azure region as the cluster nodes. If the region experiences a full outage, both the cluster nodes and the witness become unavailable simultaneously. Place the witness storage account in a different region.
Official references
- Failover Clustering overview — Microsoft Learn
- Create a failover cluster — Microsoft Learn
- Failover cluster hardware requirements and storage options — Microsoft Learn
- Configure and manage quorum — Microsoft Learn
- Deploy a cloud witness for a failover cluster — Microsoft Learn
- Pre-stage cluster computer objects in Active Directory — Microsoft Learn
- Failover clustering networking basics — Microsoft Learn
- New-Cluster — Microsoft Learn
- Test-Cluster — Microsoft Learn
- Set-ClusterQuorum — Microsoft Learn
- SQL Server on Azure VMs — Always On overview — Microsoft Learn
- Accelerated Networking for Azure VMs — Microsoft Learn
- Azure Availability Sets — Microsoft Learn
Conclusion
The cluster creation failed because one AD permission was missing on one OU. Not domain admin. Not a network outage. One CreateChild right on a computer class schema GUID, on one OU, for one account. It took forty minutes to find.
The pattern across WSFC deployments is consistent: the cluster creation command takes seconds. Everything before it — AD permissions, firewall ports, DNS resolution, feature installation, cluster validation — determines whether that command succeeds. Everything after it — quorum configuration, CNO permissions for VCOs — determines whether the cluster can host anything useful.
Build the prerequisites. Run validation before creating. Create the cluster. Configure quorum before adding any cluster roles. Grant the CNO permission to create VCOs. Then build the Availability Group.
Continue reading
- Building a SQL Server Availability Group from Scratch: On-Premises and Azure
- Extending a Two-Node SQL Server Availability Group Across Azure Regions
Marios Pavlidis Principal Database Administrator