---
layout: docs
page_title: Generate mTLS Certificates for Consul with Vault and Consul Template
description: >-
  Use Vault and Consul Template to create and configure Vault-managed mTLS certificates for Consul's API and RPC traffic.
---

# Generate mTLS Certificates for Consul with Vault and Consul Template

This page describes the process to use Vault's [PKI Secrets Engine](/vault/docs/secrets/pki) to generate and renew dynamic X.509 certificates, using [Consul Template](/consul/docs/automate/consul-template) to rotate your certificates.

This method enables each agent in the Consul datacenter to have a unique certificate, with a relatively short time-to-live (TTL), that is automatically rotated, which allows you to safely and securely scale your datacenter while using mutual TLS (mTLS).

## Prerequisites

- **Consul:** to complete this guide, you need at least a node to install a Consul server agent and ideally another node to install a Consul client agent. Follow [Deploy Consul on VMs](/consul/tutorials/get-started-vms/virtual-machine-gs-deploy) to learn how to deploy a Consul agent. This page will provide you with the necessary specific configuration to apply for the scenario.

- **Vault:** this guide assumes you have a running Vault cluster in your network. You can use a [local Vault dev server](/vault/tutorials/get-started/setup#set-up-the-lab) or an existing Vault deployment.

- **Consul Template:** to interact with your Consul agent you will need to [install the `consul-template` binary](/consul/docs/automate/consul-template/install) on a node. To rotate gossip keys you need the binary to be installed on one node only; changes will be automatically propagated across the Consul datacenter.

The diagram below shows the minimal architecture needed to demonstrate the functionality.

![Architectural diagram showing a Client server and a Vault server with an operator issuing a command to start an automation](/img/consul-template/consul-vault-tls.png)

## Configure Vault's PKI secrets engine

Before you can initialize the secrets engine, you need to set the `VAULT_ADDR` and `VAULT_TOKEN` environment variables so that you can connect to your local instance of Vault.

The `VAULT_ADDR` environment variable should be set to the target Vault server address you want to connect to.

```shell-session
$ export VAULT_ADDR='http://127.0.0.1:8200'
```

The `VAULT_TOKEN` environment variable should store your client token (e.g. `root`).

```shell-session
$ export VAULT_TOKEN="root"
```

Enable Vault's PKI secrets engine at the `pki` path.

```shell-session
$ vault secrets enable pki

Success! Enabled the pki secrets engine at: pki/
```

Tune the PKI secrets engine to issue certificates with a maximum time-to-live (TTL) of `87600` hours.

```shell-session
$ vault secrets tune -max-lease-ttl=87600h pki

Success! Tuned the secrets engine at: pki/
```

<Note>

This tutorial uses a common and recommended pattern which is to have one mount act as the root CA and to use this CA only to sign intermediate CA CSRs from other PKI secrets engines (which you will create in the next few steps). For tighter security, you can store your CA outside of Vault and use the PKI engine only as an intermediate CA.

</Note>

## Configure Vault as Consul's CA

Consul requires that all servers and clients have key pairs that are generated by a single Certificate Authority (CA).

You will use Vault's PKI secrets engine to generate the necessary CA and certificates.

#### 1. Generate the root CA

Generate the root certificate and save the certificate in `CA_cert.crt`.

```shell-session
$ vault write -field=certificate pki/root/generate/internal \
        common_name="dc1.consul" \
        ttl=87600h > CA_cert.crt
```

This generates a new self-signed CA certificate and private key. Vault will automatically revoke the generated root at the end of its lease period (TTL); the CA certificate will sign its own Certificate Revocation List (CRL).

<Tip title="Tuning configuration">

You can adapt the TTL to comply with your internal policies on certificate lifecycle.

</Tip>

You can inspect the certificate created using `openssl x509 -text -noout -in CA_cert.crt`

Configure the CA and CRL URLs.

```shell-session
$ vault write pki/config/urls \
        issuing_certificates="http://127.0.0.1:8200/v1/pki/ca" \
        crl_distribution_points="http://127.0.0.1:8200/v1/pki/crl"
```

Example output:

<CodeBlockConfig hideClipboard>

```plaintext
Success! Data written to: pki/config/urls
```

</CodeBlockConfig>

#### 2. Generate an intermediate CA

Enable the PKI secrets engine at the `pki_int` path.

```shell-session
$ vault secrets enable -path=pki_int pki

Success! Enabled the pki secrets engine at: pki_int/
```

Tune the `pki_int` secrets engine to issue certificates with a maximum time-to-live (TTL) of `43800` hours.

```shell-session
$ vault secrets tune -max-lease-ttl=43800h pki_int

Success! Tuned the secrets engine at: pki_int/
```

<Tip title="Tuning configuration">

You can adapt the TTL to comply with your internal policies on certificate lifecycle.

</Tip>

Request an intermediate certificate signing request (CSR) and save request as `pki_intermediate.csr`.

```shell-session
$ vault write -format=json pki_int/intermediate/generate/internal \
        common_name="dc1.consul Intermediate Authority" \
        | jq -r '.data.csr' > pki_intermediate.csr
```

The command has no output.

#### 3. Sign the CSR and import the certificate into Vault

```shell-session
$ vault write -format=json pki/root/sign-intermediate csr=@pki_intermediate.csr \
        format=pem_bundle ttl="43800h" \
        | jq -r '.data.certificate' > intermediate.cert.pem
```

The command has no output.

Once the CSR is signed, and the root CA returns a certificate, it can be imported back into Vault.

```shell-session
$ vault write pki_int/intermediate/set-signed certificate=@intermediate.cert.pem

Success! Data written to: pki_int/intermediate/set-signed
```

#### 4. Create a Vault role

A role is a logical name that maps to a policy used to generate credentials.

```shell-session
$ vault write pki_int/roles/consul-dc1 \
  allowed_domains="dc1.consul" \
  allow_subdomains=true \
  generate_lease=true \
  max_ttl="720h"
```

Example output:

<CodeBlockConfig hideClipboard>

```plaintext
Success! Data written to: pki_int/roles/consul-dc1
```

</CodeBlockConfig>

For this guide, you are using the following options for the role:

- `allowed_domains`: Specifies the domains of the role. The command uses `dc1.consul` as the domain, which is the default configuration you are going to use for Consul.
- `allow_subdomains`: Specifies if clients can request certificates with CNs that are subdomains of the CNs allowed by the other role options

  <Note>

  This includes wildcard subdomains.

  </Note>

- `generate_lease`: Specifies if certificates issued/signed against this role will have Vault leases attached to them. Certificates can be added to the CRL by Vault revoke `<lease_id>` when certificates are associated with leases.

This completes the Vault configuration as a CA.

## Generate a server certificate

You can test the `pki` engine is configured correctly by generating your first certificate.

```shell-session
$ vault write pki_int/issue/consul-dc1 \
  common_name="server.dc1.consul" \
  ttl="24h" | tee certs.txt
```

<Note title="Certificate TTL tuning">

The TTL for the certificate is being set to 24 hours in this guide, meaning that this certificate will be valid only for 24 hours before expiring. You can try using a shorter TTL on a test environment to ensure certificates are revoked properly after TTL is expired.

</Note>

Example output:

<CodeBlockConfig hideClipboard>

```plaintext
Key                 Value
---                 -----
lease_id            pki_int/issue/consul-dc1/lFfKfpxtM0xY0AHDlr9pJ2GM
lease_duration      23h59m59s
lease_renewable     false
ca_chain            [-----BEGIN CERTIFICATE-----
##...
-----END CERTIFICATE-----]
certificate         -----BEGIN CERTIFICATE-----
##...
-----END CERTIFICATE-----
expiration          1599645187
issuing_ca          -----BEGIN CERTIFICATE-----
##...
-----END CERTIFICATE-----
private_key         -----BEGIN RSA PRIVATE KEY-----
##...
-----END RSA PRIVATE KEY-----
private_key_type    rsa
serial_number       3f:ec:bd:ea:01:a6:35:49:a7:6d:17:ba:13:88:c1:b8:35:b4:fc:4c
```

</CodeBlockConfig>

## Configure Consul

<Tabs>
<Tab heading="Server">

Configure Consul TLS using the following configuration:

<CodeTabs>

```hcl
tls {
  defaults {
    verify_incoming = true
    verify_outgoing = true
    verify_server_hostname = true
    ca_file = "/opt/consul/agent-certs/ca.crt"
    cert_file = "/opt/consul/agent-certs/agent.crt"
    key_file = "/opt/consul/agent-certs/agent.key"
  }
}

auto_encrypt {
  allow_tls = true
}
```

```json
{
  "tls": {
    "defaults": {
      "verify_incoming": true,
      "verify_outgoing": true,
      "verify_server_hostname": true,
      "ca_file": "/opt/consul/agent-certs/ca.crt",
      "cert_file": "/opt/consul/agent-certs/agent.crt",
      "key_file": "/opt/consul/agent-certs/agent.key"
    }
  },
  "auto_encrypt": {
      "allow_tls": true
    }
}
```

</CodeTabs>


To configure TLS encryption for Consul servers, three files are required:

- `ca_file` - CA (or intermediate) certificate to verify the identity of the other nodes.
- `cert_file` - Consul agent public certificate
- `key_file` - Consul agent private key

For the first Consul startup, you will use the certificate generated earlier.

Use the following commands to extract the two certificates and private key from the `certs.txt` and place them into the right file and location.

Create the certificates folder.

```shell-session
$ mkdir -p /opt/consul/agent-certs
```

Extract the root CA certificate.

```shell-session
$ grep -Pzo "(?s)(?<=issuing_ca)[^\-]*.*?END CERTIFICATE[^\n]*\n" certs.txt | sed 's/^\s*-/-/g' > /opt/consul/agent-certs/ca.crt
```

Extract the agent certificate.

```shell-session
$ grep -Pzo "(?s)(?<=certificate)[^\-]*.*?END CERTIFICATE[^\n]*\n" certs.txt | sed 's/^\s*-/-/g' > /opt/consul/agent-certs/agent.crt
```

Extract the agent key.

```shell-session
$ grep -Pzo "(?s)(?<=private_key)[^\-]*.*?END RSA PRIVATE KEY[^\n]*\n" certs.txt | sed 's/^\s*-/-/g' > /opt/consul/agent-certs/agent.key
```

</Tab>
<Tab heading="Client">

<Note>

This section describes the automated [client certificate deployment process](/consul/docs/reference/agent/configuration-file/encryption#auto_encrypt) available in Consul 1.5.2 and newer.

</Note>

With auto-encryption, you can configure the Consul servers to automatically distribute certificates to the clients. To use this feature, you will need to configure clients to automatically get the certificates from the server.

Configure Consul client TLS using the following configuration:

<CodeTabs>

```hcl
tls {
  defaults {
    verify_incoming = true
    verify_outgoing = true
    verify_server_hostname = true
    ca_file = "/opt/consul/agent-certs/ca.crt"
  }
}

auto_encrypt {
  tls = true
}
```

```json
{
  "tls": {
    "defaults": {
      "verify_incoming": true,
      "verify_outgoing": true,
      "verify_server_hostname": true,
      "ca_file": "/opt/consul/agent-certs/ca.crt",
    }
  },
  "auto_encrypt": {
      "tls": true
    }
}
```

</CodeTabs>


To configure TLS encryption for Consul clients only one file is required:

- `ca_file` - CA (or intermediate) certificate to verify the identity of the other nodes.

Use the following commands to extract the certificate from the `certs.txt` and place them into the right file and location.

Create the certificates folder.

```shell-session
$ mkdir -p /opt/consul/agent-certs
```

Extract the root CA certificate.

```shell-session
$ grep -Pzo "(?s)(?<=certificate)[^\-]*.*?END CERTIFICATE[^\n]*\n" certs.txt | sed 's/^\s*-/-/g' > /opt/consul/agent-certs/agent.crt
```

</Tab>
</Tabs>

## Configure Consul Template

The guide steps used `ttl="24h"` ad a parameter during certificate creation, meaning that this certificates will be valid only for 24 hours before expiring.

Deciding the right trade-off for certificate lifespan is always a compromise between security and agility. A possible third way that does not require you to lower your security is to use Consul Template to automate certificate renewal for Consul when the TTL is expired.

### Create template files

You can instruct Consul Template to generate and retrieve those files from Vault using the following templates:

<CodeBlockConfig filename="agent.crt.tpl (server only)">

```go
{{ with secret "pki_int/issue/consul-dc1" "common_name=server.dc1.consul" "ttl=24h" "alt_names=localhost" "ip_sans=127.0.0.1"}}
{{ .Data.certificate }}
{{ end }}
```

</CodeBlockConfig>

The template uses the `pki_int/issue/consul-dc1` endpoint that Vault exposes to generate new certificates. It also mentions the common name and alternate names for the certificate.

<CodeBlockConfig filename="agent.key.tpl (server only)">

```go
{{ with secret "pki_int/issue/consul-dc1" "common_name=server.dc1.consul" "ttl=24h" "alt_names=localhost" "ip_sans=127.0.0.1"}}
{{ .Data.private_key }}
{{ end }}
```

</CodeBlockConfig>

The same endpoint also exposes the CA certificate under the `.Data.issuing_ca` parameter.

<CodeBlockConfig filename="ca.crt.tpl">

```go
{{ with secret "pki_int/issue/consul-dc1" "common_name=server.dc1.consul" "ttl=24h"}}
{{ .Data.issuing_ca }}
{{ end }}
```

</CodeBlockConfig>


Copy the newly created files into `/opt/consul/templates`.

```shell-session
$ cp *.tpl /opt/consul/templates/
```

### Create Consul Template configuration

Create a configuration file, `consul_template.hcl`, that will instruct Consul Template to retrieve the files needed for the Consul agents, client and server, to configure TLS encryption.

<Tabs>
<Tab heading="Server">

<CodeBlockConfig filename="consul_template.hcl">

```hcl
# This denotes the start of the configuration section for Vault. All values
# contained in this section pertain to Vault.
vault {
  # This is the address of the Vault leader. The protocol (http(s)) portion
  # of the address is required.
  address      = "http://localhost:8200"

  # This value can also be specified via the environment variable VAULT_TOKEN.
  token        = "root"

  unwrap_token = false

  renew_token  = false
}

# This block defines the configuration for a template. Unlike other blocks,
# this block may be specified multiple times to configure multiple templates.
template {
  # This is the source file on disk to use as the input template. This is often
  # called the "consul-template template".
  source      = "agent.crt.tpl"

  # This is the destination path on disk where the source template will render.
  # If the parent directories do not exist, consul-template will attempt to
  # create them, unless create_dest_dirs is false.
  destination = "/opt/consul/agent-certs/agent.crt"

  # This is the permission to render the file. If this option is left
  # unspecified, consul-template will attempt to match the permissions of the
  # file that already exists at the destination path. If no file exists at that
  # path, the permissions are 0644.
  perms       = 0700

  # This is the optional command to run when the template is rendered. The
  # command will only run if the resulting template changes.
  command     = "sh -c 'date && consul reload'"
}

template {
  source      = "agent.key.tpl"
  destination = "/opt/consul/agent-certs/agent.key"
  perms       = 0700
  command     = "sh -c 'date && consul reload'"
}

template {
  source      = "ca.crt.tpl"
  destination = "/opt/consul/agent-certs/ca.crt"
  command     = "sh -c 'date && consul reload'"
}
```

</CodeBlockConfig>

The configuration file for the server contains the information to retrieve the
CA certificate as well as the certificate/key pair for the server agent.

</Tab>

<Tab heading="Client">

<CodeBlockConfig filename="consul_template.hcl">

```hcl
# This denotes the start of the configuration section for Vault. All values
# contained in this section pertain to Vault.
vault {
  # This is the address of the Vault leader. The protocol (http(s)) portion
  # of the address is required.
  address      = "http://localhost:8200"

  # This value can also be specified via the environment variable VAULT_TOKEN.
  token        = "root"

  unwrap_token = false

  renew_token  = false
}

template {
  source      = "ca.crt.tpl"
  destination = "/opt/consul/agent-certs/ca.crt"
  command     = "sh -c 'date && consul reload'"
}
```

</CodeBlockConfig>

The configuration file for the client contains the information to retrieve the CA certificate only, the certificates for client agents are automatically
generated from Consul when using the `auto_encrypt` setting.

</Tab>
</Tabs>


To allow Consul Template to communicate with Vault, define the following parameters:

- `address` : the address of your Vault server. If Vault runs on the same node as Consul, you can use `http://localhost:8200`.

- `token` : a valid Vault ACL token with appropriate permissions. You can use Vault root token for this example.

<Warning title="Production check">

The use of Vault root token is not recommended for production use; the recommended security approach is to create a new token based on a specific policy with limited privileges.

</Warning>

### Start Consul Template

Start Consul Template using the `-config` parameter to provide the configuration file.

```shell-session
$ consul-template -config "consul_template.hcl"

Configuration reload triggered
```

## Verify certificate rotation

The certificate you created manually for the Consul server had a TTL of 24 hours.

This means that after the certificate expires Vault will renew it and Consul Template will update the files on your agent it reload Consul configuration automatically to make it pick up the new files.

You can verify the rotation by checking that Consul Template keeps listing, every 24 hours, a timestamp and the log line:

```plaintext hideClipboard
Configuration reload triggered
```

You can also use `openssl` to verify the certificate content:

```shell-session
$ openssl x509 -text -noout -in /opt/consul/agent-certs/agent.crt

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            1b:2d:d6:5d:63:9b:aa:05:84:7b:be:3b:6f:e1:95:bb:1c:36:8c:a4
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN=dc1.consul Intermediate Authority
        Validity
            Not Before: Sep 16 16:03:45 2020 GMT
            Not After : Sep 16 16:06:15 2020 GMT
        Subject: CN=server.dc1.consul
...
```

and verify that the `Not Before` and `Not After` values are being updated to reflect the new certificate.
