Skip to content

7.3. Certificate Management

When to Look at This

  • When an HTTPS connection produces a certificate error
  • When a certificate is nearing expiry
  • When issuing a certificate for a new domain

Why Certificates Are Needed

When you connect over HTTPS, what the browser and the server exchange is encrypted. The server has to prove "I am the real owner of that domain", and the certificate is that proof.

Certificates have a validity period. Once expired, the browser warns and blocks the connection. With several domains, the expiry times all differ, which makes manual management difficult.

What cert-manager Does

cert-manager issues and renews certificates automatically inside the cluster.

It works like any other Kubernetes resource. You create a request (a Certificate) saying "a certificate for this domain is needed", cert-manager proceeds with issuance to match that state, and it renews on its own as expiry approaches.

The result is stored as a Secret, and the Ingress points at that Secret to use it (see 3.4, 5.1).

The Certificate List

Go to Cert-manager > Certificates.

Certificate list
ColumnDescription
NameThe certificate resource name
NamespaceThe namespace it belongs to
ReadyWhether issuance has finished
IssuerWhich Issuer issued it
ExpiryWhen the certificate expires

Ready being True means it is fine.

Certificates are namespace-scoped resources. They must be in the same namespace as the Ingress to be usable. A Secret in another namespace is not referenced.

Certificate Detail

Selecting the name shows the issuance state and expiry time in detail.

Certificate detail
ItemWhat to check
ConditionsWhether Ready is True, and the reason if False
Expiry timeHow long it is valid
Scheduled renewal timeWhen cert-manager will attempt renewal
Target domainsThe addresses this certificate covers
Storage SecretThe name of the Secret holding the result

If the target domains differ from the actual access address, the browser raises a certificate error. Check that they match the Ingress host (see 5.1).

Issuing with a wildcard such as *.example.com covers all subdomains. However, the ownership verification method changes (DNS verification only), which makes the setup more demanding.

Issuers

This is the configuration that decides where certificates come from.

KindScope
IssuerWithin one namespace only
ClusterIssuerThe whole cluster

View them under Cert-manager > Cluster Issuers and Issuers respectively.

There are broadly three issuance methods.

MethodDescriptionAir-gapped
Self-signedSigned with a certificate cert-manager made itselfPossible
Internal certificate authority (CA)Signed with a CA certificate the organization holdsPossible. This is usually the method used
ACMEObtained automatically from a public authorityRequires external communication, so not possible

ACME (Automatic Certificate Management Environment) is a protocol for obtaining certificates without human hands. In the past you submitted an application on the authority's website, waited for review, and downloaded a file; ACME completes that exchange between programs.

The sequence is as follows.

OrderWhat happens
1The side needing a certificate asks the authority "give me a certificate for this domain"
2The authority sets a challenge: "prove that domain is really yours"
3The requesting side completes the challenge (places a file at a given path or adds a value to DNS)
4The authority verifies from outside and, if it matches, issues the certificate

Steps 2 and 3 are why it cannot be used in an air-gapped environment. The authority has to reach our server or DNS from the internet, and an air-gapped environment has no such route. The Order and Challenge resources come from this protocol (see "Resources in the Issuance Process" below).

The best-known authority using ACME is Let's Encrypt, and it issues free of charge. The detailed procedure is in "Getting a Public Certificate With Let's Encrypt" below.

In an air-gapped environment, register an internal CA as a ClusterIssuer and use that. Because these certificates are not signed by a public authority, the organization's CA certificate has to be added to the trust store on the connecting PCs in advance for the browser warning to disappear.

Resources in the Issuance Process

Requesting a certificate creates the following resources in order.

The certificate issuance process
ResourceWhat it does
CertificateDeclares the certificate you want
CertificateRequestThe actual signing request
OrderThe order created in the ACME method
ChallengeThe procedure proving domain ownership

With the internal CA method, Order and Challenge are not created, because signing happens directly from the CertificateRequest.

If issuance stalls, follow this order to find the stage where it stopped. Each has a screen under the Cert-manager menu.

Creating Self-Signed Certificates — Step by Step

This is the method used most in air-gapped environments. You create an internal CA once and stamp out certificates for each service with it.

You could also create a separate self-signed certificate per service without going through a CA, but that is not recommended. Each certificate has a different signer, so connecting PCs need as many trust registrations as there are certificates. With one internal CA, only that CA has to be registered.

There are four steps. Each step is the material for the next, so the order has to be kept.

The four steps of creating self-signed certificates

Apply the YAML for each step through Create resource in the Console or the YAML editor (see 1.2).

Step 1 — The Self-Signed Issuer

Something has to sign the CA certificate itself. The very first certificate has nothing above it to sign it, so it signs itself. This issuer is used once, in the next step, and then done.

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: selfsigned-cluster-issuer
spec:
selfSigned: {}

spec.selfSigned being an empty object is correct. There is nothing to set.

Step 2 — The Internal CA Certificate

Create the organization's root certificate. isCA: true is what makes this "a certificate that can sign other certificates".

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: cluster-root-ca
namespace: cert-manager
spec:
isCA: true # a certificate that can sign other certificates
commonName: cluster-root-ca
secretName: cluster-root-ca-secret # the CA certificate and private key are stored here
duration: 87600h # 10 years
renewBefore: 8760h # renew one year before expiry
privateKey:
algorithm: RSA
size: 4096 # a CA is used for a long time, so be generous
issuerRef:
name: selfsigned-cluster-issuer
kind: ClusterIssuer
FieldDescription
isCAtrue makes it a signing certificate. Do not set it on service certificates
secretNameThe name of the Secret where the created certificate and private key are stored
durationThe validity period. Written in hours (h). 87600h = 10 years
renewBeforeHow many hours before expiry to renew
privateKey.sizeThe key length. 4096 for a CA and 2048 for service certificates is typical

Give a CA a long period. Changing the CA means reissuing every certificate signed by it and replacing the trust store on every connecting PC.

The reason for namespace: cert-manager is that the ClusterIssuer in the next step looks for this Secret in that namespace. Created elsewhere, it will not be found.

Step 3 — The CA Issuer

This is the issuer that signs with the CA created above. From now on, every certificate points at this.

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: cluster-ca-issuer
spec:
ca:
secretName: cluster-root-ca-secret # the Secret created in step 2

This is the end of the one-time preparation.

Step 4 — Service Certificates

These are the certificates actually attached to services. Create one per service.

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: app-tls
namespace: my-app
spec:
secretName: app-tls-secret # the Ingress points at this name
duration: 8760h # 1 year
renewBefore: 720h # renew 30 days before expiry
commonName: app.example.com
dnsNames: # the list of addresses this certificate covers
- app.example.com
- app.my-app.svc.cluster.local
privateKey:
algorithm: RSA
size: 2048
issuerRef:
name: cluster-ca-issuer # the issuer created in step 3
kind: ClusterIssuer

List every address you will actually connect to in dnsNames. Connecting on an address not listed makes the browser warn that "the certificate name does not match". For a service used only inside the cluster, include the internal address (<service>.<namespace>.svc.cluster.local) as well.

commonName is the older mechanism, and what matters now is dnsNames. Write both, but always include the commonName value in dnsNames too.

Confirming Issuance

It is done when the Ready column turns True in the Cert-manager > Certificates list in the Console. The detail screen shows the validity period and the scheduled renewal time.

Registering the CA on Connecting PCs

Certificates made with an internal CA make browsers warn, because they do not know that CA. Adding the CA certificate created in step 2 to the trust store on connecting PCs removes the warning. Operations staff handle this distribution — pushing it to company PCs in bulk is the usual approach.

Getting a Public Certificate With Let's Encrypt

For a service published on the internet, you can obtain a public certificate. cert-manager obtains and renews it automatically from Let's Encrypt using the ACME protocol described above.

It cannot be used in an air-gapped environment. The issuance process has to communicate with Let's Encrypt servers, and domain ownership verification requires that our service be reachable from outside. COP is installed air-gapped by default, so this method applies only to services exposed to the internet.

How Domain Ownership Is Proven

Let's Encrypt issues after confirming "is this domain really yours". There are two verification methods.

MethodHow it verifiesWhat it needs
HTTP-01Places a temporary file at a particular path on our web server and Let's Encrypt reads it from outsidePort 80 must be open from the internet
DNS-01Adds a temporary record to DNS and Let's Encrypt looks it upAPI access to the DNS provider

HTTP-01 is simpler. However, wildcard certificates such as *.example.com can be obtained only through DNS-01.

Step 1 — The ACME Issuer (Staging)

Always test with the staging server first. The Let's Encrypt production server has issuance limits. In particular, only five certificates per week for the same domain combination can be obtained, so repeated attempts with a wrong configuration block issuance for the rest of that week. The staging server has generous limits.

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory
email: admin@example.com # the address for expiry notices
privateKeySecretRef:
name: letsencrypt-staging-key # the Secret where the account key is stored (created automatically)
solvers:
- http01:
ingress:
ingressClassName: default # the Ingress controller that will create the temporary path
FieldDescription
serverThe staging address. Production uses the address in step 2 below
emailWhere Let's Encrypt sends notices about impending expiry and problems. Use an address you actually read
privateKeySecretRefThe Secret holding the ACME account key. It is created automatically; you do not have to prepare it
solversThe ownership verification method. HTTP-01 here

Certificates issued by the staging server are not trusted by browsers. Warnings are expected; it is only for confirming that the issuance procedure runs end to end.

Step 2 — The ACME Issuer (Production)

Once issuance is confirmed with staging, create the production one by changing only the address.

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@example.com
privateKeySecretRef:
name: letsencrypt-prod-key
solvers:
- http01:
ingress:
ingressClassName: default

Step 3 — Requesting the Certificate

Only the issuer name differs; the shape is the same as the self-signed method.

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: app-tls
namespace: my-app
spec:
secretName: app-tls-secret
dnsNames:
- app.example.com
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer

duration and renewBefore are not written here. Let's Encrypt fixes the validity period at 90 days and we cannot set it. Writing them is either ignored or raises an error. cert-manager decides the renewal timing itself.

Step 4 — Attaching It to an Ingress

You can skip creating a Certificate and just add one annotation to the Ingress. cert-manager creates the Certificate for you.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
namespace: my-app
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: default
tls:
- hosts:
- app.example.com
secretName: app-tls-secret # cert-manager creates it under this name
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: app
port:
number: 8080

This approach works the same with self-signed and internal CA issuers. Only the issuer name in the annotation changes.

When Using DNS-01

Use DNS-01 when you need a wildcard certificate or cannot open port 80. Only the solvers part differs, and settings vary by DNS provider. Below is an example showing only the shape.

solvers:
- dns01:
cloudflare:
email: admin@example.com
apiTokenSecretRef:
name: cloudflare-api-token
key: api-token

The DNS provider access information has to be created as a Secret in advance (see 3.4).

Attaching a Certificate to a Service

  1. Decide the Secret name to store it in when creating the Certificate.
  2. Wait until Ready turns True.
  3. Put that Secret name in the Ingress TLS settings (see 5.1).

Adding the cert-manager.io/cluster-issuer annotation to the Ingress means cert-manager creates the Certificate for you, without creating one separately (see the example above).

The Benefits

Managing certificates by hand repeats the same work — request issuance, download the file, create a Secret, mark the expiry date on a calendar, and do it all again before expiry. cert-manager removes that.

BenefitDescription
Outages from expiry disappearThis is the biggest reason. Certificate expiry is among the most common yet most preventable causes of service outages
Renewal takes no effortIt obtains a new certificate before expiry and swaps the Secret. The Ingress configuration is untouched
The configuration remains as codeWhich certificate covers which addresses is written in YAML, so it can be kept under version control
Private keys never leave the clusterKeys are generated inside the cluster and stored in a Secret. There is no exchanging them by email or shared folder
You see everything at onceThe Console screen shows the expiry date and status of every certificate together
Each service can have its ownYou do not have to reuse one everywhere, so if one certificate leaks, only that service needs reissuing

Points to Watch

ItemContent
The CA private key is the top-level assetIf cluster-root-ca-secret leaks, anyone can create certificates in your organization's name. Restrict who can see this Secret (see 7.1)
Secrets are not encryptedKubernetes Secrets are only encoded. Anyone who can read Secrets in that namespace reads the private key too (see 3.4)
Changing the CA means redoing everythingEvery certificate signed by it has to be reissued and the trust store on connecting PCs replaced. That is why CA periods are set long
Applications reading certificates directly need a restartThe Ingress controller notices Secret changes, but an application reading the file directly does not know about a renewal. Prepare a procedure to restart the pods after renewal
Let's Encrypt has issuance limitsTest with the staging server first. Repeated attempts on production with a wrong configuration block you for that week
Omitting dnsNames produces warningsAdding an address later means editing the Certificate and reissuing
ACME does not work in an air-gapped environmentChoosing the wrong issuer leaves it stalled at the Challenge stage, never finishing
Keep checking expiry datesAutomatic renewal can fail (issuer problems, permission changes, DNS changes). Review the Console list regularly

cert-manager does not manage the COP cluster's own certificates. Those are a separate system, covered in 7.4. Confusing the two leads to "the certificate management screen is fine but the cluster stops".

Automatic Renewal

cert-manager renews on its own before expiry. The default is when two thirds of the validity period has passed. For a 90-day certificate, that is around day 60.

On renewal, the Secret contents change automatically. The Ingress configuration does not have to be edited.

The Ingress controller detects the Secret change and uses the new certificate. However, if the application reads the certificate directly, the pod may have to be restarted for it to take effect.

When Issuance Fails

SymptomWhat to check
Ready stays FalseThe conditions and events on the Certificate detail
It stalls at CertificateRequestWhether the Issuer name is correct and that Issuer is ready
It stalls at ChallengeWhether the ownership verification method works in this environment. External verification methods cannot be used in an air-gapped environment
The Secret is not createdThe certificate has not been issued yet. Check the ready state first
Browser warningDomain mismatch, or the PC does not trust the internal CA

The cause is recorded in the events on the Certificate detail. Look there first.