Secure traffic to your application with Kubernetes and cert-manager

As a DevOps engineer at, I am working on the creation of the Production Kubernetes (EKS) cluster with all needed mechanisms for our SaaS, including the TLS certificates issuing for secure communication to and within the k8s cluster.

In this tutorial, I will show how to secure external and internal traffic coming to your application on the Kubernetes cluster by issuing the TLS certificates with the Cert-Manager.

To simplify things let’s introduce the ‘simply’ web app which sits on the Kubernetes cluster, gets external traffic, and talks to DB (internal traffic) which also installed on the same k8s cluster. Let’s make an assumption that we are able to reach the ‘simply’ app on this domain using the HTTP protocol (port 80).

What I am trying to achieve?

  1. Secure all external traffic to the ‘simply’ app using TLS certificate from Let’s Encrypt and adding communication through port 443 (HTTPS)
  2. Secure internal traffic between the ‘simply’ app and DB using the self-signed certificate.

What is NGINX Ingress?

ingress-nginx is an Ingress controller for Kubernetes using NGINX as a reverse proxy and load balancer.

To install Nginx Ingress we will use the Helm package manager.

What is cert-manager?

cert-manager is a native Kubernetes certificate management controller. It can help with issuing certificates from a variety of sources, such as Let’s Encrypt, HashiCorp Vault, Venafi, a simple signing key pair, or self signed. It will ensure certificates are valid and up to date, and attempt to renew certificates at a configured time before expiry. It is loosely based upon the work of kube-lego and has borrowed some wisdom from other similar projects such as kube-cert-manager.

So how to achieve this?


  • Kubernetes cluster (EKS in my case)

  • Ingress-Nginx installed to Kubernetes cluster

  • Cert-Manager Installed to Kubernetes cluster

  • Configured ‘A’ record which points to your Load Balancer created by Ingress, basically, an alias which points to created LB by Ingress.

You can read about the installation and configuration of all those components in details in my post: Building the CI/CD of the Future, NGINX Ingress + Cert-Manager

Configure Cluster Issuer to issue Let’s Encrypt certificates

To obtain Let’s Encrypt certificates we need to create an issuer, it may be Issuer or ClusterIssuer, the difference is that an Issuer is scoped to a single namespace and ClusterIssuer is a cluster-wide version of an Issuer.

Creation of production ‘Cluster Issuer’ for Let’s Encrypt certificates:

# cluster-issuer.yaml
kind: ClusterIssuer
  name: letsencrypt-prod
    # The ACME server URL
    # Email address used for ACME registration
    email: [email protected]
    # Name of a secret used to store the ACME account private key
      name: letsencrypt-prod
    # Enable the HTTP-01 challenge provider
    - http01:
          class: nginx

We using HTTP Validation and ACME protocol for ClusterIssuer

Deploy cluster-issuer.yaml

$ kubectl create -f cluster-issuer.yaml

Issue a new Let’s Encrypt certificate with cert-manager for

To issue a new certificate we will create the Ingress-Nginx rule for the domain, which uses cert-manager and cluster-issuer we created to issue a certificate from Let’s encrypt.

Pay attention to ‘annotations’ and ‘tls’ parts, you can see the magic of integration between ingress and cert-manager by using annotations.

# simply-ingress.yaml
apiVersion: extensions/v1
kind: Ingress
  name: simply-ing
  annotations: "nginx" "letsencrypt-prod"
  - hosts:
    secretName: simply-tls-prod
  - host:
      - path: /
          serviceName: simply-svc
          servicePort: 80

I make an assumption that you can reach the ‘simply’ app inside of the cluster via the ‘simply-svc’ service and port 80.

Deploy simply-ingress.yaml

$ kubectl create -f simply-ingress.yaml

Now you need to wait till Cert-Manager acquires a certificate for the domain, it may take some time. When the certificate will be acquired you will be able to reach using HTTPS.

You can check acquired certificates and status using ‘kubectl’:

$ kubectl get certificates -n cert-manager

Issue self-signed certificate (signed by CA) with cert-manager for securing internal traffic between ‘simply’ and DB

I will make an assumption that ‘simply’ will be deployed to ‘simplyns’ namespace:

$ kubectl create ns simplyns

Create self-signed certificate issuer:

notice that Issuer is scoped to a single namespace

kind: Issuer
  name: selfsigned-issuer
  selfSigned: {}

Create a CA certificate that will be used to sign other certificates:

‘isCA’ must be true

kind: Certificate
  name: simplyns-ca
  secretName: simplyns-ca-tls
  commonName: simplyns.svc.cluster.local
    - server auth
    - client auth
  isCA: true
    name: selfsigned-issuer

Check generated certificate/secret created:

$ kubectl get certificate simplyns-ca -n simplyns
$ kubectl get secret simplyns-ca-tls -n simplyns

Create an additional Issuer to issuer certificates signed by CA

notice that we using previously generated for CA secret

kind: Issuer
  namespace: simplyns
  name: simply-ca-issuer
    secretName: *simplyns-ca-tls*

And finally, generate a certificate signed by CA for ‘simply’ and DB communication:

simply deployed to simplyns namespace

db deployed to dbns namespace

‘simply.simplyns.svc.cluster.local’ is internal dns domain of ‘simply’

‘db.dbns.svc.cluster.local’ is internal dns domain of DB

kind: Certificate
  namespace: simplyns
  name: simply-cert
  secretName: simply-certs
  isCA: false
    - server auth
    - client auth
  - "simply.simplyns.svc.cluster.local"
  - "db.dbns.svc.cluster.local"
    name: simply-ca-issuer

You can check generated secret is created:

$ kubectl get secret simply-cert -n simplyns

Please follow me on Twitter (@warolv)

My Github

This post on my medium