KurrentDB Secure 3-Node Cluster Setup Guide for Windows

Madhu Murty avatar
Madhu Murty

Overview

Version: 25.x
Last Updated: January 2026

This guide provides step-by-step instructions for deploying a secure, highly-available 3-node KurrentDB cluster on Windows Server. KurrentDB is an event-native database designed for event sourcing, event-driven architectures, and microservices.

Key Features

  • High Availability: Quorum-based replication ensures data durability and cluster resilience
  • TLS Security: All communications encrypted with TLS certificates
  • Automatic Failover: Leader election ensures continuous operation during node failures
  • Shared Nothing Architecture: Each node maintains its own copy of data

Cluster Topology

A 3-node cluster can tolerate the failure of 1 node while maintaining full read/write capability. The cluster uses a quorum-based replication model where a majority of nodes (2 out of 3) must acknowledge writes before they are confirmed to clients.


Prerequisites

Hardware Requirements (Per Node)

ResourceMinimumRecommended
CPU2 cores4+ cores
RAM4 GB8+ GB
Storage50 GB SSD100+ GB SSD
Network1 Gbps10 Gbps

Software Requirements

  • Windows Server 2019 or later (Windows 10/11 for development)
  • OpenSSL (for certificate generation)
  • PowerShell 5.1 or later
  • Administrator privileges

Network Requirements

PortProtocolPurpose
2113TCPHTTP/HTTPS - Client connections, Admin UI, gRPC
1112TCPInternal TCP - Cluster replication

Important: Ensure these ports are open in:

  • Windows Firewall on each node
  • AWS Security Groups (if using AWS)
  • Any network firewalls between nodes

Time Synchronization (Critical!)

All cluster nodes MUST have synchronized clocks. KurrentDB will reject gossip from nodes with clock differences greater than 60 seconds.

Configure time sync on all nodes:

# For AWS instances - use Amazon Time Sync Service
w32tm /config /manualpeerlist:"169.254.169.123" /syncfromflags:manual /reliable:yes /update
Restart-Service w32time
w32tm /resync /force

# Verify time sync
w32tm /query /status

Example Node Configuration

This guide uses the following example configuration:

NodeHostnameIP Address
Node 1node1.kurrentdb.local172.31.17.231
Node 2node2.kurrentdb.local172.31.30.35
Node 3node3.kurrentdb.local172.31.0.152

Important: Replace these values with your actual hostnames and IP addresses.


Architecture

   ┌─────────────┐           ┌─────────────┐           ┌─────────────┐
   │   Node 1    │◄─────────►│   Node 2    │◄─────────►│   Node 3    │
   │   (Leader)  │  Internal │  (Follower) │  Internal │  (Follower) │
   │             │    TCP    │             │    TCP    │             │
   │  Port 2113  │  Port 1112│  Port 2113  │  Port 1112│  Port 2113  │
   └─────────────┘           └─────────────┘           └─────────────┘
         │                         │                         │
         ▼                         ▼                         ▼
   ┌─────────────┐           ┌─────────────┐           ┌─────────────┐
   │   Data/     │           │   Data/     │           │   Data/     │
   │   Index     │           │   Index     │           │   Index     │
   └─────────────┘           └─────────────┘           └─────────────┘

Communication Flow

  1. Client to Node: HTTPS on port 2113 (gRPC protocol)
  2. Node to Node: Internal TCP on port 1112 (replication)
  3. Gossip Protocol: HTTPS on port 2113 (cluster discovery)

Certificate Generation with OpenSSL

KurrentDB requires TLS certificates for secure communication. We’ll use OpenSSL to create a private Certificate Authority (CA) and node certificates.

Step 1: Install OpenSSL

Option A - Using Chocolatey (Recommended):

choco install openssl -y

Option B - Manual Download: Download from: https://slproweb.com/products/Win32OpenSSL.html

After installation, verify OpenSSL is available:

openssl version

Step 2: Create Directory Structure

Open PowerShell as Administrator and run:

# Create certificate directories
New-Item -ItemType Directory -Path "C:\KurrentDB\certs\ca" -Force
New-Item -ItemType Directory -Path "C:\KurrentDB\certs\node1" -Force
New-Item -ItemType Directory -Path "C:\KurrentDB\certs\node2" -Force
New-Item -ItemType Directory -Path "C:\KurrentDB\certs\node3" -Force

# Navigate to certs directory
cd C:\KurrentDB\certs

Step 3: Generate CA Certificate

# Generate CA private key (2048-bit RSA)
openssl genrsa -out ca\ca.key 2048

# Generate CA certificate (valid for 10 years)
openssl req -new -x509 -days 3650 -key ca\ca.key -out ca\ca.crt -subj "/CN=KurrentDB CA"

This creates:

  • C:\KurrentDB\certs\ca\ca.crt - CA public certificate (distribute to all nodes)
  • C:\KurrentDB\certs\ca\ca.key - CA private key (keep secure!)

Step 4: Generate Node 1 Certificate

Create the OpenSSL configuration file for Node 1. Use Notepad to avoid encoding issues:

notepad node1\openssl.cnf

Paste the following content and save as ANSI encoding:

[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no

[req_distinguished_name]
CN = eventstoredb-node

[v3_req]
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth, clientAuth
subjectAltName = @alt_names

[alt_names]
DNS.1 = localhost
DNS.2 = node1.kurrentdb.local
IP.1 = 127.0.0.1
IP.2 = 172.31.17.231

Important: Replace 172.31.17.231 with your Node 1 IP address.

Generate the certificate:

# Generate private key
openssl genrsa -out node1\node.key 2048

# Generate certificate signing request (CSR)
openssl req -new -key node1\node.key -out node1\node.csr -config node1\openssl.cnf

# Sign with CA to create certificate
openssl x509 -req -days 3650 -in node1\node.csr -CA ca\ca.crt -CAkey ca\ca.key -CAcreateserial -out node1\node.crt -extensions v3_req -extfile node1\openssl.cnf

# Clean up CSR
Remove-Item node1\node.csr

Step 5: Generate Node 2 Certificate

Create configuration file:

notepad node2\openssl.cnf

Content (save as ANSI):

[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no

[req_distinguished_name]
CN = eventstoredb-node

[v3_req]
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth, clientAuth
subjectAltName = @alt_names

[alt_names]
DNS.1 = localhost
DNS.2 = node2.kurrentdb.local
IP.1 = 127.0.0.1
IP.2 = 172.31.30.35

Generate certificate:

openssl genrsa -out node2\node.key 2048
openssl req -new -key node2\node.key -out node2\node.csr -config node2\openssl.cnf
openssl x509 -req -days 3650 -in node2\node.csr -CA ca\ca.crt -CAkey ca\ca.key -CAcreateserial -out node2\node.crt -extensions v3_req -extfile node2\openssl.cnf
Remove-Item node2\node.csr

Step 6: Generate Node 3 Certificate

Create configuration file:

notepad node3\openssl.cnf

Content (save as ANSI):

[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no

[req_distinguished_name]
CN = eventstoredb-node

[v3_req]
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth, clientAuth
subjectAltName = @alt_names

[alt_names]
DNS.1 = localhost
DNS.2 = node3.kurrentdb.local
IP.1 = 127.0.0.1
IP.2 = 172.31.0.152

Generate certificate:

openssl genrsa -out node3\node.key 2048
openssl req -new -key node3\node.key -out node3\node.csr -config node3\openssl.cnf
openssl x509 -req -days 3650 -in node3\node.csr -CA ca\ca.crt -CAkey ca\ca.key -CAcreateserial -out node3\node.crt -extensions v3_req -extfile node3\openssl.cnf
Remove-Item node3\node.csr

Step 7: Verify Certificates

# Verify certificate chain
openssl verify -CAfile ca\ca.crt node1\node.crt
openssl verify -CAfile ca\ca.crt node2\node.crt
openssl verify -CAfile ca\ca.crt node3\node.crt

# Check certificate details and SANs
openssl x509 -in node1\node.crt -noout -subject -dates -ext subjectAltName
openssl x509 -in node2\node.crt -noout -subject -dates -ext subjectAltName
openssl x509 -in node3\node.crt -noout -subject -dates -ext subjectAltName

Expected output for each verify command: nodeX\node.crt: OK

Step 8: Distribute Certificates

Copy the appropriate certificates to each node:

FileNode 1Node 2Node 3
ca\ca.crt
node1\node.crt
node1\node.key
node2\node.crt
node2\node.key
node3\node.crt
node3\node.key

Step 9: Install CA Certificate

Run on ALL nodes as Administrator:

Import-Certificate -FilePath "C:\KurrentDB\certs\ca\ca.crt" -CertStoreLocation Cert:\LocalMachine\Root

Node Configuration

Create a YAML configuration file for each node. Important notes:

  • GossipSeed should contain the OTHER nodes (not itself)
  • NodeHostAdvertiseAs should be set to the IP address to avoid DNS resolution issues
  • All paths must match your actual certificate locations

Node 1 Configuration

Create C:\KurrentDB\config\kurrentdb.conf on Node 1:

---
# Cluster Configuration
ClusterSize: 3
DiscoverViaDns: false
GossipSeed: 172.31.30.35:2113,172.31.0.152:2113

# Network Configuration
NodeIp: 172.31.17.231
NodePort: 2113
ReplicationIp: 172.31.17.231
ReplicationPort: 1112

# Advertise IP address (avoids DNS resolution issues)
NodeHostAdvertiseAs: 172.31.17.231

# Certificate Configuration
CertificateFile: C:\KurrentDB\certs\node1\node.crt
CertificatePrivateKeyFile: C:\KurrentDB\certs\node1\node.key
TrustedRootCertificatesPath: C:\KurrentDB\certs\ca

# Data Directories
Db: C:\KurrentDB\data
Log: C:\KurrentDB\logs

# Features
RunProjections: All
StartStandardProjections: true
EnableAtomPubOverHttp: true

Node 2 Configuration

Create C:\KurrentDB\config\kurrentdb.conf on Node 2:

---
# Cluster Configuration
ClusterSize: 3
DiscoverViaDns: false
GossipSeed: 172.31.17.231:2113,172.31.0.152:2113

# Network Configuration
NodeIp: 172.31.30.35
NodePort: 2113
ReplicationIp: 172.31.30.35
ReplicationPort: 1112

# Advertise IP address (avoids DNS resolution issues)
NodeHostAdvertiseAs: 172.31.30.35

# Certificate Configuration
CertificateFile: C:\KurrentDB\certs\node2\node.crt
CertificatePrivateKeyFile: C:\KurrentDB\certs\node2\node.key
TrustedRootCertificatesPath: C:\KurrentDB\certs\ca

# Data Directories
Db: C:\KurrentDB\data
Log: C:\KurrentDB\logs

# Features
RunProjections: All
StartStandardProjections: true
EnableAtomPubOverHttp: true

Node 3 Configuration

Create C:\KurrentDB\config\kurrentdb.conf on Node 3:

---
# Cluster Configuration
ClusterSize: 3
DiscoverViaDns: false
GossipSeed: 172.31.17.231:2113,172.31.30.35:2113

# Network Configuration
NodeIp: 172.31.0.152
NodePort: 2113
ReplicationIp: 172.31.0.152
ReplicationPort: 1112

# Advertise IP address (avoids DNS resolution issues)
NodeHostAdvertiseAs: 172.31.0.152

# Certificate Configuration
CertificateFile: C:\KurrentDB\certs\node3\node.crt
CertificatePrivateKeyFile: C:\KurrentDB\certs\node3\node.key
TrustedRootCertificatesPath: C:\KurrentDB\certs\ca

# Data Directories
Db: C:\KurrentDB\data
Log: C:\KurrentDB\logs

# Features
RunProjections: All
StartStandardProjections: true
EnableAtomPubOverHttp: true

Configuration Parameters Reference

ParameterDescription
ClusterSizeNumber of nodes in the cluster (3 for HA)
DiscoverViaDnsSet to false when using gossip seeds
GossipSeedComma-separated list of OTHER cluster nodes (not self)
NodeIpIP address for client connections
NodePortPort for HTTP/gRPC connections (default: 2113)
ReplicationIpIP address for internal cluster communication
ReplicationPortPort for replication traffic (default: 1112)
NodeHostAdvertiseAsIP/hostname advertised to clients (use IP to avoid DNS issues)
CertificateFilePath to node’s TLS certificate
CertificatePrivateKeyFilePath to node’s private key
TrustedRootCertificatesPathDirectory containing CA certificate

Installation Steps

Step 1: Install KurrentDB (on ALL nodes)

Option A - Chocolatey (Recommended):

choco install kurrentdb -y

Option B - Manual Download: Download from https://cloudsmith.io/~eventstore/repos/kurrent-latest/packages/

Step 2: Create Directory Structure (on ALL nodes)

New-Item -ItemType Directory -Path "C:\KurrentDB\config" -Force
New-Item -ItemType Directory -Path "C:\KurrentDB\data" -Force
New-Item -ItemType Directory -Path "C:\KurrentDB\logs" -Force
New-Item -ItemType Directory -Path "C:\KurrentDB\certs\ca" -Force
New-Item -ItemType Directory -Path "C:\KurrentDB\certs\node1" -Force  # Adjust nodeX per node

Step 3: Configure Windows Firewall (on ALL nodes)

New-NetFirewallRule -DisplayName "KurrentDB HTTP" -Direction Inbound -Protocol TCP -LocalPort 2113 -Action Allow
New-NetFirewallRule -DisplayName "KurrentDB Replication" -Direction Inbound -Protocol TCP -LocalPort 1112 -Action Allow

Step 4: Configure AWS Security Groups (if applicable)

Ensure your Security Group allows inbound traffic on ports 2113 and 1112 from your VPC CIDR block.

Step 5: Sync Time (on ALL nodes)

# For AWS - use Amazon Time Sync
w32tm /config /manualpeerlist:"169.254.169.123" /syncfromflags:manual /reliable:yes /update
Restart-Service w32time
w32tm /resync /force

Step 6: Verify Configuration

Test configuration on each node before starting:

KurrentDB.exe --what-if --config=C:\KurrentDB\config\kurrentdb.conf

Starting the Cluster

Option A: Run as Console Application (Testing)

Start each node in a separate PowerShell window:

KurrentDB.exe --config=C:\KurrentDB\config\kurrentdb.conf

Important: Start all three nodes within 1-2 minutes of each other.

Option B: Run as Windows Service (Production)

Create the Service (on each node):

sc.exe create "KurrentDB" start=delayed-auto binpath="\"C:\ProgramData\chocolatey\lib\kurrentdb\tools\KurrentDB.exe\" --config=\"C:\KurrentDB\config\kurrentdb.conf\""

Configure Service Recovery:

sc.exe failure "KurrentDB" reset=0 actions=restart/5000/restart/5000/restart/5000

Start the Service:

sc.exe start "KurrentDB"

Cluster Formation

  1. Start all three nodes within a short timeframe
  2. Nodes discover each other via gossip seeds
  3. Election occurs to select the leader
  4. Once quorum (2+ nodes) is reached, the cluster becomes operational
  5. You should see logs indicating “ELECTIONS DONE” and node roles (Leader/Follower)

Verification

Check Cluster Status via Admin UI

  1. Open a web browser
  2. Navigate to: https://<any-node-ip>:2113
  3. Accept the certificate warning (if CA not installed on client)
  4. Login with default credentials:
    • Username: admin
    • Password: changeit

Important: Change the default passwords immediately after first login!

Check Node Health via API

Using curl (Recommended):

# Check if node is alive
curl.exe -k https://172.31.17.231:2113/health/live

# Check cluster gossip (shows all nodes and their roles)
curl.exe -k https://172.31.17.231:2113/gossip

Using PowerShell 5.1:

# First, disable certificate validation (run once per session)
add-type @"
using System.Net;
using System.Security.Cryptography.X509Certificates;
public class TrustAllCertsPolicy : ICertificatePolicy {
    public bool CheckValidationResult(ServicePoint srvPoint, X509Certificate certificate, WebRequest request, int certificateProblem) { return true; }
}
"@
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy

# Then make requests
Invoke-WebRequest -Uri "https://172.31.17.231:2113/health/live"
Invoke-WebRequest -Uri "https://172.31.17.231:2113/gossip"

Using PowerShell 7+:

Invoke-WebRequest -Uri "https://172.31.17.231:2113/health/live" -SkipCertificateCheck

Cluster Health Check Script

# Disable certificate validation for this session
add-type @"
using System.Net;
using System.Security.Cryptography.X509Certificates;
public class TrustAllCertsPolicy : ICertificatePolicy {
    public bool CheckValidationResult(ServicePoint srvPoint, X509Certificate certificate, WebRequest request, int certificateProblem) { return true; }
}
"@
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy

$nodes = @(
    "https://172.31.17.231:2113",
    "https://172.31.30.35:2113",
    "https://172.31.0.152:2113"
)

Write-Host "KurrentDB Cluster Health Check" -ForegroundColor Cyan
Write-Host "==============================" -ForegroundColor Cyan

foreach ($node in $nodes) {
    try {
        $response = Invoke-WebRequest -Uri "$node/health/live" -TimeoutSec 5
        Write-Host "$node - OK" -ForegroundColor Green
    }
    catch {
        Write-Host "$node - FAILED" -ForegroundColor Red
    }
}

Or use curl for a quick check:

curl.exe -k https://172.31.17.231:2113/health/live
curl.exe -k https://172.31.30.35:2113/health/live
curl.exe -k https://172.31.0.152:2113/health/live

Verify Cluster Membership

The gossip endpoint should show all three nodes:

  • One node as Leader
  • Two nodes as Follower

Client Connection

Connection String

kurrentdb://admin:changeit@172.31.17.231:2113,172.31.30.35:2113,172.31.0.152:2113?tls=true

.NET Client Example

using KurrentDB.Client;

var settings = KurrentClientSettings.Create(
    "kurrentdb://admin:changeit@172.31.17.231:2113,172.31.30.35:2113,172.31.0.152:2113?tls=true"
);
var client = new KurrentClient(settings);

// Append events
await client.AppendToStreamAsync(
    "my-stream",
    StreamState.Any,
    new[] { eventData }
);

Certificate Verification Options

Option 1: Install CA on Client Machine (Recommended)

Import-Certificate -FilePath "ca.crt" -CertStoreLocation Cert:\CurrentUser\Root

Option 2: Skip Verification (Development Only)

kurrentdb://...?tls=true&tlsVerifyCert=false

DNS Configuration (Optional)

If you prefer to use DNS names, add to your hosts file (C:\Windows\System32\drivers\etc\hosts):

172.31.17.231  node1.kurrentdb.local
172.31.30.35   node2.kurrentdb.local
172.31.0.152   node3.kurrentdb.local

Troubleshooting

Common Issues and Solutions

IssueCauseSolution
Nodes can’t find each otherFirewall blockingOpen ports 2113 and 1112 in Windows Firewall and Security Groups
Certificate errorsWrong SANs or pathVerify certificate includes correct IP/DNS; check file paths
Election timeoutsOnly one node runningStart all nodes; need 2+ for quorum
Time difference errorsClocks out of syncSync time with w32tm /resync /force
Redirect to DNS name failsDNS not configuredSet NodeHostAdvertiseAs to IP address
Path not found errorsWrong config pathsVerify certificate and data directory paths exist

Diagnostic Commands

Check connectivity between nodes:

Test-NetConnection -ComputerName 172.31.30.35 -Port 2113
Test-NetConnection -ComputerName 172.31.30.35 -Port 1112

Check time sync:

w32tm /query /status
[System.DateTime]::UtcNow.ToString("yyyy-MM-dd HH:mm:ss")

Verify certificate details:

openssl x509 -in C:\KurrentDB\certs\node1\node.crt -noout -subject -dates -ext subjectAltName
openssl verify -CAfile C:\KurrentDB\certs\ca\ca.crt C:\KurrentDB\certs\node1\node.crt

Check firewall rules:

Get-NetFirewallRule -DisplayName "KurrentDB*" | Format-Table Name, DisplayName, Enabled, Action

Test configuration without starting:

KurrentDB.exe --what-if --config=C:\KurrentDB\config\kurrentdb.conf

Log Analysis

Review logs at C:\KurrentDB\logs\. Key entries to watch:

Log MessageMeaning
ELECTIONS DONECluster successfully elected a leader
Became Leader / Became FollowerNode role assignment
Time difference... too greatClock sync issue between nodes
TIMED OUT! (S=ElectingLeader)Can’t reach other nodes

Security Best Practices

Password Management

  1. Change default passwords immediately after cluster setup
  2. Use strong, unique passwords for admin and ops users
  3. Create application-specific user accounts with minimum necessary permissions

Certificate Security

  1. Protect private keys: Restrict access to *.key files
  2. Regular rotation: Plan for certificate renewal before expiration
  3. Secure CA key: Keep CA private key offline or in secure storage
  4. Monitor expiration: Set up alerts for certificate expiry

Network Security

  1. Isolate cluster network: Use private subnets/VLANs
  2. Restrict Security Groups: Only allow necessary IPs and ports
  3. Use internal IPs: Keep cluster traffic on private network

Quick Reference

Essential Commands

TaskCommand
Start servicesc.exe start KurrentDB
Stop servicesc.exe stop KurrentDB
Check service statussc.exe query KurrentDB
View logsGet-Content C:\KurrentDB\logs\*.log -Tail 100
Test configKurrentDB.exe --what-if --config=...
Health checkcurl.exe -k https://IP:2113/health/live
Sync timew32tm /resync /force

Default Credentials

UserPasswordPurpose
adminchangeitFull administrative access
opschangeitOperational access

Key Directories

PathPurpose
C:\KurrentDB\config\Configuration files
C:\KurrentDB\data\Event data
C:\KurrentDB\logs\Log files
C:\KurrentDB\certs\TLS certificates

Ports

PortPurpose
2113Client connections, Admin UI, gRPC, Gossip
1112Internal cluster replication

Support and Resources


Document prepared for KurrentDB v25.x on Windows Server.