Skip to content

Secure Ingress with TLS Using cert-manager

Introduction

In this guide, you will learn how to automatically secure your Kubernetes Ingress resources with TLS certificates using cert-manager and Let's Encrypt.

What is cert-manager?

cert-manager is a powerful Kubernetes add-on that automates the management of TLS certificates. It can request, renew, and manage certificates from a variety of sources — including public certificate authorities like Let's Encrypt. It helps you keep your services secure without manual intervention.

cert-manager introduces several Custom Resource Definitions (CRDs) to automate TLS certificate management:

  • Certificate: Defines the desired certificate, including the domain names, the secret to store it in, and which issuer to use.
  • CertificateRequest: Automatically created from a Certificate resource. Contains the certificate signing request (CSR).
  • Issuer / ClusterIssuer: Define how and from where the certificate should be requested. An Issuer works in a single namespace, while a ClusterIssuer can be used cluster-wide.
  • Order: Automatically created when using Let's Encrypt. Represents a certificate order and is handled internally by cert-manager.
  • Challenge: Automatically created to verify domain ownership. cert-manager manages the challenge-response process for you.

In addition to these CRDs, cert-manager interacts with standard Kubernetes Secret resources:

  • Secret: The certificate and private key are stored here, making them available for use by Ingress controllers or other workloads.

These resources form a chain of automation. Once a Certificate resource is defined, cert-manager:

  1. Creates a CertificateRequest.
  2. Uses the Issuer to fulfill the request.
  3. Solves challenges via Order and Challenge.
  4. Stores the final certificate in a Kubernetes Secret.

What is Let's Encrypt?

Let's Encrypt is a free, automated, and trusted certificate authority (CA). It allows anyone to get valid TLS certificates for their domains — without needing to pay or do complex manual setup. Certificates issued by Let's Encrypt are trusted by all major browsers and operating systems.

What Will You Do?

  1. Install cert-manager using Helm.

  2. Deploy a ClusterIssuer resource that uses Let's Encrypt.

  3. Automatically issue and renew TLS certificates for your Kubernetes Ingress resources.

Step 0: Prerequisites

Helm

Ensure that Helm is installed locally. If it isn't, follow the official Helm installation guide.

Ingress Resources

This guide assumes you have followed the Deploy Ingress NGINX Controller guide.

Make sure you have:

  • Deployed the Ingress NGINX controller using Helm.

  • Deployed backend services.

  • Created corresponding Ingress resources for those services.

A similar setup will also work, but may require adapting commands and values.

DNS Records

Let's Encrypt uses DNS and HTTP challenges to validate domain ownership. For the HTTP-01 challenge used in this guide, ensure that each domain name has a valid A-record pointing to the external IP address of your Ingress controller service.

Download Templates

Download the following templates and store them in a dedicated folder:

Or copy and paste them from below:

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    email: user@example.com
    privateKeySecretRef:
      name: letsencrypt-staging
    solvers:
    - http01:
        ingress:
          class: nginx
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: user@example.com
    privateKeySecretRef:
      name: letsencrypt-prod
    solvers:
    - http01:
        ingress:
          class: nginx

Step 1: Deploy cert-manager

cert-manager provides a Helm chart for easy, out-of-the-box installation.

To install the cert-manager Helm chart, run the following command:

helm upgrade --install \
  cert-manager cert-manager \
  --repo https://charts.jetstack.io \
  --namespace cert-manager \
  --create-namespace \
  --set crds.enabled=true
This will:

  • Download the chart from the Jetstack Helm repository.
  • Install cert-manager in the cert-manager namespace.
  • Create the cert-manager namespace if it does not exist yet.
  • Install the latest stable version available at the time you run it.
  • Install the CustomResourceDefinitions provided and required by cert-manager.

Important

You can find a full list of available Helm values on cert-manager's ArtifactHub page.

Example output
Release "cert-manager" does not exist. Installing it now.
NAME: cert-manager
LAST DEPLOYED: Tue Apr 22 12:13:43 2025
NAMESPACE: cert-manager
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
cert-manager v1.17.1 has been deployed successfully!

Step 2: Configure a Let's Encrypt ClusterIssuer

Now that cert-manager is up and running, you can set up a ClusterIssuer in order to generate signed certificates.

In this guide, you will set up both a staging and production Let's Encrypt ClusterIssuer. The production issuer has rate limits, and therefore the staging issuer should be used for experimenting and learning. As soon as you're confident that the issuer is working well, you can switch to the production issuer.

In the templates provided in Download Templates (staging_issuer.yaml, production_issuer.yaml), you can optionally provide an email address. The email address might be used by Let's Encrypt for important account-related updates.

Info

In this guide, we use ClusterIssuer, which is a cluster-scoped resource and must not specify a namespace field in its metadata.

If you prefer to use an Issuer instead (which is namespaced), you must:

  • Change the kind field from ClusterIssuer to Issuer.
  • Add a namespace field under metadata, specifying the namespace where the Issuer will be available.

Apply the staging issuer:

kubectl apply --filename staging_issuer.yaml
Example output
clusterissuer.cert-manager.io/letsencrypt-staging created
staging_issuer.yaml explained
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
  # ACME server URL
  # For prod: https://acme-v02.api.letsencrypt.org/directory
  server: https://acme-staging-v02.api.letsencrypt.org/directory
  # Email used for ACME registration
  email: user@example.com
  # The name of a Kubernetes Secret resource that will be used to
  # store the automatically generated ACME account private key
  privateKeySecretRef:
    name: letsencrypt-staging
  solvers:
  # Configures cert-manager to attempt to complete authorizations by performing
  # the HTTP01 challenge flow (see https://letsencrypt.org/docs/challenge-types/#http-01-challenge)
  - http01:
      ingress:
        class: nginx

When cert-manager uses a ClusterIssuer for the first time, it automatically registers an ACME account with Let's Encrypt. This account identifies your cluster and is associated with the private key stored in the privateKeySecretRef Secret.

If the referenced Secret already exists, cert-manager will reuse the existing ACME account. If the Secret does not exist yet, cert-manager generates a new private key, registers a new ACME account, and stores it in the Secret.

Info

One ACME account can issue certificates for many different domains and subdomains. It is independent of any specific certificate.

Warning

If the Secret referenced in privateKeySecretRef already exists, cert-manager reuses the existing ACME account. Changing the email field in the ClusterIssuer will have no effect — Let's Encrypt continues using the email address associated with the existing ACME account. To use a new email address, you must create a new Secret and register a new ACME account.

Now, repeat this process (enter your email address and apply production_issuer.yaml) for the production ClusterIssuer:

kubectl apply --filename production_issuer.yaml
Example output
clusterissuer.cert-manager.io/letsencrypt-prod created

Step 3: Issue Staging and Production Let's Encrypt Certificates

Now that the ClusterIssuer is created for both staging and production, you can modify the Ingress resources deployed previously in order to enable TLS encryption.

Using the ingress.yaml template, make the following changes for each ingress resource:

...
metadata:
  name: cheddar
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-staging"
spec:
  tls:
    - hosts:
        - cheddar.example.com
      secretName: cheddar-tls
  ingressClassName: nginx
  rules:
...
  • In Lines 5 and 6, you tell cert-manager to use the letsencrypt-staging ClusterIssuer that you deployed in Step 2.
  • In the tls block on Line 8, you specify the hosts for which you want to generate certificates. In addition, a name is defined for the secret that will contain the TLS private key.

Warning

Be sure to swap out the example domain with your own.

Apply the changes to the existing Ingress resources (or deploy them from anew):

kubectl apply --filename ingress.yaml

To track the state of the changes you just applied to the Ingress resources, run:

kubectl describe ingress

For each Ingress deployed, you should see that Certificates are created:

Example output
...
Events:
Type    Reason             Age   From                       Message
----    ------             ----  ----                       -------
Normal  Sync               3s    nginx-ingress-controller   Scheduled for sync
Normal  CreateCertificate  3s    cert-manager-ingress-shim  Successfully created Certificate "cheddar-tls"
...

Info

Kubernetes events are ephemeral and often expire within an hour. If you run the command some time after applying your resources the Events section may be empty — this is normal and does not indicate a problem.

To confirm that the certificates have been successfully issued, run:

kubectl describe certificate

This command shows detailed information about the Certificate resource, including its current status, validity period, and associated events.

Example output
...
Status:
  Conditions:
    Last Transition Time:  2025-04-23T09:13:56Z
    Message:               Certificate is up to date and has not expired
    Observed Generation:   2
    Reason:                Ready
    Status:                True
    Type:                  Ready
  Not After:               2025-07-22T08:15:21Z
  Not Before:              2025-04-23T08:15:22Z
  Renewal Time:            2025-06-22T08:15:21Z
  Revision:                1
...
Events:
  Type    Reason     Age    From                                       Message
  ----    ------     ----   ----                                       -------
  Normal  Generated  7m43s  cert-manager-certificates-key-manager      Stored new private key in temporary Secret resource "cheddar-tls-f6m5k"
  Normal  Requested  7m43s  cert-manager-certificates-request-manager  Created new CertificateRequest resource "cheddar-tls-7x5hq"
  Normal  Issuing    6m51s  cert-manager-certificates-issuing          The certificate has been successfully issued

HTTPS encryption is now active for the domains configured. You are ready to send a request to one of the backend services in order to test that HTTPS is working correctly.

Run the following command to send a request to the cheddar backend and print the response in your terminal:

curl -Lvs -o- cheddar.example.com

You should see the following output:

Example output
< HTTP/1.1 308 Permanent Redirect
... 
< Location: https://cheddar.example.com
...
* schannel: SEC_E_UNTRUSTED_ROOT (0x80090325) - The certificate chain was issued by an authority that is not trusted.

This confirms HTTP requests are correctly redirected to HTTPS, and that a TLS certificate is being served. The warning about an untrusted certificate is expected when using Let's Encrypt's staging environment, and it will disappear once you're using a valid certificate from Let's Encrypt's live environment.

To fix this, you can now roll out production certificates.

To do so, update the ClusterIssuer name in all your ingress resources like in the example below:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: cheddar
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
...

And apply the changes:

kubectl apply --filename ingress.yaml

It might take a few minutes for the Let's Encrypt production server to issue the certificate. You can track the progress on the certificate object:

kubectl describe certificate cheddar-tls

The certificate has been successfully issued if you see the following output messages:

Example output
Normal  Issuing    28s   cert-manager                               Issuing certificate as Secret was previously issued by ClusterIssuer.cert-manager.io/letsencrypt-staging
Normal  Reused     84s   cert-manager-certificates-key-manager      Reusing private key stored in existing Secret resource "cheddar-tls"
Normal  Requested  84s   cert-manager-certificates-request-manager  Created new CertificateRequest resource "cheddar-tls-zj2hc"
Normal  Issuing    53s   cert-manager-certificates-issuing          The certificate has been successfully issued

Once again, you can send a request to the cheddar backend:

curl cheddar.example.com
Example output
...
* Connected to cheddar.example.com (<external_ip>) port 443
< HTTP/1.1 200 OK
< Strict-Transport-Security: max-age=31536000; includeSubDomains
...
<html>
  <body>
    <h1>Cheddar</h1>
  </body>
</html>

This indicates that HTTP requests are being redirected to HTTPS. Navigate to one of your domains (i.e. cheddar.example.com) in a browser or run curl https://cheddar.example.com. You should see your backend service running as expected. The padlock on the left of the address bar will confirm that your connection is secure.

Alternatively, run curl -v https://cheddar.example.com in a terminal. The verbose output will display the successful certificate handshake process as well as some certificate information.

Certificate Validity and Renewal

Let's Encrypt certificates have a relatively short validity period of 90 days by design. This short lifetime improves security, but it also means that certificates must be renewed regularly.

You do not need to manually renew certificates. cert-manager automatically handles the renewal process and requests new certificates from Let's Encrypt before the current certificates expire. As long as cert-manager is operating correctly and the required Issuer (or ClusterIssuer) and Certificate resources exist, certificate renewal happens seamlessly in the background.

Info

cert-manager typically initiates the renewal process 30 days before a certificate's expiration date. You can customize this behavior by setting the renewBefore field in the Certificate resource. For more details, see the cert-manager Certificate documentation.

Conclusion

In this guide, you secured your Ingress by installing the cert-manager certificate provisioner and setting up a Let's Encrypt ClusterIssuer to automatically issue TLS certificates. Your HTTP traffic is now redirected to HTTPS, and your backend services are accessible over a secure connection.