ELK Stack — High Availability
High Availability for Elasticsearch
High Availability (HA) ensures your Elasticsearch cluster continues to serve reads and writes even when individual nodes, racks, or entire availability zones fail. A well-designed HA deployment combines dedicated master nodes, zone-aware shard allocation, replica strategies, and load balancing.
Multi-Node Cluster Architecture
A production cluster separates responsibilities across node roles:
| Role | Purpose | Minimum Count |
|---|---|---|
| Master-eligible | Cluster state management, shard allocation | 3 |
| Data | Stores shards, executes searches and indexing | 2+ |
| Ingest | Pre-processes documents (pipelines) | 1+ (optional) |
| Coordinating-only | Routes requests, scatter-gather | 1+ (optional) |
| ML | Machine learning jobs | As needed |
Dedicated Master Nodes
The master node is responsible for cluster-wide operations — creating/deleting indices, allocating shards, and tracking node membership. Never run data workloads on master nodes.
Why 3 Master-Eligible Nodes?
Elasticsearch uses a quorum-based consensus algorithm. With 3 master-eligible nodes the quorum is 2, meaning the cluster tolerates one master node failure:
Quorum = (number_of_master_eligible_nodes / 2) + 1
= (3 / 2) + 1 = 2
Voting Configuration
Starting in ES 7.x, the voting configuration is managed automatically. You can inspect it:
curl -s localhost:9200/_cluster/state?filter_path=metadata.cluster_coordination.last_committed_config | jq .
For initial bootstrap, set cluster.initial_master_nodes only on the first start:
# elasticsearch.yml — first start only
cluster.initial_master_nodes:
- es-master-1
- es-master-2
- es-master-3
Warning: Remove
cluster.initial_master_nodesafter the cluster is formed. Leaving it in place can cause split-brain during a full cluster restart.
Cross-Zone / Cross-AZ Deployment
Distributing nodes across availability zones (AZs) protects against zone-level failures.
ASCII Diagram — Multi-AZ Deployment
┌──────────────────────────────────────────────────────────────────┐
│ Load Balancer │
│ (Kibana + ES coordinating) │
└──────────┬──────────────────┬──────────────────┬─────────────────┘
│ │ │
┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ AZ-1 │ │ AZ-2 │ │ AZ-3 │
│ │ │ │ │ │
│ es-master-1 │ │ es-master-2 │ │ es-master-3 │
│ es-data-1a │ │ es-data-2a │ │ es-data-3a │
│ es-data-1b │ │ es-data-2b │ │ es-data-3b │
│ kibana-1 │ │ kibana-2 │ │ kibana-3 │
│ logstash-1 │ │ logstash-2 │ │ logstash-3 │
└─────────────┘ └─────────────┘ └─────────────┘
Shard Allocation Awareness
Use cluster.routing.allocation.awareness to spread primary and replica shards across zones:
# elasticsearch.yml — every data node
cluster.routing.allocation.awareness.attributes: zone
# On each node, set the zone attribute
node.attr.zone: az-1 # change per AZ
To force allocation awareness (prevent all copies of a shard from landing in one zone):
cluster.routing.allocation.awareness.force.zone.values: az-1,az-2,az-3
This guarantees that if only one zone is available, unassigned replicas stay unassigned rather than all landing in the surviving zone.
Replica Strategy
Replicas serve two purposes — fault tolerance and read throughput.
| Scenario | Recommended Replicas |
|---|---|
| 2-zone deployment | 1 replica (each shard has a copy in each zone) |
| 3-zone deployment | 1–2 replicas |
| Critical data | 2 replicas (survive 2-node failures) |
| High read throughput | 2+ replicas (more copies = more search parallelism) |
Set the default on index creation:
curl -X PUT "localhost:9200/my-index" -H 'Content-Type: application/json' -d'
{
"settings": {
"number_of_shards": 5,
"number_of_replicas": 1
}
}'
Network Partitioning Handling
Elasticsearch handles network partitions through:
- Fault detection — The elected master pings data nodes. If a node doesn’t respond within
discovery.cluster_state_publish_timeout, it is removed. - No split-brain — The quorum-based leader election prevents two masters from forming.
- Shard reallocation — After a node leaves, the master reallocates shards to remaining nodes (controlled by
cluster.routing.allocation.node_concurrent_recoveries).
Key settings:
# elasticsearch.yml
discovery.seed_hosts:
- es-master-1:9300
- es-master-2:9300
- es-master-3:9300
cluster.fault_detection.leader_check.interval: 1s
cluster.fault_detection.leader_check.timeout: 10s
cluster.fault_detection.follower_check.interval: 1s
cluster.fault_detection.follower_check.timeout: 10s
Cross-Cluster Replication (CCR)
CCR replicates indices from a leader cluster to one or more follower clusters for disaster recovery or geo-locality.
Step 1 — Configure Remote Cluster
curl -X PUT "localhost:9200/_cluster/settings" -H 'Content-Type: application/json' -d'
{
"persistent": {
"cluster.remote.dc-west": {
"seeds": ["es-west-1:9300", "es-west-2:9300"]
}
}
}'
Step 2 — Create Follower Index
curl -X PUT "localhost:9200/logs-follower/_ccr/follow" -H 'Content-Type: application/json' -d'
{
"remote_cluster": "dc-west",
"leader_index": "logs-2026.04"
}'
Step 3 — Monitor Replication
curl -s "localhost:9200/logs-follower/_ccr/stats" | jq .
Disaster Recovery Planning
| Strategy | RPO | RTO | Complexity |
|---|---|---|---|
| Replicas within cluster | 0 | Seconds | Low |
| CCR to secondary cluster | Near-zero | Minutes | Medium |
| Snapshots to S3/GCS | Hours (depends on schedule) | 30 min–hours | Low |
| CCR + Snapshots | Near-zero backup, minutes restore | Minutes | High |
RPO = Recovery Point Objective (how much data you can afford to lose). RTO = Recovery Time Objective (how fast you need to recover).
Load Balancing
Place a load balancer in front of both Elasticsearch and Kibana:
- Elasticsearch — Round-robin to coordinating-only nodes (or data nodes if no dedicated coordinators). Use the
/_cluster/healthendpoint for health checks. - Kibana — Round-robin across Kibana instances. All instances must point to the same ES cluster and share the same
encryptionKey.
Example Nginx upstream for Elasticsearch:
upstream elasticsearch {
least_conn;
server es-coord-1:9200;
server es-coord-2:9200;
server es-coord-3:9200;
}
server {
listen 9200;
location / {
proxy_pass http://elasticsearch;
proxy_set_header Host $host;
}
location /_cluster/health {
proxy_pass http://elasticsearch;
}
}
Complete HA elasticsearch.yml
# ── Cluster ──
cluster.name: production-elk
node.name: es-data-1a
# ── Node Roles ──
node.roles: [data, ingest]
# ── Paths ──
path.data: /var/lib/elasticsearch
path.logs: /var/log/elasticsearch
# ── Network ──
network.host: 0.0.0.0
http.port: 9200
transport.port: 9300
# ── Discovery ──
discovery.seed_hosts:
- es-master-1:9300
- es-master-2:9300
- es-master-3:9300
# ── Zone Awareness ──
node.attr.zone: az-1
cluster.routing.allocation.awareness.attributes: zone
cluster.routing.allocation.awareness.force.zone.values: az-1,az-2,az-3
# ── Recovery ──
gateway.expected_data_nodes: 6
gateway.recover_after_data_nodes: 4
cluster.routing.allocation.node_concurrent_recoveries: 4
# ── Fault Detection ──
cluster.fault_detection.leader_check.interval: 1s
cluster.fault_detection.leader_check.timeout: 10s
# ── Security (TLS) ──
xpack.security.enabled: true
xpack.security.transport.ssl.enabled: true
xpack.security.transport.ssl.keystore.path: certs/elastic-certificates.p12
xpack.security.transport.ssl.truststore.path: certs/elastic-certificates.p12
xpack.security.http.ssl.enabled: true
xpack.security.http.ssl.keystore.path: certs/http.p12
Quick Reference
| Task | Command / Setting |
|---|---|
| Check cluster health | GET _cluster/health |
| View shard allocation | GET _cat/shards?v |
| Check zone awareness | GET _cluster/settings?include_defaults&filter_path=**.awareness* |
| List voting config | GET _cluster/state?filter_path=metadata.cluster_coordination |
| CCR follower stats | GET /<index>/_ccr/stats |
| Pause CCR | POST /<index>/_ccr/pause_follow |
| Resume CCR | POST /<index>/_ccr/resume_follow |
Next Steps
With a highly available cluster in place, the next critical task is protecting your data with automated backups. Continue to Backup & Restore.