Skip to main content
High AvailabilitySQL ServerWSFCWindows Server Failover ClusterAlways Onavailability groupsHADRAzureon-premisesActive Directoryquorumcloud witnessPowerShell

Building a Windows Server Failover Cluster for SQL Server: On-Premises and Azure

Step-by-step guide to building a WSFC as the foundation for SQL Server Always On Availability Groups. Covers AD permissions and CNO pre-staging, Windows Firewall rules, Azure NSG rules, cluster validation, creation, and all quorum options with PowerShell for every step.

26 min read

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-Cluster must be a member of the local Administrators group on all cluster nodes.
  • The account used to run New-Cluster must 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 running New-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.net over 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-65535 from VirtualNetwork is broad. Where security policy permits, replace VirtualNetwork with 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 -NoStorage is 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:

  1. TCP 443 is reachable from each cluster node to <account>.blob.core.windows.net
  2. The access key is correct and has not been rotated since the witness was configured
  3. 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-Cluster does not run validation internally. Clusters built without passing Test-Cluster have 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 Only quorum. 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


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


Marios Pavlidis Principal Database Administrator