Skip to content

Last updated: 2026-06-25

HashiCorp Consul TLS/SSL Configuration Guide

This guide provides recommended TLS/SSL settings for HashiCorp Consul, a service networking and service discovery tool. A Consul datacenter is a clustered control plane: agents constantly exchange catalog data, health checks, and configuration over the network. TLS protects three distinct surfaces, the internal RPC traffic between agents, the HTTPS API, and the gRPC interface, so all of them must be secured together.

Prerequisites

  • HashiCorp Consul 1.12 or later for the unified tls stanza (1.21 LTS or the 2.0 series recommended)
  • A Consul CA and an agent certificate per node (use consul tls ca create and consul tls cert create)
  • Consul is built with Go's crypto/tls, which provides strong TLS defaults

Certificate Setup

Every agent needs a certificate whose SAN covers the node's hostname and IP addresses, plus the CA that signed it. Consul's built-in CLI generates a compatible PKI:

consul tls ca create
consul tls cert create -server -dc dc1

This produces consul-agent-ca.pem, a per-node dc1-server-consul-0.pem, and its key. Restrict the key:

chmod 640 /etc/consul.d/certs/dc1-server-consul-0-key.pem
chown consul:consul /etc/consul.d/certs/dc1-server-consul-0-key.pem

TLS Stanza

Since Consul 1.12 all TLS settings live in the tls stanza of the agent configuration file (/etc/consul.d/consul.hcl). The defaults block applies to every interface; the older top-level ca_file, cert_file, verify_incoming, and related keys are deprecated and emit a warning.

Default TLS Setup

tls {
    defaults {
        ca_file   = "/etc/consul.d/certs/consul-agent-ca.pem"
        cert_file = "/etc/consul.d/certs/dc1-server-consul-0.pem"
        key_file  = "/etc/consul.d/certs/dc1-server-consul-0-key.pem"

        verify_incoming        = true
        verify_outgoing        = true
        verify_server_hostname = true
    }
}

Key settings:

  • verify_outgoing = true: agents validate the certificate of any server they connect to. This is the baseline; without it, outgoing connections accept any certificate.
  • verify_incoming = true: agents require the connecting party to present a certificate signed by ca_file. This is mutual TLS (see Security Notes). Enabling it on the gRPC interface can break Consul service mesh sidecars, so it is often scoped to internal_rpc and https only.
  • verify_server_hostname = true: agents check that the server certificate's SAN matches the expected server.<dc>.consul name. This prevents a node with any valid datacenter certificate from impersonating a server. Always enable it.

Minimum TLS Version

tls {
    defaults {
        ca_file   = "/etc/consul.d/certs/consul-agent-ca.pem"
        cert_file = "/etc/consul.d/certs/dc1-server-consul-0.pem"
        key_file  = "/etc/consul.d/certs/dc1-server-consul-0-key.pem"

        verify_incoming        = true
        verify_outgoing        = true
        verify_server_hostname = true

        tls_min_version = "TLSv1_2"
    }
}

Available values: TLSv1_0, TLSv1_1, TLSv1_2 (the default since 1.12), TLSv1_3. Do not set a value below TLSv1_2.

Cipher Suites

Consul uses Go's cipher suite names. Configure the allowed list for TLS 1.2 as a comma-separated string (the TLS 1.3 suites are fixed by Go and are always secure, so they are not configurable):

tls {
    defaults {
        ca_file   = "/etc/consul.d/certs/consul-agent-ca.pem"
        cert_file = "/etc/consul.d/certs/dc1-server-consul-0.pem"
        key_file  = "/etc/consul.d/certs/dc1-server-consul-0-key.pem"

        verify_incoming        = true
        verify_outgoing        = true
        verify_server_hostname = true

        tls_min_version = "TLSv1_2"

        tls_cipher_suites = "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"
    }
}

Per-Interface Overrides

The defaults block can be overridden per interface with the internal_rpc, https, and grpc sub-stanzas. Each accepts the same parameters as defaults, except grpc, which does not support verify_outgoing. A common pattern enforces mutual TLS on the internal RPC and HTTPS surfaces while relaxing it for gRPC so service mesh sidecars continue to work:

tls {
    defaults {
        ca_file   = "/etc/consul.d/certs/consul-agent-ca.pem"
        cert_file = "/etc/consul.d/certs/dc1-server-consul-0.pem"
        key_file  = "/etc/consul.d/certs/dc1-server-consul-0-key.pem"

        verify_outgoing        = true
        verify_server_hostname = true

        tls_min_version = "TLSv1_2"
    }

    internal_rpc {
        verify_incoming = true
    }

    https {
        verify_incoming = true
    }

    grpc {
        verify_incoming = false
    }
}

HTTPS API and Disabling Plaintext

By default Consul exposes the HTTP API on port 8500 in the clear. Disable it and enable the HTTPS API on 8501 in the ports stanza:

ports {
    http  = -1
    https = 8501
    grpc_tls = 8503
}

Setting http = -1 turns the plaintext listener off entirely. After this change every API client must connect over HTTPS.

Gossip Encryption

TLS does not cover the gossip (Serf) protocol that agents use for membership and failure detection. Gossip is protected separately by a symmetric key shared across the datacenter, configured with the encrypt parameter. This is independent of TLS and should be enabled alongside it. Generate a key once:

consul keygen

Then set the same value on every agent:

encrypt = "aPuGh+5UDskRAbkLaXRzFXqDLuDmYWXgLDDgcAPQVdg="

Automatic Certificate Distribution

Managing a certificate per client agent is tedious. Consul can distribute client certificates automatically from the servers using auto_encrypt. Enable it on the servers so they sign and hand out client certificates:

auto_encrypt {
    allow_tls = true
}

Client agents then request a certificate at startup and only need the CA file locally:

tls {
    defaults {
        ca_file = "/etc/consul.d/certs/consul-agent-ca.pem"

        verify_outgoing        = true
        verify_server_hostname = true
    }
}

auto_encrypt {
    tls = true
}

With auto_encrypt.tls = true the client obtains its cert_file and key_file from the servers, so they are omitted from the client configuration. The newer auto_config mechanism extends this to distribute the gossip key and ACL token as well, using a JWT to authorize the initial request.

Complete Configuration

A representative server configuration with TLS on all three interfaces, gossip encryption, and HTTPS-only API:

datacenter = "dc1"
data_dir   = "/opt/consul"
server     = true

bind_addr   = "[::]"
client_addr = "[::]"

encrypt = "aPuGh+5UDskRAbkLaXRzFXqDLuDmYWXgLDDgcAPQVdg="

ports {
    http  = -1
    https = 8501
    grpc_tls = 8503
}

tls {
    defaults {
        ca_file   = "/etc/consul.d/certs/consul-agent-ca.pem"
        cert_file = "/etc/consul.d/certs/dc1-server-consul-0.pem"
        key_file  = "/etc/consul.d/certs/dc1-server-consul-0-key.pem"

        verify_outgoing        = true
        verify_server_hostname = true

        tls_min_version = "TLSv1_2"

        tls_cipher_suites = "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"
    }

    internal_rpc {
        verify_incoming = true
    }

    https {
        verify_incoming = true
    }
}

auto_encrypt {
    allow_tls = true
}

Client Configuration

Environment Variables

The CLI and API clients read these variables for HTTPS and mutual TLS:

export CONSUL_HTTP_ADDR="https://consul.example.com:8501"
export CONSUL_CACERT="/etc/consul.d/certs/consul-agent-ca.pem"

For mutual TLS against an HTTPS interface with verify_incoming = true:

export CONSUL_CLIENT_CERT="/etc/consul.d/certs/client.pem"
export CONSUL_CLIENT_KEY="/etc/consul.d/certs/client-key.pem"

CLI Usage

consul members -ca-file=/etc/consul.d/certs/consul-agent-ca.pem -http-addr=https://consul.example.com:8501

Skip Verification (Development Only)

export CONSUL_HTTP_SSL_VERIFY=false

Never disable verification in production. This accepts any certificate and defeats the purpose of TLS.

Security Notes

Consul uses Go's crypto/tls package, which has a different vulnerability history than OpenSSL:

  • POODLE (CVE-2014-3566, 2014): SSL 3.0 has never been supported in Go's TLS implementation.
  • BEAST (CVE-2011-3389, 2011): Mitigated by recommending TLS 1.2 as the minimum; AEAD-only ciphers eliminate the CBC padding oracle.
  • CRIME (CVE-2012-4929, 2012): TLS compression is not supported in Go's crypto/tls.
  • Lucky13 (2013): AEAD-only cipher list eliminates CBC padding timing side-channels entirely.
  • FREAK (CVE-2015-0204, 2015): EXPORT-grade ciphers have never been supported in Go's crypto/tls.
  • LOGJAM (CVE-2015-4000, 2015): DHE key exchange is not offered by default in Go; only ECDHE is used.
  • Sweet32 (CVE-2016-2183, 2016): 3DES was removed from Go's default cipher list in Go 1.23 (August 2024) and is excluded from the recommended configuration.
  • ROBOT (2017): Static RSA key exchange is not offered in Go's crypto/tls; only ECDHE is used.
  • Downgrade attacks: Go's crypto/tls does not perform insecure version-fallback retries, and TLS 1.3 has built-in downgrade protection.

The following are not addressable through TLS configuration alone:

  • Heartbleed (CVE-2014-0160, 2014): Not applicable. Go's crypto/tls is an independent TLS implementation and was never affected by Heartbleed.
  • BREACH (CVE-2013-3587, 2013): Consul exposes a REST API over HTTPS. If HTTP response compression is enabled by a reverse proxy in front of the API, apply BREACH countermeasures at that layer; TLS configuration cannot prevent it.
  • DROWN (CVE-2016-0800, 2016): Not applicable. Go's crypto/tls does not support SSLv2.

Gossip encryption is symmetric and separate from TLS. A leaked gossip key lets an attacker join the serf pool even when every TLS surface is locked down, so rotate it with consul keygen and the keyring commands, and protect it like any other secret.

Verification

Start or restart Consul:

systemctl restart consul

Confirm the members list over the encrypted gossip pool:

consul members

Test the TLS handshake on the HTTPS API and the internal RPC port:

openssl s_client -connect consul.example.com:8501 -CAfile /etc/consul.d/certs/consul-agent-ca.pem
openssl s_client -connect consul.example.com:8300 -CAfile /etc/consul.d/certs/consul-agent-ca.pem

Port 8501 is the HTTPS API; 8300 is the server RPC port. Both should complete a valid handshake once TLS is enabled.

Query the catalog over HTTPS with a client certificate to confirm mutual TLS:

curl --cacert /etc/consul.d/certs/consul-agent-ca.pem \
     --cert /etc/consul.d/certs/client.pem \
     --key /etc/consul.d/certs/client-key.pem \
     https://consul.example.com:8501/v1/agent/members

Related Guides

View all Infrastructure guides →

Configured TLS? Now Monitor It.

Generator Labs alerts you before certificates expire, get revoked, or fail chain validation, across HTTPS, SMTPS, IMAPS, LDAPS, and more.

Certificate Monitoring →