Set up HashiCorp Consul for distributed service discovery with a secure three-node cluster, ACL authentication, and encrypted communication for production microservices environments.
Prerequisites
- Root or sudo access
- At least 3 servers for clustering
- Static IP addresses for cluster nodes
- Basic understanding of networking and DNS
What this solves
Consul provides service discovery, health checking, and configuration management for distributed applications and microservices. This tutorial sets up a production-ready Consul cluster with encryption, ACLs, and monitoring capabilities to handle service registration and discovery across multiple nodes.
Step-by-step installation
Update system packages
Start by updating your package manager to ensure you have the latest security patches and dependencies.
sudo apt update && sudo apt upgrade -y
sudo apt install -y wget unzip curl
Create Consul system user
Create a dedicated system user for Consul with minimal privileges and no shell access.
sudo useradd --system --home /etc/consul.d --shell /bin/false consul
sudo mkdir -p /opt/consul /etc/consul.d /opt/consul/data
sudo chown consul:consul /opt/consul /etc/consul.d /opt/consul/data
Download and install Consul binary
Download the latest Consul binary from HashiCorp's releases and install it to /usr/local/bin.
CONSUL_VERSION="1.17.1"
wget https://releases.hashicorp.com/consul/${CONSUL_VERSION}/consul_${CONSUL_VERSION}_linux_amd64.zip
unzip consul_${CONSUL_VERSION}_linux_amd64.zip
sudo mv consul /usr/local/bin/
sudo chmod +x /usr/local/bin/consul
consul version
Generate encryption key and certificates
Create the encryption key for gossip protocol and generate CA certificates for TLS communication between nodes.
consul keygen > /tmp/consul_encrypt.key
cd /etc/consul.d
sudo consul tls ca create
sudo consul tls cert create -server -dc dc1
sudo chown consul:consul .pem .key
Configure Consul server
Create the main configuration file for the Consul server with clustering, encryption, and ACL settings.
datacenter = "dc1"
data_dir = "/opt/consul/data"
log_level = "INFO"
node_name = "consul-server-1"
bind_addr = "203.0.113.10"
client_addr = "0.0.0.0"
server = true
bootstrap_expect = 3
retry_join = ["203.0.113.10", "203.0.113.11", "203.0.113.12"]
ui_config {
enabled = true
}
connect {
enabled = true
}
ports {
grpc = 8502
https = 8501
}
encrypt = "REPLACE_WITH_YOUR_ENCRYPT_KEY"
acl = {
enabled = true
default_policy = "deny"
enable_token_persistence = true
}
tls {
defaults {
ca_file = "/etc/consul.d/consul-agent-ca.pem"
cert_file = "/etc/consul.d/dc1-server-consul-0.pem"
key_file = "/etc/consul.d/dc1-server-consul-0-key.pem"
verify_incoming = true
verify_outgoing = true
}
internal_rpc {
verify_server_hostname = true
}
}
Set proper file permissions
Secure the configuration files and certificates with appropriate permissions for the consul user.
sudo chown -R consul:consul /etc/consul.d
sudo chmod 640 /etc/consul.d/consul.hcl
sudo chmod 600 /etc/consul.d/.pem /etc/consul.d/-key.pem
Create systemd service file
Configure Consul as a systemd service with proper security restrictions and resource limits.
[Unit]
Description=Consul Service Discovery Agent
Documentation=https://www.consul.io/
Requires=network-online.target
After=network-online.target
ConditionFileNotEmpty=/etc/consul.d/consul.hcl
[Service]
Type=notify
User=consul
Group=consul
ExecStart=/usr/local/bin/consul agent -config-dir=/etc/consul.d/
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
LimitNOFILE=65536
Security settings
NoNewPrivileges=yes
PrivateTmp=yes
ProtectHome=yes
ProtectSystem=strict
ReadWritePaths=/opt/consul/data
[Install]
WantedBy=multi-user.target
Configure firewall rules
Open the necessary ports for Consul cluster communication, UI access, and client connections.
sudo ufw allow 8300/tcp comment 'Consul server RPC'
sudo ufw allow 8301/tcp comment 'Consul serf LAN'
sudo ufw allow 8301/udp comment 'Consul serf LAN'
sudo ufw allow 8302/tcp comment 'Consul serf WAN'
sudo ufw allow 8302/udp comment 'Consul serf WAN'
sudo ufw allow 8500/tcp comment 'Consul HTTP API'
sudo ufw allow 8501/tcp comment 'Consul HTTPS API'
sudo ufw allow 8502/tcp comment 'Consul gRPC'
sudo ufw reload
Start and enable Consul service
Enable Consul to start automatically on boot and start the service for the first time.
sudo systemctl daemon-reload
sudo systemctl enable consul
sudo systemctl start consul
sudo systemctl status consul
Bootstrap ACL system
Initialize the ACL system and create the bootstrap token for administrative access.
sleep 10
consul acl bootstrap
Create ACL policies and tokens
Set up specific ACL policies for different service types and create corresponding tokens.
node_prefix "" {
policy = "write"
}
service_prefix "" {
policy = "read"
}
key_prefix "" {
policy = "read"
}
export CONSUL_HTTP_TOKEN="YOUR_BOOTSTRAP_TOKEN_HERE"
consul acl policy create -name "node-policy" -description "Node registration policy" -rules @/tmp/node-policy.hcl
consul acl token create -description "Node token" -policy-name "node-policy"
Configure service registration
Create a sample service definition to test service discovery functionality.
{
"service": {
"name": "web",
"tags": ["nginx", "frontend"],
"port": 80,
"check": {
"http": "http://localhost:80/health",
"interval": "10s",
"timeout": "3s"
}
}
}
sudo chown consul:consul /etc/consul.d/web-service.json
sudo systemctl reload consul
Verify your setup
Check that Consul is running correctly and the cluster is healthy.
consul members
consul info
consul catalog services
curl -H "X-Consul-Token: YOUR_TOKEN" http://localhost:8500/v1/status/leader
consul acl token list
Access the Consul UI at https://your-server-ip:8501 using your bootstrap token. You should see your services, nodes, and health checks.
Configure health checks and monitoring
Set up comprehensive health monitoring
Configure Consul to monitor system resources and service health with custom check intervals.
{
"checks": [
{
"id": "disk-usage",
"name": "Disk Usage Check",
"script": "df -h | grep -E '9[0-9]%|100%'",
"interval": "30s",
"status": "passing"
},
{
"id": "memory-usage",
"name": "Memory Usage Check",
"script": "free | awk 'FNR==2{printf \"%.2f%%\\n\", $3/$2*100}'",
"interval": "60s"
}
]
}
For comprehensive monitoring integration, consider setting up Grafana with Prometheus to visualize Consul metrics and cluster health.
Set up Consul agents on client nodes
Configure Consul client agents
Install Consul on client nodes that need to register services or perform service discovery.
datacenter = "dc1"
data_dir = "/opt/consul/data"
log_level = "INFO"
node_name = "client-node-1"
bind_addr = "203.0.113.20"
server = false
retry_join = ["203.0.113.10", "203.0.113.11", "203.0.113.12"]
encrypt = "SAME_ENCRYPT_KEY_AS_SERVERS"
acl = {
enabled = true
default_policy = "deny"
tokens = {
default = "CLIENT_TOKEN_HERE"
}
}
tls {
defaults {
ca_file = "/etc/consul.d/consul-agent-ca.pem"
verify_incoming = false
verify_outgoing = true
}
}
This setup integrates well with Traefik reverse proxy for automatic service discovery and load balancing in containerized environments.
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Consul won't start | Configuration syntax error | consul validate /etc/consul.d/consul.hcl |
| Nodes can't join cluster | Firewall blocking ports | Check ports 8300-8302 are open between nodes |
| ACL token denied errors | Insufficient token permissions | Create policy with required permissions and attach to token |
| TLS certificate errors | Mismatched certificates or hostname | Regenerate certificates with correct hostnames |
| UI not accessible | HTTPS redirect or token missing | Use https://ip:8501 and provide valid ACL token |
| Service discovery fails | Service not registered or unhealthy | consul catalog services and check health status |
Next steps
- Integrate Vault for secrets management with Consul storage backend
- Set up Consul Connect service mesh for secure service-to-service communication
- Configure Consul Template for dynamic configuration management
- Integrate Consul with Kubernetes for hybrid cloud service discovery
- Set up automated backup and disaster recovery for Consul clusters
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Configuration variables
CONSUL_VERSION="1.17.1"
DATACENTER="dc1"
NODE_NAME="consul-server-1"
BOOTSTRAP_EXPECT=3
BIND_ADDR=""
RETRY_JOIN=""
# Usage message
usage() {
echo "Usage: $0 -b <bind_address> [-j <retry_join_ips>] [-n <node_name>] [-c <bootstrap_expect>]"
echo " -b: Bind address for this consul server (required)"
echo " -j: Comma-separated list of retry join IPs (optional)"
echo " -n: Node name (default: consul-server-1)"
echo " -c: Bootstrap expect count (default: 3)"
echo "Example: $0 -b 192.168.1.10 -j '192.168.1.10,192.168.1.11,192.168.1.12'"
exit 1
}
# Parse arguments
while getopts "b:j:n:c:h" opt; do
case $opt in
b) BIND_ADDR="$OPTARG" ;;
j) RETRY_JOIN="$OPTARG" ;;
n) NODE_NAME="$OPTARG" ;;
c) BOOTSTRAP_EXPECT="$OPTARG" ;;
h) usage ;;
*) usage ;;
esac
done
if [ -z "$BIND_ADDR" ]; then
echo -e "${RED}Error: Bind address is required${NC}"
usage
fi
# Check if running as root
if [ "$EUID" -ne 0 ]; then
echo -e "${RED}Error: This script must be run as root${NC}"
exit 1
fi
# Auto-detect distro
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update && apt upgrade -y"
PKG_INSTALL="apt install -y"
FIREWALL_CMD="ufw"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
FIREWALL_CMD="firewall-cmd"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum update -y"
PKG_INSTALL="yum install -y"
FIREWALL_CMD="firewall-cmd"
;;
*)
echo -e "${RED}Unsupported distro: $ID${NC}"
exit 1
;;
esac
else
echo -e "${RED}Error: Cannot detect OS distribution${NC}"
exit 1
fi
# Cleanup function
cleanup() {
echo -e "${YELLOW}Cleaning up on error...${NC}"
systemctl stop consul 2>/dev/null || true
systemctl disable consul 2>/dev/null || true
rm -f /etc/systemd/system/consul.service
rm -rf /opt/consul /etc/consul.d
userdel consul 2>/dev/null || true
rm -f /usr/local/bin/consul
echo -e "${GREEN}Cleanup completed${NC}"
}
# Set trap for cleanup on error
trap cleanup ERR
echo -e "${GREEN}[1/10] Updating system packages...${NC}"
$PKG_UPDATE
$PKG_INSTALL wget unzip curl
echo -e "${GREEN}[2/10] Creating Consul system user...${NC}"
if ! id consul &>/dev/null; then
useradd --system --home /etc/consul.d --shell /bin/false consul
fi
mkdir -p /opt/consul /etc/consul.d /opt/consul/data
chown consul:consul /opt/consul /etc/consul.d /opt/consul/data
chmod 750 /opt/consul /etc/consul.d /opt/consul/data
echo -e "${GREEN}[3/10] Downloading and installing Consul binary...${NC}"
cd /tmp
wget -q "https://releases.hashicorp.com/consul/${CONSUL_VERSION}/consul_${CONSUL_VERSION}_linux_amd64.zip"
unzip -q "consul_${CONSUL_VERSION}_linux_amd64.zip"
mv consul /usr/local/bin/
chmod 755 /usr/local/bin/consul
chown root:root /usr/local/bin/consul
echo -e "${GREEN}[4/10] Generating encryption key and certificates...${NC}"
ENCRYPT_KEY=$(/usr/local/bin/consul keygen)
cd /etc/consul.d
sudo -u consul /usr/local/bin/consul tls ca create
sudo -u consul /usr/local/bin/consul tls cert create -server -dc "$DATACENTER"
echo -e "${GREEN}[5/10] Creating Consul configuration file...${NC}"
# Build retry_join array
RETRY_JOIN_CONFIG=""
if [ -n "$RETRY_JOIN" ]; then
RETRY_JOIN_CONFIG="retry_join = ["
IFS=',' read -ra ADDR <<< "$RETRY_JOIN"
for ip in "${ADDR[@]}"; do
RETRY_JOIN_CONFIG="${RETRY_JOIN_CONFIG}\"${ip// /}\", "
done
RETRY_JOIN_CONFIG="${RETRY_JOIN_CONFIG%, }]"
fi
cat > /etc/consul.d/consul.hcl << EOF
datacenter = "$DATACENTER"
data_dir = "/opt/consul/data"
log_level = "INFO"
node_name = "$NODE_NAME"
bind_addr = "$BIND_ADDR"
client_addr = "0.0.0.0"
server = true
bootstrap_expect = $BOOTSTRAP_EXPECT
$RETRY_JOIN_CONFIG
ui_config {
enabled = true
}
connect {
enabled = true
}
ports {
grpc = 8502
https = 8501
}
encrypt = "$ENCRYPT_KEY"
acl = {
enabled = true
default_policy = "deny"
enable_token_persistence = true
}
tls {
defaults {
ca_file = "/etc/consul.d/consul-agent-ca.pem"
cert_file = "/etc/consul.d/$DATACENTER-server-consul-0.pem"
key_file = "/etc/consul.d/$DATACENTER-server-consul-0-key.pem"
verify_incoming = true
verify_outgoing = true
}
internal_rpc {
verify_server_hostname = true
}
}
EOF
echo -e "${GREEN}[6/10] Setting proper file permissions...${NC}"
chown -R consul:consul /etc/consul.d
chmod 640 /etc/consul.d/consul.hcl
chmod 600 /etc/consul.d/*.pem /etc/consul.d/*-key.pem
echo -e "${GREEN}[7/10] Creating systemd service file...${NC}"
cat > /etc/systemd/system/consul.service << EOF
[Unit]
Description=Consul Service Discovery Agent
Documentation=https://www.consul.io/
Requires=network-online.target
After=network-online.target
ConditionFileNotEmpty=/etc/consul.d/consul.hcl
[Service]
Type=notify
User=consul
Group=consul
ExecStart=/usr/local/bin/consul agent -config-dir=/etc/consul.d/
ExecReload=/bin/kill -HUP \$MAINPID
KillMode=process
Restart=on-failure
LimitNOFILE=65536
# Security settings
NoNewPrivileges=yes
PrivateTmp=yes
ProtectHome=yes
ProtectSystem=strict
ReadWritePaths=/opt/consul/data
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable consul
echo -e "${GREEN}[8/10] Configuring firewall rules...${NC}"
if [ "$FIREWALL_CMD" = "ufw" ]; then
if command -v ufw &> /dev/null; then
ufw allow 8300/tcp comment "Consul server RPC"
ufw allow 8301/tcp comment "Consul serf LAN"
ufw allow 8301/udp comment "Consul serf LAN"
ufw allow 8302/tcp comment "Consul serf WAN"
ufw allow 8302/udp comment "Consul serf WAN"
ufw allow 8500/tcp comment "Consul HTTP API"
ufw allow 8501/tcp comment "Consul HTTPS API"
ufw allow 8502/tcp comment "Consul gRPC"
ufw allow 8600/tcp comment "Consul DNS"
ufw allow 8600/udp comment "Consul DNS"
fi
elif [ "$FIREWALL_CMD" = "firewall-cmd" ]; then
if command -v firewall-cmd &> /dev/null && systemctl is-active firewalld &> /dev/null; then
firewall-cmd --permanent --add-port=8300/tcp --add-port=8301/tcp --add-port=8301/udp --add-port=8302/tcp --add-port=8302/udp --add-port=8500/tcp --add-port=8501/tcp --add-port=8502/tcp --add-port=8600/tcp --add-port=8600/udp
firewall-cmd --reload
fi
fi
echo -e "${GREEN}[9/10] Starting Consul service...${NC}"
systemctl start consul
echo -e "${GREEN}[10/10] Verifying installation...${NC}"
sleep 5
# Verification checks
if ! systemctl is-active consul &> /dev/null; then
echo -e "${RED}Error: Consul service is not running${NC}"
exit 1
fi
if ! /usr/local/bin/consul version &> /dev/null; then
echo -e "${RED}Error: Consul binary not working${NC}"
exit 1
fi
# Remove trap since we succeeded
trap - ERR
echo -e "${GREEN}Consul installation completed successfully!${NC}"
echo -e "${YELLOW}Configuration details:${NC}"
echo " - Node name: $NODE_NAME"
echo " - Bind address: $BIND_ADDR"
echo " - Datacenter: $DATACENTER"
echo " - Bootstrap expect: $BOOTSTRAP_EXPECT"
echo " - UI available at: https://$BIND_ADDR:8501"
echo ""
echo -e "${YELLOW}Next steps:${NC}"
echo "1. Bootstrap ACL system: consul acl bootstrap"
echo "2. Configure client agents with the same encrypt key"
echo "3. Join additional servers to the cluster"
echo "4. Configure services for registration"
Review the script before running. Execute with: bash install.sh