HashiCorp Consul Connect Cheat Sheet
Overview
HashiCorp Consul is a multi-networking tool that provides service discovery, service mesh, and network automation capabilities. Consul Connect is its service mesh feature that provides service-to-service connection authorization and encryption using mutual TLS, enabling secure communication between services without modifying application code.
Consul Connect works across multiple platforms including Kubernetes, VMs, and bare metal. It uses sidecar proxies (Envoy by default) to handle mTLS, traffic routing, and observability. Consul also provides a built-in key-value store, health checking, multi-datacenter support, and a web UI for service visualization.
Installation
Linux Binary
# Download and install
wget https://releases.hashicorp.com/consul/1.18.0/consul_1.18.0_linux_amd64.zip
unzip consul_1.18.0_linux_amd64.zip
sudo mv consul /usr/local/bin/
# Verify
consul version
# Start dev agent (development only)
consul agent -dev -client 0.0.0.0
# Web UI: http://localhost:8500
Docker
docker run -d --name consul \
-p 8500:8500 \
-p 8600:8600/udp \
-p 8301:8301 \
-p 8302:8302 \
hashicorp/consul:1.18 agent -server -bootstrap-expect=1 \
-ui -client=0.0.0.0
Kubernetes (Helm)
helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update
helm install consul hashicorp/consul \
--namespace consul --create-namespace \
--values consul-values.yaml
# consul-values.yaml
global:
name: consul
datacenter: dc1
tls:
enabled: true
acls:
manageSystemACLs: true
server:
replicas: 3
storageClass: standard
connectInject:
enabled: true
default: false
meshGateway:
enabled: true
ui:
enabled: true
service:
type: LoadBalancer
Service Registration
Service Definition File
{
"service": {
"name": "web",
"port": 8080,
"tags": ["v1", "production"],
"meta": {
"version": "1.0.0"
},
"check": {
"http": "http://localhost:8080/health",
"interval": "10s",
"timeout": "5s"
},
"connect": {
"sidecar_service": {
"proxy": {
"upstreams": [
{
"destination_name": "api",
"local_bind_port": 9191
},
{
"destination_name": "database",
"local_bind_port": 9192
}
]
}
}
}
}
}
# Register service
consul services register web-service.json
# Deregister
consul services deregister -id=web
# List services
consul catalog services
# Query service health
consul catalog nodes -service=web
Kubernetes Service Registration
apiVersion: v1
kind: Pod
metadata:
name: web
annotations:
consul.hashicorp.com/connect-inject: "true"
consul.hashicorp.com/connect-service-upstreams: "api:9191,database:9192"
spec:
containers:
- name: web
image: myapp/web:v1
ports:
- containerPort: 8080
env:
- name: API_URL
value: "http://localhost:9191"
- name: DB_URL
value: "localhost:9192"
CLI Commands
| Command | Description |
|---|---|
consul members | List cluster members |
consul catalog services | List registered services |
consul catalog nodes | List nodes |
consul services register <file> | Register a service |
consul services deregister -id <id> | Deregister a service |
consul connect proxy -sidecar-for <svc> | Start sidecar proxy |
consul intention list | List all intentions |
consul intention create web api | Allow web to access api |
consul config write <file> | Write config entry |
consul config list -kind <type> | List config entries |
consul kv put <key> <value> | Write KV pair |
consul kv get <key> | Read KV pair |
Intentions (Service Access Control)
# Allow web to access api
consul intention create -allow web api
# Deny web to access database
consul intention create -deny web database
# List intentions
consul intention list
# Delete an intention
consul intention delete web api
# Check if connection is allowed
consul intention check web api
Intention Config Entry (L7)
{
"Kind": "service-intentions",
"Name": "api",
"Sources": [
{
"Name": "web",
"Action": "allow",
"Permissions": [
{
"Action": "allow",
"HTTP": {
"PathPrefix": "/api/v1",
"Methods": ["GET", "POST"]
}
},
{
"Action": "deny",
"HTTP": {
"PathPrefix": "/admin"
}
}
]
},
{
"Name": "*",
"Action": "deny"
}
]
}
consul config write intentions.json
Configuration Entries
Service Router
{
"Kind": "service-router",
"Name": "api",
"Routes": [
{
"Match": {
"HTTP": {
"PathPrefix": "/api/v2"
}
},
"Destination": {
"Service": "api-v2"
}
},
{
"Match": {
"HTTP": {
"Header": [
{ "Name": "x-canary", "Exact": "true" }
]
}
},
"Destination": {
"Service": "api",
"ServiceSubset": "canary"
}
}
]
}
Service Splitter (Traffic Splitting)
{
"Kind": "service-splitter",
"Name": "api",
"Splits": [
{
"Weight": 90,
"ServiceSubset": "stable"
},
{
"Weight": 10,
"ServiceSubset": "canary"
}
]
}
Service Resolver
{
"Kind": "service-resolver",
"Name": "api",
"DefaultSubset": "stable",
"Subsets": {
"stable": {
"Filter": "Service.Meta.version == v1"
},
"canary": {
"Filter": "Service.Meta.version == v2"
}
},
"ConnectTimeout": "15s",
"RequestTimeout": "30s"
}
Configuration
Server Configuration (consul.hcl)
datacenter = "dc1"
data_dir = "/opt/consul/data"
log_level = "INFO"
server = true
bootstrap_expect = 3
bind_addr = "0.0.0.0"
client_addr = "0.0.0.0"
ui_config {
enabled = true
}
connect {
enabled = true
}
ports {
grpc = 8502
grpc_tls = 8503
}
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
}
}
acl {
enabled = true
default_policy = "deny"
enable_token_persistence = true
}
Advanced Usage
Mesh Gateway (Multi-Datacenter)
# Register mesh gateway
consul connect envoy -mesh-gateway -register \
-address '{{ GetInterfaceIP "eth0" }}:8443' \
-wan-address '{{ GetInterfaceIP "eth0" }}:8443'
# Config entry for mesh
consul config write - <<EOF
{
"Kind": "mesh",
"Peering": {
"PeerThroughMeshGateways": true
}
}
EOF
Service Defaults
{
"Kind": "service-defaults",
"Name": "api",
"Protocol": "http",
"MeshGateway": {
"Mode": "local"
},
"UpstreamConfig": {
"Defaults": {
"ConnectTimeoutMs": 5000,
"Limits": {
"MaxConnections": 512,
"MaxPendingRequests": 512,
"MaxConcurrentRequests": 512
}
}
}
}
Monitoring
# Health check of all services
curl http://localhost:8500/v1/health/state/critical
# Service health
curl http://localhost:8500/v1/health/service/api?passing
# Envoy proxy admin (per sidecar)
# http://localhost:19000/stats
# http://localhost:19000/clusters
# http://localhost:19000/config_dump
Troubleshooting
| Issue | Solution |
|---|---|
| Service not discoverable | Check service registration; verify health checks pass |
| Connect proxy not starting | Ensure connect.enabled = true; check Envoy binary availability |
| Intention denied | Review intentions with consul intention list; check ordering |
| mTLS failing | Verify CA certificates; check TLS configuration on all agents |
| Upstream connection refused | Verify upstream service is healthy; check local bind port |
| Config entry not applied | Verify Kind and Name; check protocol matches (HTTP vs TCP) |
| ACL permission denied | Bootstrap ACLs; create proper tokens for services |
| Split not taking effect | Ensure service-resolver defines subsets; check service metadata |