In a microservices environment, services need a reliable way to discover each other without hardcoded IP addresses. HashiCorp Consul solves this with a distributed key-value store, a DNS interface, and an HTTP API that services query to locate healthy peers. On RHEL 9, Consul runs as a systemd service and integrates cleanly with firewalld. This tutorial covers installing Consul in single-server mode, registering services, running DNS-based discovery, and setting up basic health checks.
Prerequisites
- RHEL 9 server with root or sudo access
- Outbound internet access to download the Consul binary
unzipinstalled:dnf install -y unzip- A static IP address on the server (this tutorial uses
192.168.1.10) - Firewalld running
Step 1 — Download and Install Consul
HashiCorp distributes Consul as a single static binary. Download the latest release from the official releases page, verify it, and place it in /usr/local/bin:
# Set the version you want to install
CONSUL_VERSION="1.18.2"
# Download the binary and SHA256 checksum file
curl -fsSLO "https://releases.hashicorp.com/consul/${CONSUL_VERSION}/consul_${CONSUL_VERSION}_linux_amd64.zip"
curl -fsSLO "https://releases.hashicorp.com/consul/${CONSUL_VERSION}/consul_${CONSUL_VERSION}_SHA256SUMS"
# Verify the checksum
sha256sum --check --ignore-missing "consul_${CONSUL_VERSION}_SHA256SUMS"
# Extract and install
unzip "consul_${CONSUL_VERSION}_linux_amd64.zip"
mv consul /usr/local/bin/
chmod +x /usr/local/bin/consul
# Confirm installation
consul version
Step 2 — Create the Consul User and Directory Structure
# Create a dedicated system user (no login shell)
useradd --system --home /etc/consul.d --shell /bin/false consul
# Create configuration and data directories
mkdir -p /etc/consul.d /opt/consul/data
chown -R consul:consul /etc/consul.d /opt/consul
Step 3 — Configure the Consul Server
Create the main configuration file at /etc/consul.d/server.hcl. In production you would set bootstrap_expect to match your cluster size (3 or 5 nodes); for a single-node lab, set it to 1:
cat > /etc/consul.d/server.hcl << 'EOF'
datacenter = "dc1"
data_dir = "/opt/consul/data"
log_level = "INFO"
node_name = "consul-server-01"
# Bind to the private IP; client_addr 0.0.0.0 exposes the HTTP API
bind_addr = "192.168.1.10"
client_addr = "0.0.0.0"
# Single-server mode
server = true
bootstrap_expect = 1
# Enable the web UI
ui_config {
enabled = true
}
# Enable script health checks (optional, disable for security in production)
enable_script_checks = false
# ACL default allow for development; switch to "deny" in production
acl {
enabled = false
default_policy = "allow"
}
EOF
# Set correct ownership
chown consul:consul /etc/consul.d/server.hcl
chmod 640 /etc/consul.d/server.hcl
Step 4 — Create the Systemd Service
cat > /etc/systemd/system/consul.service << 'EOF'
[Unit]
Description=HashiCorp Consul - Service Discovery and Configuration
Documentation=https://www.consul.io/
Requires=network-online.target
After=network-online.target
ConditionFileNotEmpty=/etc/consul.d/server.hcl
[Service]
Type=notify
User=consul
Group=consul
ExecStart=/usr/local/bin/consul agent -config-dir=/etc/consul.d/
ExecReload=/bin/kill --signal HUP $MAINPID
KillMode=process
KillSignal=SIGTERM
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now consul
# Check the service is running
systemctl status consul
consul members
Step 5 — Open Firewall Ports
# HTTP API and Web UI
firewall-cmd --permanent --add-port=8500/tcp
# DNS interface
firewall-cmd --permanent --add-port=8600/tcp
firewall-cmd --permanent --add-port=8600/udp
# Server RPC
firewall-cmd --permanent --add-port=8300/tcp
# LAN serf (gossip)
firewall-cmd --permanent --add-port=8301/tcp
firewall-cmd --permanent --add-port=8301/udp
# WAN serf (multi-datacenter)
firewall-cmd --permanent --add-port=8302/tcp
firewall-cmd --permanent --add-port=8302/udp
firewall-cmd --reload
You can now access the Consul web UI at http://192.168.1.10:8500.
Step 6 — Register a Service and Use DNS-Based Discovery
Define a service by placing a JSON file in /etc/consul.d/. Consul watches this directory and reloads automatically (or on consul reload):
# Register a web service with a TCP health check
cat > /etc/consul.d/web.json << 'EOF'
{
"service": {
"name": "web",
"tags": ["nginx", "frontend"],
"port": 80,
"check": {
"id": "web-tcp",
"name": "Web TCP check",
"tcp": "localhost:80",
"interval": "10s",
"timeout": "3s"
}
}
}
EOF
chown consul:consul /etc/consul.d/web.json
# Reload Consul to pick up the new service definition
consul reload
# Query the service via the HTTP API
curl http://127.0.0.1:8500/v1/catalog/service/web | python3 -m json.tool
# Query via DNS — Consul answers on port 8600
# Format: .service..consul
dig @127.0.0.1 -p 8600 web.service.dc1.consul
# Short form (uses default datacenter)
dig @127.0.0.1 -p 8600 web.service.consul
# SRV record returns port information as well
dig @127.0.0.1 -p 8600 web.service.consul SRV
# List all registered services
consul catalog services
Conclusion
You now have a running Consul server on RHEL 9 that provides DNS-based service discovery, a web UI, and an HTTP API. Applications can resolve service-name.service.consul directly using the DNS interface, or query the REST API for richer metadata such as tags and health status. For production, deploy three or five Consul nodes to achieve high availability and enable ACL tokens to restrict access to the API.
Next steps: How to Set Up a Multi-Node Consul Cluster on RHEL 9, How to Enable Consul ACLs and Gossip Encryption on RHEL 9, and How to Integrate Consul with HAProxy for Automatic Backend Updates on RHEL 9.