Jul 10, 2026 · devops · 22 min read · 3550 words advanced

SQL Server 2025 Always On across AWS subnets.

devopsawssql-serverhigh-availability

A SQL Server Availability Group that works on an on-premises VLAN can fail in subtle ways on EC2: cluster IPs refuse to come online, the listener registers unreachable addresses, failover succeeds but applications remain disconnected, or a brief UDP interruption evicts a healthy node. WSFC was designed around movable virtual addresses and normal Layer 2 behavior; Amazon VPC networking uses ENI ownership and routing semantics instead.

This guide builds a two-node SQL Server 2025 Enterprise Availability Group across two Availability Zones. It assumes Windows Server 2025, an existing Active Directory domain named corp.example.com, AD-integrated DNS, default SQL instances, a file-share witness in a third failure domain, and one listener address in each subnet. SQL Server 2025 Standard is limited to a two-replica, one-database Basic Availability Group; verify edition-specific features in Microsoft's SQL Server 2025 edition matrix.

Architecture: WSFC, AG, and FCI are different layers

Windows Server Failover Clustering is the coordination layer. It tracks membership, quorum, health, dependencies, ownership, and failover decisions; it does not replicate SQL data. An Availability Group is the SQL database-replication layer built on WSFC. Each replica is an independent SQL instance with its own system databases and EBS storage. User-database changes travel through database-mirroring endpoints, normally TCP 5022.

An FCI protects an entire SQL instance and requires storage accessible by every possible owner. Only one node runs that instance. An AG keeps separate database copies and can provide readable secondaries and database-level failover. The AG listener is a virtual network name that lets applications find the current primary.

Use VPC 10.2.0.0/16, subnet A 10.2.8.0/24 in AZ A, and subnet B 10.2.9.0/24 in AZ B. SQLNODE1 uses primary address 10.2.8.10, WSFC address 10.2.8.20, and listener address 10.2.8.30. SQLNODE2 uses 10.2.9.10, 10.2.9.20, and 10.2.9.30. The logical names are SQLCLUSTER.corp.example.com and APPAG-LST.corp.example.com.

AWS control plane

Reserves each virtual address on the correct ENI. This decides whether VPC routing can deliver packets at all.

WSFC control plane

Maintains quorum, resource dependencies, IP online state, DNS registration, and role ownership.

SQL data plane

Replicates transaction log over TCP 5022 and serves client sessions through the listener on TCP 1433.

Architecture note

Each node has one ENI with three AWS private IPv4 addresses. Windows keeps the normal NIC on DHCP. WSFC brings the cluster and listener addresses online only as clustered resources. AWS recommends this one-ENI, three-address model and a unique subnet per node in its SQL Server clustering guidance.

What actually changes during cross-subnet failover

ResourceBefore failoverAfter failoverWhat does not move
AG primarySQLNODE1SQLNODE2Each instance and its EBS volumes stay on its EC2 node.
Listener IP10.2.8.30 online10.2.9.30 online10.2.8.30 remains assigned to node 1's ENI; 10.2.9.30 remains assigned to node 2's ENI.
Listener DNSBoth A records when RAP=1Both A records remainThe client driver chooses the reachable address with MultiSubnetFailover.
Cluster quorumTwo nodes plus witness voteSame votersThe witness is not a SQL replica and contains no database copy.

Prerequisites

Patch both nodes to the same Windows and SQL Server cumulative-update level. Use comparable, EBS-optimized instances sized to carry the entire production workload alone. Keep data, log, tempdb, and backup paths consistent across nodes. Join both hosts to the same AD domain, run SQL Server under a domain identity such as CORP\svc_sql, and ensure the nodes resolve each other, the domain controllers, cluster name, listener, and witness through AD DNS.

The cluster-creation identity needs local Administrator access on both nodes and permission to create the cluster computer object. The WSFC cluster name object, or CNO, must then create or control the listener virtual computer object, or VCO. Microsoft documents the identities and delegation model in its failover-cluster accounts overview. Use a witness outside both SQL nodes; a two-node cluster without a witness cannot tolerate many single-failure combinations while retaining quorum.

1. Reserve cluster and listener addresses on the ENIs

The secondary addresses are AWS control-plane reservations. They tell the VPC fabric which ENI owns each address. Replace all angle-bracket placeholders before running these AWS CLI v2 commands.

aws ec2 assign-private-ip-addresses `
  --network-interface-id <NODE1_ENI> `
  --private-ip-addresses 10.2.8.20 10.2.8.30

aws ec2 assign-private-ip-addresses `
  --network-interface-id <NODE2_ENI> `
  --private-ip-addresses 10.2.9.20 10.2.9.30

aws ec2 describe-network-interfaces `
  --network-interface-ids <NODE1_ENI> <NODE2_ENI> `
  --query "NetworkInterfaces[].{ENI:NetworkInterfaceId,Subnet:SubnetId,IPs:PrivateIpAddresses[].PrivateIpAddress}" `
  --output table

Do not add these four addresses persistently under Windows IPv4 Advanced Settings. AWS warns that guest-level assignment of cluster-managed secondary addresses can produce network interruptions during failover. The control-plane operation is documented under assign-private-ip-addresses.

Why this matters

EC2 does not let a guest claim an arbitrary IP merely by sending gratuitous ARP. An address can look valid inside Windows yet remain unreachable unless EC2 has assigned it to the correct ENI.

Warning

Do not use --allow-reassignment to move these addresses during normal AG failover. A multi-subnet listener is not one floating address moved between ENIs; it is two subnet-local IP resources connected by an OR dependency.

2. Keep Windows on DHCP and verify DNS

Get-NetIPConfiguration
Get-NetIPInterface -AddressFamily IPv4 |
  Format-Table InterfaceAlias,Dhcp,ConnectionState
Get-DnsClientServerAddress -AddressFamily IPv4

Resolve-DnsName corp.example.com -Type SOA
Resolve-DnsName SQLNODE1.corp.example.com
Resolve-DnsName SQLNODE2.corp.example.com

The production adapter should use DHCP. The VPC DHCP option set must provide DNS servers capable of resolving the AD zone. Do not point domain members only at public DNS or a VPC resolver that lacks conditional forwarding for the domain.

Why this matters

Keeping only the primary address under normal adapter configuration gives WSFC exclusive control over the virtual addresses and avoids duplicate ownership.

AWS gotcha

The EC2 console can show three ENI addresses while Get-NetIPAddress shows only the primary address during steady state. That is expected. The other addresses appear in the guest only when their cluster IP resources are brought online.

3. Configure security groups and NACLs

PurposeProtocol/portSource
WSFC heartbeatUDP 3343SQL node SG
WSFC join/controlTCP 3343SQL node SG
RPC mapper/dynamicTCP 135, 49152-65535SQL node SG
SMB and validationTCP 445, ICMPv4SQL node SG
AG endpointTCP 5022SQL node SG
SQL listenerTCP 1433Application SG
RDPTCP 3389Management SG only
$SqlAgSg = aws ec2 create-security-group `
  --group-name sql2025-ag-nodes `
  --description "SQL Server 2025 AG and WSFC nodes" `
  --vpc-id <VPC_ID> --query GroupId --output text

aws ec2 authorize-security-group-ingress --group-id $SqlAgSg --protocol udp --port 3343 --source-group $SqlAgSg
aws ec2 authorize-security-group-ingress --group-id $SqlAgSg --protocol tcp --port 3343 --source-group $SqlAgSg
aws ec2 authorize-security-group-ingress --group-id $SqlAgSg --protocol tcp --port 135 --source-group $SqlAgSg
aws ec2 authorize-security-group-ingress --group-id $SqlAgSg --protocol tcp --port 445 --source-group $SqlAgSg
aws ec2 authorize-security-group-ingress --group-id $SqlAgSg --protocol tcp --port 49152-65535 --source-group $SqlAgSg
aws ec2 authorize-security-group-ingress --group-id $SqlAgSg --protocol icmp --port -1 --source-group $SqlAgSg
aws ec2 authorize-security-group-ingress --group-id $SqlAgSg --protocol tcp --port 5022 --source-group $SqlAgSg
aws ec2 authorize-security-group-ingress --group-id $SqlAgSg --protocol tcp --port 1433 --source-group <APPLICATION_SG_ID>

Domain-controller security groups must accept node-initiated DNS TCP/UDP 53, Kerberos TCP/UDP 88, NTP UDP 123, RPC TCP 135 and 49152-65535, LDAP TCP/UDP 389, SMB TCP 445, Kerberos password-change TCP/UDP 464, and Global Catalog TCP 3268. Add 636/3269 for LDAP/GC over TLS and TCP 9389 where Active Directory Web Services is used. Security groups are stateful; custom NACLs are stateless and must allow both directions and return traffic.

Production tip

Reference security groups instead of copying instance private IPs into rules. The node-to-node rules then survive instance replacement as long as the replacement ENI receives the same security group. Restrict TCP 1433 to application and DBA management groups; never use 0.0.0.0/0 for SQL or RDP.

NACL warning

A security group is stateful, but a NACL is not. A restrictive NACL must allow the request and return path, including RPC dynamic ports. An apparently correct security group cannot compensate for a dropped NACL return packet.

4. Configure Windows Firewall

$ClusterSubnets = @("10.2.8.0/24","10.2.9.0/24")

New-NetFirewallRule -DisplayName "WSFC UDP 3343" -Direction Inbound -Action Allow -Protocol UDP -LocalPort 3343 -RemoteAddress $ClusterSubnets -Profile Domain
New-NetFirewallRule -DisplayName "WSFC TCP 3343" -Direction Inbound -Action Allow -Protocol TCP -LocalPort 3343 -RemoteAddress $ClusterSubnets -Profile Domain
New-NetFirewallRule -DisplayName "WSFC RPC Mapper" -Direction Inbound -Action Allow -Protocol TCP -LocalPort 135 -RemoteAddress $ClusterSubnets -Profile Domain
New-NetFirewallRule -DisplayName "WSFC RPC Dynamic" -Direction Inbound -Action Allow -Protocol TCP -LocalPort 49152-65535 -RemoteAddress $ClusterSubnets -Profile Domain
New-NetFirewallRule -DisplayName "WSFC SMB" -Direction Inbound -Action Allow -Protocol TCP -LocalPort 445 -RemoteAddress $ClusterSubnets -Profile Domain
New-NetFirewallRule -DisplayName "WSFC ICMPv4" -Direction Inbound -Action Allow -Protocol ICMPv4 -IcmpType 8 -RemoteAddress $ClusterSubnets -Profile Domain
New-NetFirewallRule -DisplayName "SQL AG Endpoint" -Direction Inbound -Action Allow -Protocol TCP -LocalPort 5022 -RemoteAddress $ClusterSubnets -Profile Domain
New-NetFirewallRule -DisplayName "SQL Listener" -Direction Inbound -Action Allow -Protocol TCP -LocalPort 1433 -RemoteAddress <APPLICATION_CIDR> -Profile Domain

Get-NetConnectionProfile
Get-NetFirewallProfile

Microsoft lists UDP 3343 for cluster communication, TCP 3343 for node join, TCP 445, RPC and ICMP in its Windows port requirements.

Why this matters

Opening TCP 1433 proves only client access. It says nothing about heartbeat, cluster membership, endpoint transport, AD authentication, or DNS registration.

Operational note

Confirm Get-NetConnectionProfile reports DomainAuthenticated. If Windows temporarily classifies the adapter as Public after boot or a DC outage, Domain-profile allow rules may no longer apply even though their definitions are correct.

5. Prepare AD and DNS permissions

The account running New-Cluster needs Create Computer Objects and Read All Properties on the target AD container. The resulting SQLCLUSTER$ CNO needs Create Computer Objects there as well, or the listener VCO must be prestaged and controlled by the CNO.

Import-Module ActiveDirectory
Get-ADComputer -Filter "Name -eq 'SQLCLUSTER' -or Name -eq 'APPAG-LST'" `
  -Properties Enabled,DistinguishedName

New-ADComputer -Name "SQLCLUSTER" -SamAccountName "SQLCLUSTER$" `
  -Path "OU=Clusters,DC=corp,DC=example,DC=com" -Enabled $false

New-ADComputer -Name "APPAG-LST" -SamAccountName "APPAG-LST$" `
  -Path "OU=Clusters,DC=corp,DC=example,DC=com" -Enabled $false

Only run the creation commands when your AD team requires prestaging. In Active Directory Users and Computers with Advanced Features enabled, grant the cluster-creation identity Full Control of the disabled CNO and grant SQLCLUSTER$ Full Control of the disabled listener VCO. Let WSFC enable and manage the objects. Use secure dynamic updates and normally let WSFC create the A records rather than maintaining them manually.

Identity chain

The administrator creates or activates SQLCLUSTER$; then SQLCLUSTER$ creates or controls APPAG-LST$; then the listener network-name resource uses that VCO to register DNS. Granting rights only to the human administrator does not give the CNO permission to create the listener.

6. Install, validate, and create WSFC

Install-WindowsFeature -Name Failover-Clustering,RSAT-Clustering-PowerShell,RSAT-Clustering-Mgmt -IncludeManagementTools

Import-Module FailoverClusters
Test-Cluster -Node SQLNODE1.corp.example.com,SQLNODE2.corp.example.com -Include Inventory,Network,"System Configuration"

New-Cluster -Name SQLCLUSTER `
  -Node SQLNODE1.corp.example.com,SQLNODE2.corp.example.com `
  -StaticAddress 10.2.8.20,10.2.9.20 -NoStorage

Set-ClusterQuorum -Cluster SQLCLUSTER `
  -FileShareWitness "\\<WITNESS_FQDN>\<WITNESS_SHARE>"

Get-ClusterNode
Get-ClusterResource
Get-ClusterResourceDependency -Resource "Cluster Name"
Get-ClusterQuorum

The cluster name should depend on the two IP resources with OR semantics. Each node brings online only the address in its subnet. Microsoft's New-Cluster reference documents multiple static addresses.

Do not skip validation

AGs use independent EBS volumes, so storage-related validation output needs interpretation. Network, system configuration, domain, driver, RPC, and firewall failures do not. Save the validation report with the change record before creating the cluster.

Dependency check

Get-ClusterResourceDependency -Resource "Cluster Name" should show OR semantics across the two cluster IP resources. An AND dependency would require both subnet addresses online simultaneously and prevents the core name from coming online.

7. Enable Always On and create endpoints

Import-Module SqlServer
Enable-SqlAlwaysOn -ServerInstance $env:COMPUTERNAME -Force

Run that elevated on both nodes. -Force permits the required SQL service restart. Then create the endpoint on both instances:

USE master;
GO
CREATE ENDPOINT [Hadr_endpoint]
  STATE = STARTED
  AS TCP (LISTENER_PORT = 5022, LISTENER_IP = ALL)
  FOR DATABASE_MIRRORING
  (
    ROLE = ALL,
    AUTHENTICATION = WINDOWS NEGOTIATE,
    ENCRYPTION = REQUIRED ALGORITHM AES
  );
GO
IF SUSER_ID(N'CORP\svc_sql') IS NULL
  CREATE LOGIN [CORP\svc_sql] FROM WINDOWS;
GO
GRANT CONNECT ON ENDPOINT::[Hadr_endpoint] TO [CORP\svc_sql];
GO

If the nodes use different service accounts, grant the other replica's account access to the local endpoint. Built-in or non-domain identities require certificate authentication. See Microsoft's T-SQL AG procedure.

Concept

The endpoint is server-to-server transport, not the client listener. TCP 5022 carries log blocks and replica control messages. TCP 1433 carries application TDS sessions. They have different authentication, firewall scope, and failure symptoms.

8. Create and seed the Availability Group

On SQLNODE1, create or select a database in Full recovery model and take a full backup. Then create the AG:

ALTER DATABASE [AppDb] SET RECOVERY FULL;
GO
BACKUP DATABASE [AppDb] TO DISK=N'E:\SQLBackup\AppDb_initial.bak'
  WITH INIT,CHECKSUM,COMPRESSION,STATS=10;
GO

CREATE AVAILABILITY GROUP [AppAG]
WITH (AUTOMATED_BACKUP_PREFERENCE=PRIMARY, DB_FAILOVER=ON, DTC_SUPPORT=NONE)
FOR DATABASE [AppDb]
REPLICA ON
N'SQLNODE1' WITH
(
  ENDPOINT_URL=N'TCP://SQLNODE1.corp.example.com:5022',
  AVAILABILITY_MODE=SYNCHRONOUS_COMMIT,
  FAILOVER_MODE=AUTOMATIC,
  SEEDING_MODE=AUTOMATIC,
  SESSION_TIMEOUT=10,
  PRIMARY_ROLE (ALLOW_CONNECTIONS=ALL),
  SECONDARY_ROLE (ALLOW_CONNECTIONS=READ_ONLY)
),
N'SQLNODE2' WITH
(
  ENDPOINT_URL=N'TCP://SQLNODE2.corp.example.com:5022',
  AVAILABILITY_MODE=SYNCHRONOUS_COMMIT,
  FAILOVER_MODE=AUTOMATIC,
  SEEDING_MODE=AUTOMATIC,
  SESSION_TIMEOUT=10,
  PRIMARY_ROLE (ALLOW_CONNECTIONS=ALL),
  SECONDARY_ROLE (ALLOW_CONNECTIONS=READ_ONLY)
);
GO

Use the exact values returned by SERVERPROPERTY('ServerName') as replica names. On SQLNODE2:

ALTER AVAILABILITY GROUP [AppAG] JOIN;
GO
ALTER AVAILABILITY GROUP [AppAG] GRANT CREATE ANY DATABASE;
GO

The secondary-side grant is required for automatic seeding. Microsoft documents this behavior in Automatic seeding for secondary replicas. For very large databases, manual full/log restore with NORECOVERY is often more controllable.

Seeding warning

Automatic seeding is a full database transfer over the HADR endpoint. It consumes source read I/O, destination write I/O, transaction-log retention, network bandwidth, and cross-AZ transfer. Schedule large seeds and monitor sys.dm_hadr_automatic_seeding; convenience is not the same as zero impact.

9. Create the multi-subnet listener

Run on the current primary:

ALTER AVAILABILITY GROUP [AppAG]
ADD LISTENER N'APPAG-LST'
(
  WITH IP
  (
    (N'10.2.8.30',N'255.255.255.0'),
    (N'10.2.9.30',N'255.255.255.0')
  ),
  PORT=1433
);
GO

Microsoft recommends one static address per participating subnet in a multi-subnet listener. See Configure an availability-group listener. If creation fails, inspect CNO/VCO permissions, DNS dynamic updates, the ENI reservations, and FailoverClustering events before creating manual DNS records.

Listener gotcha

Creating the listener in Failover Cluster Manager is not equivalent to creating the first listener through SQL Server. Use T-SQL, SSMS, or SQL PowerShell so SQL Server and WSFC agree on the listener metadata. Direct WSFC construction is mainly for documented additional-listener scenarios.

10. Align DNS behavior with client drivers

Listeners created through SQL normally use RegisterAllProvidersIP=1, registering both A records. Modern clients should use MultiSubnetFailover=True so the driver attempts them in parallel:

Server=tcp:APPAG-LST.corp.example.com,1433;
Database=AppDb;
Integrated Security=true;
MultiSubnetFailover=True;
Connect Timeout=30;

For legacy clients that cannot handle multiple addresses, deliberately switch to active-address-only registration and a five-minute TTL:

$Listener = Get-ClusterResource -Name "APPAG-LST"
$Listener | Set-ClusterParameter -Name RegisterAllProvidersIP -Value 0
$Listener | Set-ClusterParameter -Name HostRecordTTL -Value 300
Stop-ClusterResource -Name "APPAG-LST"
Start-ClusterResource -Name "APPAG-LST"
Trade-off

RegisterAllProvidersIP=1 plus a capable driver gives fast failover. Setting it to 0 avoids sequential-address timeouts in old drivers but makes recovery depend on DNS update, replication, and cache expiration. Do not change the server setting without testing the actual application driver and connection pool.

Client capabilityRegisterAllProvidersIPHostRecordTTLExpected recovery behavior
Modern supported driver1Default is normally acceptableDNS returns both IPs; MultiSubnetFailover=True attempts them in parallel.
Legacy sequential driver0300 seconds is a common starting pointOnly active IP is published, but failover waits on DNS update and caching.
Unknown or mixed estateDo not guessMeasure firstInventory driver versions and run application-level failover tests before changing cluster properties.
Test the real client

SSMS success does not prove that a Java service, old ODBC provider, .NET Framework application, or pooled connection layer will recover correctly. Record driver name and version, connection-string behavior, pool retry timing, and observed outage during the failover test.

AWS-specific pitfalls

Gotcha 1 · IP ownership

Gratuitous ARP is not an ownership mechanism. Every WSFC and listener IP must exist as an ENI secondary address. Multi-subnet WSFC uses separate addresses joined by OR dependencies; it does not move one address across subnets.

Gotcha 2 · guest networking

Do not persist virtual addresses on the guest NIC. EC2 holds three addresses per ENI, but normal Windows NIC configuration remains on DHCP. WSFC adds and removes its addresses as resources. A static guest configuration containing all three addresses can create duplicate ownership and failover-related drops.

Gotcha 3 · heartbeat design

A second heartbeat ENI is not physical isolation. A second ENI still shares underlying EC2 infrastructure and bandwidth. AWS therefore notes that it does not reproduce a dedicated physical heartbeat network. Enhanced networking and correct instance sizing matter more than copying an on-premises dual-NIC pattern.

Reliability warning · UDP 3343

Test-NetConnection -Port 3343 validates TCP join traffic, not ongoing UDP heartbeat health. Event 1135 or an Unreachable node warrants checking the Domain firewall profile, UDP rules, NACLs, packet loss, CPU stalls, and ENI saturation. Microsoft documents a firewall reload failure mode in its WSFC UDP troubleshooting article.

Performance note · synchronous commit

Cross-AZ transaction completion waits for the secondary to harden and acknowledge the log. Measure HADR_SYNC_COMMIT waits, application p95/p99 latency, log-write latency, and cross-AZ transfer cost under realistic load. “Synchronized” proves data state, not acceptable workload latency.

Failure-domain warning

EC2 instance recovery, an Availability Zone failure, a WSFC role move, and a SQL AG failover are different mechanisms. Test them separately. A clean manual ALTER AVAILABILITY GROUP ... FAILOVER does not prove behavior during packet loss, OS failure, host impairment, or a lost Availability Zone.

Verification and failover testing

Verification should prove all four layers: EC2 address ownership, WSFC resource state, SQL synchronization, and client reconnection. A green Failover Cluster Manager view covers only one of them.

Get-ClusterNode
Get-ClusterGroup
Get-ClusterResource | Format-Table Name,State,OwnerGroup,OwnerNode,ResourceType
Get-ClusterQuorum
Resolve-DnsName APPAG-LST.corp.example.com -Type A

With RegisterAllProvidersIP=1, DNS should return both listener addresses while only the address in the primary's subnet is online. Check database synchronization:

SELECT DB_NAME(drs.database_id) AS database_name,
       ar.replica_server_name,
       drs.synchronization_state_desc,
       drs.synchronization_health_desc,
       drs.log_send_queue_size,
       drs.redo_queue_size
FROM sys.dm_hadr_database_replica_states AS drs
JOIN sys.availability_replicas AS ar ON drs.replica_id=ar.replica_id;
GO

Connect through the listener using a multi-subnet-aware sqlcmd:

sqlcmd -S "tcp:APPAG-LST.corp.example.com,1433" -E -M `
  -Q "SELECT @@SERVERNAME AS ConnectedReplica,SYSDATETIMEOFFSET() AS CheckedAt;"

Require SYNCHRONIZED state and synchronous commit before a planned failover. Connect directly to the target secondary and execute:

sqlcmd -S "SQLNODE2.corp.example.com" -E `
  -Q "ALTER AVAILABILITY GROUP [AppAG] FAILOVER;"

sqlcmd -S "tcp:APPAG-LST.corp.example.com,1433" -E -M `
  -Q "SELECT @@SERVERNAME AS ConnectedReplica,SYSDATETIMEOFFSET() AS CheckedAt;"

Confirm node 2 is primary, 10.2.9.30 is online, 10.2.8.30 is offline, the listener remains online, databases return to synchronized state, and the application reconnects without a new connection string. Fail back and repeat; a one-direction test misses asymmetric rules.

Acceptance criteria
  • The listener resolves according to the chosen DNS model.
  • The client reconnects using the same DNS name and records an acceptable outage.
  • The target becomes primary without forced failover or data loss.
  • Both databases return to SYNCHRONIZED and queues drain.
  • No Event 1135, 1205, 1207, endpoint-authentication, or DNS-update errors appear.
  • The reverse failover succeeds under the same criteria.

Troubleshooting

SymptomCause and fix
IP online in Windows but unreachableVerify it is assigned to the correct ENI and subnet. Windows configuration alone cannot make it routable.
New-Cluster cannot create the nameCheck CNO permissions, stale AD objects, DNS, TCP 135/445, dynamic RPC, and FailoverClustering events.
Listener creation error 19471Grant the CNO permission to create/control the VCO and verify both listener addresses are ENI secondary IPs.
Half of client connections time outDNS returns both addresses but the driver tries sequentially. Use MultiSubnetFailover=True or intentionally choose active-only DNS registration.
Replica is disconnectedTest TCP 5022, endpoint state, DNS, SQL service identities, and endpoint CONNECT grants.
Automatic seeding failsCheck GRANT CREATE ANY DATABASE, disk space, file paths, endpoint traffic, and sys.dm_hadr_automatic_seeding.
Event 1135Investigate UDP 3343, firewall profile changes, NACLs, packet loss, CPU pauses, and network saturation before relaxing thresholds.
Duplicate-address warningRemove cluster/listener addresses from persistent Windows NIC configuration while leaving them assigned to EC2 ENIs.
Get-ClusterLog -Cluster SQLCLUSTER -UseLocalTime -TimeSpan 30 -Destination C:\ClusterLogs

Conclusion

A reliable SQL Server 2025 AG on EC2 is not an on-premises cluster copied into two AWS subnets. Its virtual addresses must exist in both control planes: reserved as ENI secondary addresses in EC2 and managed as clustered IP resources in Windows. Once IP ownership, DHCP, AD permissions, firewall paths, DNS registration, and client behavior align, the SQL layer becomes conventional: endpoints, synchronous replicas, seeding, one listener IP per subnet, and repeatable failover testing through the listener.

Further reading

← browse the archivepart 2: Launch Wizard & FSx →