Skip to main content

Downstream mutual TLS (mTLS) with Pomerium

Downstream mutual TLS (mTLS) refers to a requirement that end users must present a trusted client certificate when connecting to services secured by Pomerium.

With ordinary TLS, only the server presents a certificate. This allows the client to verify the identity of the server before proceeding with the connection, ensuring that the connection between the client and server is not only private (encrypted) but also authenticated.

With mTLS, the client must also present a certificate. The server will allow requests only when the client presents a certificate that it recognizes as trusted. This capability can be used to provide an additional layer of security.

note

Pomerium uses the term "downstream mTLS" when referring to the connection between end users and Pomerium, and "upstream mTLS" when referring to the connection between Pomerium and the services protected by Pomerium. (See Upstream mTLS for more information on the latter.)

Enabling downstream mTLS in Pomerium requires all clients to authenticate themselves by providing a trusted client certificate during the initial connection. Only after Pomerium successfully verifies the client certificate will it permit access to the configured routes.

This guide shows you how to configure Pomerium to enable mTLS using client certificates issued by a private certificate authority.

Before You Begin

To complete this guide, you will need:

  • A working Pomerium instance. Complete the Pomerium Core quickstart with Docker for a quick proof of concept to test with this guide.
  • mkcert to issue certificates from a locally-trusted certificate authority (CA)
caution

The mkcert tool is designed for testing: It creates a locally-trusted root certificate for development purposes. This guide uses mkcert for a proof-of-concept example, but a production deployment will require a more sophisticated certificate management solution.

Configure Pomerium with a server certificate

Note

If your Pomerium instance already has a server certificate configured, you can skip to the Create a client certificate step.

This guide uses the domain localhost.pomerium.io as Pomerium's root domain (all subdomains under localhost.pomerium.io resolve to localhost).

Create a root CA

If you haven’t yet, install mkcert following these instructions.

Create a trusted root CA:

mkcert -install

Create a wildcard TLS certificate

Run the following command to create a wildcard server certificate for *.localhost.pomerium.io:

mkcert '*.localhost.pomerium.io'

This creates two files in the current working directory:

  • _wildcard.localhost.pomerium.io.pem
  • _wildcard.localhost.pomerium.io-key.pem

_wildcard.localhost.pomerium.io.pem is the certificate, which contains a public key bound to the DNS name *.localhost.pomerium.io.

_wildcard.localhost.pomerium.io-key.pem is the corresponding private key.

Update Pomerium configuration

Update the config.yaml file or environment variables with your wildcard certificate. If running Pomerium in Docker, you will need to bind mount these files or copy them into the container and update the file paths accordingly.

certificate_file: '_wildcard.localhost.pomerium.io.pem'
certificate_key_file: '_wildcard.localhost.pomerium.io-key.pem'

Create a client certificate

If you haven’t yet, install mkcert following these instructions.

Then, to create a client certificate, run the following command:

mkcert -client -pkcs12 'yourUsername@localhost.pomerium.io'

This creates a new file in the current working directory, containing both the client certificate and the corresponding private key:

  • yourUsername@localhost.pomerium.io-client.p12

(Note that the root CA created by mkcert does not need to be installed into the system trust store in order to be used as a trusted CA by Pomerium.)

Configure Pomerium to require mTLS

Update the config.yaml file or environment variables to trust only certificates issued by your mkcert root CA. To find the path to the root CA certificate created by mkcert, run the following command:

echo "$(mkcert -CAROOT)/rootCA.pem"

If running Pomerium in Docker, you will need to bind mount this file or copy it into the container (and update the file path accordingly).

downstream_mtls:
ca_file: '/YOUR/MKCERT/CAROOT/rootCA.pem'

(See the Downstream mTLS Settings reference page for more details about the available mTLS settings.)

Your Pomerium instance should now require a client certificate in order to access any configured routes. If you attempt to access any route from your browser, you should now see a Pomerium error page.

Install your client certificate

Now you'll need to install the client certificate you created earlier. The following instructions are for Chrome on Linux, but client certificates are supported in all major browsers.

  1. Go to chrome://settings/certificates:

    chrome settings

  2. Click on Import and browse to the directory where you created the certificates above. Choose _wildcard.localhost.pomerium.io-client.p12:

    import client certificate

  3. You will be prompted for the certificate password. The default password set by mkcert is changeit:

    enter certificate password

  4. The org-mkcert development certificate should now be in your list of certificates:

    certificate list

Using the client certificate

Visit https://verify.localhost.pomerium.io (or another route you've defined). You should be prompted to choose a client certificate:

choose client certificate

After selecting this certificate, Pomerium should now allow you to access this route.