Set up a production-ready Elasticsearch 8 cluster with SSL/TLS security, optimized JVM settings, and automated backups. Includes memory tuning, index mapping strategies, and common troubleshooting fixes.
Prerequisites
- Root or sudo access
- At least 4GB RAM
- Java 11 or later
- 10GB free disk space
What this solves
Elasticsearch 8 brings enhanced security features, improved performance, and stricter default configurations compared to earlier versions. This tutorial shows you how to install and configure a production-ready Elasticsearch instance with proper security hardening, memory optimization, and backup strategies.
Step-by-step installation
Update system packages and install Java
Elasticsearch 8 requires Java 11 or later. Install OpenJDK and update your package manager.
sudo apt update && sudo apt upgrade -y
sudo apt install -y openjdk-11-jdk curl gnupg apt-transport-https
Add Elasticsearch repository
Import the Elasticsearch GPG key and add the official repository to ensure you get authentic packages and updates.
curl -fsSL https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo gpg --dearmor -o /usr/share/keyrings/elasticsearch-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/elasticsearch-keyring.gpg] https://artifacts.elastic.co/packages/8.x/apt stable main" | sudo tee /etc/apt/sources.list.d/elastic-8.x.list
sudo apt update
Install Elasticsearch 8
Install Elasticsearch and save the automatically generated security credentials that appear during installation.
sudo apt install -y elasticsearch
Configure JVM heap size
Set the JVM heap size to half of your available RAM, with a maximum of 32GB. This prevents memory swapping and optimizes garbage collection.
sudo cp /etc/elasticsearch/jvm.options /etc/elasticsearch/jvm.options.backup
# Set heap size to half of available RAM (example for 8GB system)
-Xms4g
-Xmx4g
Enable G1GC for better performance with large heaps
-XX:+UseG1GC
-XX:G1HeapRegionSize=32m
-XX:+UnlockExperimentalVMOptions
-XX:+UseG1GC
-XX:MaxGCPauseMillis=50
Prevent swapping
-XX:+AlwaysPreTouch
Configure Elasticsearch settings
Set up cluster name, network binding, and security settings. This configuration enables external access while maintaining security.
sudo cp /etc/elasticsearch/elasticsearch.yml /etc/elasticsearch/elasticsearch.yml.backup
# Cluster configuration
cluster.name: production-cluster
node.name: node-1
path.data: /var/lib/elasticsearch
path.logs: /var/log/elasticsearch
Network configuration
network.host: 0.0.0.0
http.port: 9200
transport.port: 9300
Discovery configuration (single node)
discovery.type: single-node
Security configuration
xpack.security.enabled: true
xpack.security.enrollment.enabled: true
xpack.security.http.ssl.enabled: true
xpack.security.http.ssl.keystore.path: certs/http.p12
xpack.security.transport.ssl.enabled: true
xpack.security.transport.ssl.verification_mode: certificate
xpack.security.transport.ssl.client_authentication: required
xpack.security.transport.ssl.keystore.path: certs/transport.p12
xpack.security.transport.ssl.truststore.path: certs/transport.p12
Performance settings
indices.memory.index_buffer_size: 20%
indices.queries.cache.size: 20%
thread_pool.write.queue_size: 1000
Set proper file permissions
Elasticsearch runs as the elasticsearch user and needs proper ownership of its directories. Never use chmod 777 as it compromises security.
sudo chown -R elasticsearch:elasticsearch /etc/elasticsearch
sudo chown -R elasticsearch:elasticsearch /var/lib/elasticsearch
sudo chown -R elasticsearch:elasticsearch /var/log/elasticsearch
sudo chmod 750 /etc/elasticsearch
sudo chmod 660 /etc/elasticsearch/elasticsearch.yml
Configure system limits
Increase file descriptor and virtual memory limits to prevent Elasticsearch from failing under load.
elasticsearch soft nofile 65535
elasticsearch hard nofile 65535
elasticsearch soft nproc 4096
elasticsearch hard nproc 4096
elasticsearch soft memlock unlimited
elasticsearch hard memlock unlimited
echo 'vm.max_map_count=262144' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
Configure firewall rules
Open the necessary ports for Elasticsearch while maintaining security. Only allow access from trusted networks.
sudo ufw allow from 203.0.113.0/24 to any port 9200
sudo ufw allow from 203.0.113.0/24 to any port 9300
sudo ufw reload
Start and enable Elasticsearch
Enable Elasticsearch to start automatically on boot and start the service now.
sudo systemctl daemon-reload
sudo systemctl enable elasticsearch
sudo systemctl start elasticsearch
sudo systemctl status elasticsearch
Reset the elastic user password
Generate a new password for the elastic superuser to ensure you have access to your cluster.
sudo /usr/share/elasticsearch/bin/elasticsearch-reset-password -u elastic -i
Configure SSL certificate for external access
Extract the HTTP CA certificate to verify SSL connections from external clients.
sudo /usr/share/elasticsearch/bin/elasticsearch-certutil ca --silent --pem -out /tmp/ca.zip
sudo unzip /tmp/ca.zip -d /tmp/
sudo cp /tmp/ca/ca.crt /etc/elasticsearch/certs/http_ca.crt
sudo chown elasticsearch:elasticsearch /etc/elasticsearch/certs/http_ca.crt
sudo chmod 644 /etc/elasticsearch/certs/http_ca.crt
Create optimized index template
Set up an index template with performance-optimized mappings and settings for common use cases.
curl -X PUT "https://localhost:9200/_index_template/optimized_template" \
-H "Content-Type: application/json" \
-u elastic:YOUR_PASSWORD \
--cacert /etc/elasticsearch/certs/http_ca.crt \
-d '{
"index_patterns": ["logs-", "metrics-"],
"template": {
"settings": {
"number_of_shards": 1,
"number_of_replicas": 0,
"refresh_interval": "30s",
"index.codec": "best_compression",
"index.mapping.total_fields.limit": 2000
},
"mappings": {
"properties": {
"@timestamp": {
"type": "date",
"format": "strict_date_optional_time||epoch_millis"
},
"message": {
"type": "text",
"analyzer": "standard"
},
"level": {
"type": "keyword"
}
}
}
}
}'
Configure snapshot repository
Set up a local snapshot repository for automated backups. This enables point-in-time recovery and data protection.
sudo mkdir -p /var/backups/elasticsearch
sudo chown elasticsearch:elasticsearch /var/backups/elasticsearch
sudo chmod 750 /var/backups/elasticsearch
curl -X PUT "https://localhost:9200/_snapshot/backup_repository" \
-H "Content-Type: application/json" \
-u elastic:YOUR_PASSWORD \
--cacert /etc/elasticsearch/certs/http_ca.crt \
-d '{
"type": "fs",
"settings": {
"location": "/var/backups/elasticsearch",
"compress": true
}
}'
Set up automated backup script
Create a script to automatically create daily snapshots with retention policies.
#!/bin/bash
Elasticsearch backup script
ES_HOST="https://localhost:9200"
ES_USER="elastic"
ES_PASS="YOUR_PASSWORD"
CA_CERT="/etc/elasticsearch/certs/http_ca.crt"
REPO_NAME="backup_repository"
DATE=$(date +%Y-%m-%d)
SNAPSHOT_NAME="snapshot-${DATE}"
Create snapshot
curl -X PUT "${ES_HOST}/_snapshot/${REPO_NAME}/${SNAPSHOT_NAME}" \
-H "Content-Type: application/json" \
-u "${ES_USER}:${ES_PASS}" \
--cacert "${CA_CERT}" \
-d "{
\"indices\": \"*\",
\"ignore_unavailable\": true,
\"include_global_state\": false
}"
Delete snapshots older than 30 days
OLD_DATE=$(date -d "30 days ago" +%Y-%m-%d)
OLD_SNAPSHOT="snapshot-${OLD_DATE}"
curl -X DELETE "${ES_HOST}/_snapshot/${REPO_NAME}/${OLD_SNAPSHOT}" \
-u "${ES_USER}:${ES_PASS}" \
--cacert "${CA_CERT}" 2>/dev/null
echo "Backup completed: ${SNAPSHOT_NAME}"
sudo chmod 750 /usr/local/bin/elasticsearch-backup.sh
sudo chown root:elasticsearch /usr/local/bin/elasticsearch-backup.sh
Configure cron for automated backups
Schedule daily backups at 2 AM to minimize impact on production workloads.
echo "0 2 * elasticsearch /usr/local/bin/elasticsearch-backup.sh >> /var/log/elasticsearch/backup.log 2>&1" | sudo tee -a /etc/crontab
Verify your setup
Test your Elasticsearch installation and verify security, performance, and backup configurations.
# Check cluster health
curl -X GET "https://localhost:9200/_cluster/health" \
-u elastic:YOUR_PASSWORD \
--cacert /etc/elasticsearch/certs/http_ca.crt
Verify node information
curl -X GET "https://localhost:9200/_nodes/stats" \
-u elastic:YOUR_PASSWORD \
--cacert /etc/elasticsearch/certs/http_ca.crt
Test index creation and search
curl -X POST "https://localhost:9200/test-index/_doc/1" \
-H "Content-Type: application/json" \
-u elastic:YOUR_PASSWORD \
--cacert /etc/elasticsearch/certs/http_ca.crt \
-d '{"message": "Hello Elasticsearch", "timestamp": "2024-01-15T10:00:00Z"}'
Verify snapshot repository
curl -X GET "https://localhost:9200/_snapshot/backup_repository" \
-u elastic:YOUR_PASSWORD \
--cacert /etc/elasticsearch/certs/http_ca.crt
Check service status
sudo systemctl status elasticsearch
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Service fails to start with "Permission denied" | Incorrect file ownership | sudo chown -R elasticsearch:elasticsearch /etc/elasticsearch /var/lib/elasticsearch |
| "max virtual memory areas vm.max_map_count is too low" | Insufficient virtual memory | echo 'vm.max_map_count=262144' | sudo tee -a /etc/sysctl.conf && sudo sysctl -p |
| OutOfMemoryError in logs | Heap size too large or memory swapping | Set heap to max 50% of RAM and enable bootstrap.memory_lock: true |
| SSL connection errors | Missing or incorrect CA certificate | Use --cacert /etc/elasticsearch/certs/http_ca.crt in curl commands |
| Cluster status yellow or red | Shard allocation issues | Check /_cluster/allocation/explain and adjust replica settings |
| High CPU usage during indexing | Too frequent refresh interval | Increase refresh_interval to 30s or disable during bulk operations |
Next steps
- Install and configure Grafana with Prometheus for system monitoring to monitor Elasticsearch metrics
- Install and configure NGINX with HTTP/3 and modern security headers to set up a reverse proxy
- Configure Elasticsearch cluster with multiple nodes for high availability
- Optimize Elasticsearch indexing performance for large datasets
- Setup Elasticsearch authentication with LDAP and Active Directory
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Elasticsearch 8 Installation Script
# Production-ready setup with security and performance optimization
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# Variables
ES_PASSWORD=""
HEAP_SIZE=""
BACKUP_PATH="/var/backups/elasticsearch"
usage() {
echo "Usage: $0 [--heap-size SIZE] [--backup-path PATH]"
echo " --heap-size SIZE Set heap size (e.g., 2g, 4g). Default: 50% of RAM"
echo " --backup-path PATH Backup directory path. Default: /var/backups/elasticsearch"
exit 1
}
log() { echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] $1${NC}"; }
warn() { echo -e "${YELLOW}[WARNING] $1${NC}"; }
error() { echo -e "${RED}[ERROR] $1${NC}"; exit 1; }
cleanup() {
if [[ $? -ne 0 ]]; then
error "Installation failed. Check logs above for details."
fi
}
trap cleanup ERR
check_prerequisites() {
log "[1/12] Checking prerequisites..."
if [[ $EUID -ne 0 ]]; then
error "This script must be run as root"
fi
if ! command -v systemctl &> /dev/null; then
error "systemd is required"
fi
# Calculate default heap size (50% of RAM)
if [[ -z "$HEAP_SIZE" ]]; then
local ram_mb=$(free -m | awk '/^Mem:/ {print $2}')
local heap_mb=$((ram_mb / 2))
if [[ $heap_mb -lt 512 ]]; then
HEAP_SIZE="512m"
else
HEAP_SIZE="${heap_mb}m"
fi
fi
}
detect_distro() {
log "[2/12] Detecting distribution..."
if [[ ! -f /etc/os-release ]]; then
error "Cannot detect distribution"
fi
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update -y"
PKG_INSTALL="apt install -y"
PKG_UPGRADE="apt upgrade -y"
JAVA_PKG="openjdk-11-jdk"
;;
almalinux|rocky|centos|rhel|ol|fedora)
if command -v dnf &> /dev/null; then
PKG_MGR="dnf"
PKG_UPDATE="dnf check-update || true"
PKG_INSTALL="dnf install -y"
PKG_UPGRADE="dnf upgrade -y"
else
PKG_MGR="yum"
PKG_UPDATE="yum check-update || true"
PKG_INSTALL="yum install -y"
PKG_UPGRADE="yum upgrade -y"
fi
JAVA_PKG="java-11-openjdk-devel"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum check-update || true"
PKG_INSTALL="yum install -y"
PKG_UPGRADE="yum upgrade -y"
JAVA_PKG="java-11-openjdk-devel"
;;
*)
error "Unsupported distribution: $ID"
;;
esac
log "Detected: $PRETTY_NAME using $PKG_MGR"
}
update_system() {
log "[3/12] Updating system packages..."
$PKG_UPDATE
$PKG_UPGRADE
$PKG_INSTALL $JAVA_PKG curl gnupg
if [[ "$PKG_MGR" == "apt" ]]; then
$PKG_INSTALL apt-transport-https
fi
}
add_repository() {
log "[4/12] Adding Elasticsearch repository..."
if [[ "$PKG_MGR" == "apt" ]]; then
curl -fsSL https://artifacts.elastic.co/GPG-KEY-elasticsearch | gpg --dearmor -o /usr/share/keyrings/elasticsearch-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/elasticsearch-keyring.gpg] https://artifacts.elastic.co/packages/8.x/apt stable main" > /etc/apt/sources.list.d/elastic-8.x.list
chmod 644 /etc/apt/sources.list.d/elastic-8.x.list
apt update -y
else
rpm --import https://artifacts.elastic.co/GPG-KEY-elasticsearch
cat > /etc/yum.repos.d/elasticsearch.repo << EOF
[elasticsearch]
name=Elasticsearch repository for 8.x packages
baseurl=https://artifacts.elastic.co/packages/8.x/yum
gpgcheck=1
gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch
enabled=1
autorefresh=1
type=rpm-md
EOF
chmod 644 /etc/yum.repos.d/elasticsearch.repo
fi
}
install_elasticsearch() {
log "[5/12] Installing Elasticsearch..."
$PKG_INSTALL elasticsearch
# Capture the generated password
if [[ -f /usr/share/elasticsearch/config/elasticsearch.keystore ]]; then
systemctl daemon-reload
systemctl enable elasticsearch
fi
}
configure_system() {
log "[6/12] Configuring system settings..."
# Set virtual memory
echo 'vm.max_map_count=262144' >> /etc/sysctl.conf
sysctl -p
# Configure limits
cat > /etc/security/limits.d/elasticsearch.conf << EOF
elasticsearch soft memlock unlimited
elasticsearch hard memlock unlimited
elasticsearch soft nofile 65535
elasticsearch hard nofile 65535
EOF
chmod 644 /etc/security/limits.d/elasticsearch.conf
}
configure_elasticsearch() {
log "[7/12] Configuring Elasticsearch..."
# Backup original config
cp /etc/elasticsearch/elasticsearch.yml /etc/elasticsearch/elasticsearch.yml.backup
cat > /etc/elasticsearch/elasticsearch.yml << EOF
cluster.name: production-cluster
node.name: \${HOSTNAME}
path.data: /var/lib/elasticsearch
path.logs: /var/log/elasticsearch
bootstrap.memory_lock: true
network.host: 0.0.0.0
http.port: 9200
discovery.type: single-node
xpack.security.enabled: true
xpack.security.enrollment.enabled: true
xpack.security.http.ssl:
enabled: true
keystore.path: certs/http.p12
xpack.security.transport.ssl:
enabled: true
verification_mode: certificate
keystore.path: certs/transport.p12
truststore.path: certs/transport.p12
cluster.initial_master_nodes: ["\${HOSTNAME}"]
action.destructive_requires_name: true
EOF
# Set JVM heap size
sed -i "s/^-Xms.*/-Xms${HEAP_SIZE}/" /etc/elasticsearch/jvm.options
sed -i "s/^-Xmx.*/-Xmx${HEAP_SIZE}/" /etc/elasticsearch/jvm.options
chown root:elasticsearch /etc/elasticsearch/elasticsearch.yml
chmod 660 /etc/elasticsearch/elasticsearch.yml
}
start_elasticsearch() {
log "[8/12] Starting Elasticsearch service..."
systemctl start elasticsearch
systemctl enable elasticsearch
# Wait for service to be ready
local attempts=0
while ! systemctl is-active --quiet elasticsearch && [[ $attempts -lt 30 ]]; do
sleep 2
((attempts++))
done
if ! systemctl is-active --quiet elasticsearch; then
error "Elasticsearch failed to start"
fi
# Get the auto-generated password
sleep 10
ES_PASSWORD=$(grep "PASSWORD elastic =" /var/log/elasticsearch/*.log 2>/dev/null | tail -1 | cut -d'=' -f2 | tr -d ' ' || echo "")
if [[ -z "$ES_PASSWORD" ]]; then
warn "Could not retrieve auto-generated password. You may need to reset it manually."
fi
}
configure_firewall() {
log "[9/12] Configuring firewall..."
if command -v ufw &> /dev/null; then
ufw --force enable
ufw allow 9200/tcp comment "Elasticsearch HTTP"
ufw allow 9300/tcp comment "Elasticsearch Transport"
elif command -v firewall-cmd &> /dev/null; then
systemctl enable --now firewalld
firewall-cmd --permanent --add-port=9200/tcp
firewall-cmd --permanent --add-port=9300/tcp
firewall-cmd --reload
fi
}
setup_backup() {
log "[10/12] Setting up backup configuration..."
mkdir -p "$BACKUP_PATH"
chown elasticsearch:elasticsearch "$BACKUP_PATH"
chmod 750 "$BACKUP_PATH"
# Create backup script
cat > /usr/local/bin/elasticsearch-backup.sh << EOF
#!/bin/bash
BACKUP_DIR="$BACKUP_PATH"
SNAPSHOT_NAME="backup-\$(date +%Y-%m-%d-%H%M%S)"
ES_URL="https://localhost:9200"
ES_PASSWORD="$ES_PASSWORD"
# Create snapshot repository (run once)
curl -X PUT "\${ES_URL}/_snapshot/backup_repository" \\
-u elastic:\${ES_PASSWORD} \\
--cacert /etc/elasticsearch/certs/http_ca.crt \\
-H "Content-Type: application/json" \\
-d '{"type": "fs", "settings": {"location": "'"\$BACKUP_DIR"'"}}' 2>/dev/null || true
# Create snapshot
curl -X PUT "\${ES_URL}/_snapshot/backup_repository/\${SNAPSHOT_NAME}?wait_for_completion=true" \\
-u elastic:\${ES_PASSWORD} \\
--cacert /etc/elasticsearch/certs/http_ca.crt \\
2>/dev/null
echo "Backup completed: \${SNAPSHOT_NAME}"
EOF
chmod 750 /usr/local/bin/elasticsearch-backup.sh
chown root:elasticsearch /usr/local/bin/elasticsearch-backup.sh
# Add cron job
echo "0 2 * * * elasticsearch /usr/local/bin/elasticsearch-backup.sh >> /var/log/elasticsearch/backup.log 2>&1" >> /etc/crontab
}
verify_installation() {
log "[11/12] Verifying installation..."
# Wait for cluster to be ready
sleep 15
# Check if CA certificate exists
if [[ ! -f /etc/elasticsearch/certs/http_ca.crt ]]; then
warn "CA certificate not found. SSL verification may fail."
return 0
fi
# Test cluster health
if [[ -n "$ES_PASSWORD" ]]; then
local health_status
health_status=$(curl -s -X GET "https://localhost:9200/_cluster/health" \
-u elastic:"$ES_PASSWORD" \
--cacert /etc/elasticsearch/certs/http_ca.crt \
| grep -o '"status":"[^"]*"' | cut -d'"' -f4 2>/dev/null || echo "unknown")
if [[ "$health_status" == "green" ]] || [[ "$health_status" == "yellow" ]]; then
log "Cluster health: $health_status"
else
warn "Cluster health check failed or returned: $health_status"
fi
fi
}
display_summary() {
log "[12/12] Installation complete!"
echo -e "\n${BLUE}=== Elasticsearch 8 Installation Summary ===${NC}"
echo -e "Status: ${GREEN}Installed and Running${NC}"
echo -e "Heap Size: ${YELLOW}$HEAP_SIZE${NC}"
echo -e "Backup Path: ${YELLOW}$BACKUP_PATH${NC}"
if [[ -n "$ES_PASSWORD" ]]; then
echo -e "Elastic Password: ${YELLOW}$ES_PASSWORD${NC}"
else
echo -e "${YELLOW}Note: Reset the elastic user password with:${NC}"
echo " /usr/share/elasticsearch/bin/elasticsearch-reset-password -u elastic"
fi
echo -e "\n${BLUE}Next Steps:${NC}"
echo "1. Test connection: curl -u elastic:PASSWORD https://localhost:9200 --cacert /etc/elasticsearch/certs/http_ca.crt"
echo "2. Configure additional nodes for clustering"
echo "3. Set up monitoring and alerting"
echo "4. Review and customize backup schedule"
echo -e "\n${BLUE}Service Management:${NC}"
echo " Start: systemctl start elasticsearch"
echo " Stop: systemctl stop elasticsearch"
echo " Status: systemctl status elasticsearch"
echo " Logs: journalctl -u elasticsearch -f"
}
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--heap-size)
HEAP_SIZE="$2"
shift 2
;;
--backup-path)
BACKUP_PATH="$2"
shift 2
;;
-h|--help)
usage
;;
*)
echo "Unknown option: $1"
usage
;;
esac
done
# Main execution
check_prerequisites
detect_distro
update_system
add_repository
install_elasticsearch
configure_system
configure_elasticsearch
start_elasticsearch
configure_firewall
setup_backup
verify_installation
display_summary
Review the script before running. Execute with: bash install.sh