Certificates and TLS with Pomerium
This page covers downstream TLS (the connection between end users and Pomerium) and upstream TLS (the connection between Pomerium and the services behind it). Both can leverage mutual TLS (mTLS) for additional security in a zero-trust environment.
Downstream TLS
Downstream mTLS refers to a requirement that end users present a trusted client certificate when connecting to services secured by Pomerium. With ordinary TLS, only the server presents a certificate. With mTLS, the client must also present a certificate, and the server will only allow requests if the client's certificate is trusted.
Pomerium uses the term “downstream mTLS” for the client-to-Pomerium connection, and “upstream mTLS” for the connection between Pomerium and protected services. See Upstream TLS below for details on securing the connection to your upstream services.
Why use downstream mTLS?
- Stronger Client Authentication: Each connecting user or device has its own certificate.
- Zero-Trust: Certificates verify user identity at the network level, not just via credentials/SSO.
- Compliance: Many security standards (e.g., PCI-DSS, HIPAA) recommend or require mutual authentication for sensitive data transport.
Before You Begin (Downstream TLS)
You will need:
- A working Pomerium instance. Complete the Pomerium Core quickstart with Docker for a quick proof of concept.
mkcert
, to issue certificates from a locally-trusted certificate authority (CA).
mkcert
is designed for local testing. Production environments require a more advanced certificate management system.
Configure Pomerium with a server certificate
If Pomerium already has a server certificate configured, you can skip to Create a client certificate.
-
Create a Root CA
Installmkcert
, then run:mkcert -install
This creates a trusted root CA for local development.
-
Create a Wildcard TLS Certificate
mkcert '*.localhost.pomerium.io'
This produces
_wildcard.localhost.pomerium.io.pem
(certificate) and_wildcard.localhost.pomerium.io-key.pem
(key). -
Update Pomerium Configuration
Pointcertificate_file
andcertificate_key_file
to these files:certificate_file: '_wildcard.localhost.pomerium.io.pem'
certificate_key_file: '_wildcard.localhost.pomerium.io-key.pem'Make sure the paths align with your environment (e.g., Docker bind mounts).
Create a client certificate
mkcert -client -pkcs12 "yourUsername@localhost.pomerium.io"
This produces a file like yourUsername@localhost.pomerium.io-client.p12
, containing both the client certificate and its private key.
Require mTLS in Pomerium
Add the downstream_mtls
key to your config.yaml
or environment variables, pointing to the rootCA.pem
created by mkcert
:
downstream_mtls:
ca_file: '/YOUR/MKCERT/CAROOT/rootCA.pem'
Pomerium now requires a client certificate for any route. Browsers without a valid client certificate will see a Pomerium error page.
Installing the client certificate (example: Chrome on Linux)
- Navigate to
chrome://settings/certificates
. - Under Your Certificates, click Import and select
yourUsername@localhost.pomerium.io-client.p12
. - Enter the default password
changeit
(from mkcert). - A new “org-mkcert development certificate” entry appears in your certificate list.
When you visit a route like https://verify.localhost.pomerium.io
, Chrome should prompt you to select the newly imported certificate. If everything is correct, you'll be granted access.
Upstream TLS
Upstream TLS ensures that Pomerium and your protected services authenticate each other. By default, Pomerium authenticates user traffic but does not require or verify the identity of the upstream service. For zero-trust consistency, the upstream service should also present a valid TLS certificate, and Pomerium can optionally present its own client certificate (mTLS) to the service.
How Pomerium verifies upstream services
- Custom CA: Point Pomerium to a trusted certificate authority using
tls_custom_ca_file
ortls_upstream_server_name
. This ensures Pomerium will only trust upstreams signed by your preferred internal CA or other recognized CAs. - Client Certificate: If required by the upstream, Pomerium can present its own certificate and key (
tls_client_cert_file
andtls_client_key_file
) to complete an mTLS handshake.
Example: mkcert + OpenSSL
This guide demonstrates a proof-of-concept using mkcert
to generate self-signed certificates and an OpenSSL s_server as the upstream application.
Prerequisites
- A root CA created by
mkcert
(as above). - A Pomerium configuration already set up (in all-in-one mode).
openssl
installed on your system.
Steps
-
Create Upstream Certificates
mkcert openssl.localhost
mkcert -client "pomerium@localhost"Move the resulting files to appropriate locations (e.g.,
/etc/pomerium/
) and adjust ownership as needed. -
Start an OpenSSL Server
openssl s_server \
-key ./openssl.localhost-key.pem \
-cert ./openssl.localhost.pem \
-accept 44330 \
-wwwThis listens on port 44330 for TLS connections.
-
Configure a Pomerium Route
- from: https://openssl.localhost.pomerium.io
to: https://localhost:44330
tls_upstream_server_name: openssl.localhost
policy:
- allow:
or:
- email:
is: user@example.comVisiting
https://openssl.localhost.pomerium.io
should show the following notice in your browser:“no client certificate available” indicates that Pomerium is connecting but not supplying a certificate to the upstream yet.
-
Require mTLS on the Upstream
Restart the OpenSSL server with-Verify 1
:openssl s_server \
-Verify 1 \
-key ./openssl.localhost-key.pem \
-cert ./openssl.localhost.pem \
-accept 44330 \
-wwwNow the upstream expects a client certificate from its connection peer.
-
Provide Pomerium's Client Certificate
Update the route to include:tls_client_cert_file: /etc/pomerium/pomerium@localhost-client.pem
tls_client_key_file: /etc/pomerium/pomerium@localhost-client-key.pemRefresh
https://openssl.localhost.pomerium.io
. The OpenSSL server's output should now show a validated client certificate from Pomerium.
More Resources
With both downstream TLS and upstream TLS in place, Pomerium ensures secure connections at every hop—users prove their identity to Pomerium, and Pomerium proves its identity to the protected services (and vice versa, if the service requires a client certificate). This consistent end-to-end encryption and mutual authentication is a key building block for a robust zero-trust architecture.