bg-tutorials

How to Install an ACME SSL Certificate in Kubernetes

Kubernetes handles certificates differently from every other platform in this series. There is no ACME client you run and no file you copy: cert-manager is a controller that watches for certificate resources, talks to the CA, stores the result in a Kubernetes secret and renews it on its own. This guide sets that up against a commercial certificate authority, which means supplying External Account Binding credentials.

Under CA/Browser Forum ballot SC-081v3, publicly trusted TLS certificates have been capped at 200 days since March 15, 2026, dropping to 100 days on March 15, 2027 and to 47 days on March 15, 2029. cert-manager copes with that better than most tooling because its renewal trigger is a proportion of the certificate’s lifetime rather than a fixed number of days, as the renewal section explains.

What you will need

  • A working cluster and a configured kubectl. Each cert-manager release supports a specific range of Kubernetes versions, so check the compatibility table for the release you are installing rather than relying on a number from an older guide.
  • An ingress controller such as ingress-nginx or Traefik, with your domain resolving to its public address. HTTP-01 validation is answered through it.
  • Helm, for the installation below.
  • Your CA’s ACME directory URL, EAB Key ID and EAB HMAC Key.

Step 1: Install cert-manager

cert-manager’s own documentation now points at the OCI registry rather than the older Helm repository, which it describes as the source of truth, published immediately on release, while the legacy HTTP repository is updated some hours later.

helm install cert-manager \
  oci://quay.io/jetstack/charts/cert-manager \
  --version v1.21.1 \
  --namespace cert-manager \
  --create-namespace \
  --set crds.enabled=true

Two things there commonly come from stale instructions. The flag for installing the custom resource definitions is crds.enabled; older guides use installCRDs, which the current documentation no longer mentions. And --create-namespace removes the need for a separate namespace command. Check the current release rather than copying the version above, since pinning a two-year-old chart is how installations drift.

Confirm the three controllers are running:

kubectl get pods -n cert-manager

You should see cert-manager, cert-manager-webhook and cert-manager-cainjector. Nothing will be issued until all three are ready, because the webhook validates the resources you are about to create.

Step 2: Store the EAB key as a secret

kubectl create secret generic acme-eab-secret \
  --namespace cert-manager \
  --from-literal=secret="YOUR_EAB_HMAC_KEY"

The namespace matters. A ClusterIssuer looks for its secrets in cert-manager’s own namespace, not in the namespace of the application, so a secret created next to your workload will not be found. A plain Issuer, which is scoped to one namespace, reads its secrets from that same namespace instead.

One requirement is easy to miss and produces a confusing failure. cert-manager states that the key stored in the secret must be un-padded, base64url encoded data. Most CAs hand you the HMAC key in exactly that form, so paste it unchanged; the mistake is adding padding, converting it to standard base64, or decoding it first because it looks like it needs decoding.

Step 3: Create the ClusterIssuer

This resource is what connects cert-manager to your CA. Indentation is significant in YAML, so keep the structure exactly as below.

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: acme-issuer
spec:
  acme:
    server: https://acme.yourca.example/v2/DV
    email: [email protected]
    privateKeySecretRef:
      name: acme-account-key
    externalAccountBinding:
      keyID: YOUR_EAB_KEY_ID
      keySecretRef:
        name: acme-eab-secret
        key: secret
    solvers:
      - http01:
          ingress:
            class: nginx
kubectl apply -f clusterissuer.yaml

Points worth understanding rather than copying:

  • privateKeySecretRef names a secret cert-manager creates itself to hold the ACME account key. It is not something you populate; it is where the registration is stored.
  • keySecretRef points at the secret from Step 2, and key is the field name inside it. The two must agree, which is why the example uses secret in both places.
  • class under the HTTP-01 ingress solver is your ingress class, so traefik or another value if you do not run ingress-nginx.
  • There is a keyAlgorithm field you may see in older examples. It is deprecated, and cert-manager notes the algorithm is now hardcoded to HS256. That matters if your CA requires HS384 or HS512, because cert-manager cannot negotiate those; check with the CA before assuming EAB will work.

Check that registration succeeded before going further:

kubectl describe clusterissuer acme-issuer

A ready issuer reports the ACME account URI in its status. If it does not, the problem is the EAB credentials or the directory URL, and no certificate will work until it is fixed.

Step 4: Request the certificate, one way or the other

There are two routes, and you should pick one. Doing both is a common mistake that leaves two resources managing the same secret.

Route A: annotate the Ingress

The simpler option. cert-manager watches for the annotation and creates the Certificate resource for you, based on the hosts in the TLS block.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-ingress
  namespace: default
  annotations:
    cert-manager.io/cluster-issuer: acme-issuer
spec:
  tls:
    - hosts:
        - example.com
        - www.example.com
      secretName: my-ssl-cert-tls
  rules:
    - host: example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: your-service
                port:
                  number: 80

Use cert-manager.io/issuer instead of cert-manager.io/cluster-issuer if you created a namespaced Issuer.

Route B: write the Certificate yourself

More explicit, and the option to take when you want control over the names, the key, or the renewal timing. Leave the annotation off the Ingress in this case, and simply reference the same secret name in its TLS block.

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: my-ssl-cert
  namespace: default
spec:
  secretName: my-ssl-cert-tls
  issuerRef:
    name: acme-issuer
    kind: ClusterIssuer
  dnsNames:
    - example.com
    - www.example.com
kubectl apply -f certificate.yaml

The Certificate must live in the same namespace as the workload that uses the secret, since secrets are namespaced even when the issuer is cluster-wide. Setting commonName is optional and rarely useful now: clients match on the subject alternative names, which is what dnsNames produces.

Step 5: Verify

kubectl describe certificate my-ssl-cert

The events at the bottom tell the story: the Certificate creates a CertificateRequest, which creates an Order, which creates a Challenge. When something stalls, walking down that chain finds the reason faster than reading controller logs:

kubectl get certificate,certificaterequest,order,challenge -A

A Challenge stuck in pending means the CA cannot reach the validation path, which is an ingress or DNS problem rather than a cert-manager one. Then confirm what the ingress actually serves, from outside the cluster:

openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates

The SSL Checker reports the same from outside your network, including whether the intermediate is being served with the leaf.

Step 6: Renewal

Nothing to schedule: cert-manager renews on its own. What is worth knowing is when.

  • The default is one third of the issued certificate’s lifetime remaining. Not a fixed 30 days, which is the figure older guides give because it is what one third of a 90-day certificate happens to be. On a 200-day certificate the same rule renews at roughly 67 days remaining, and it will keep adjusting as the industry caps fall.
  • cert-manager uses the lifetime of the certificate it actually received, not the one requested, so a CA that issues something shorter than you asked for still gets a correctly scaled renewal window.
  • ACME Renewal Information is not used unless you switch it on. ARI arrived in cert-manager 1.21 as the alpha feature gate ACMEUseARI, and alpha gates are off by default, so out of the box the proportional rule above is the only thing deciding when a certificate is renewed. Add ACMEUseARI=true to the controller’s feature gates, which the Helm chart exposes as its featureGates value, if you want the window the CA publishes to be followed instead.
  • renewBefore on the Certificate overrides the default with an absolute duration, and renewBeforePercentage does the same as a proportion. Neither is needed in normal use.

To force a renewal for testing, use the cert-manager CLI. It is a separate install: the tool now lives in its own repository, and cert-manager 1.14 was the last release to ship it as part of cert-manager itself, so on any current version you install cmctl separately. It also still works as a kubectl plugin, though the standalone binary is what the project now recommends.

cmctl renew my-ssl-cert

This asks the CA for a fresh certificate, so it counts against rate limits. Run it once rather than in a loop.

Common problems

  • The ClusterIssuer never becomes ready. Almost always the EAB credentials. Check the key is un-padded base64url, that the secret is in cert-manager’s namespace for a ClusterIssuer, and that key in the issuer matches the field name you used when creating the secret.
  • A Challenge sits pending. The CA cannot reach the challenge path. Confirm the ingress class in the solver matches your controller, that the domain resolves to the ingress address, and that port 80 is reachable, since HTTP-01 starts there even when the site redirects to HTTPS.
  • Two certificates appear for one host. You used both the Ingress annotation and a hand-written Certificate. Remove one.
  • The secret exists but the application does not use it. Secrets are namespaced. The Certificate and the workload must be in the same namespace, whatever the issuer’s scope.
  • Resources are rejected on apply. Check the webhook pod is running; cert-manager validates its own resources through it, and a webhook that is not ready rejects everything.
  • kubectl cert-manager is not found. The plugin is a separate install on current versions.

For error strings that come from the ACME protocol rather than from cert-manager, such as badNonce, unauthorized or a CAA record refusing issuance, see our guide to fixing ACME SSL certificate errors.

Frequently Asked Questions

Should I use a ClusterIssuer or an Issuer?

A ClusterIssuer when several namespaces need certificates from the same CA, which is the usual case, and an Issuer when you want a single namespace to own its own ACME account. The practical difference to remember is where secrets are read from: a ClusterIssuer looks in cert-manager’s namespace, an Issuer in its own.

Can I use DNS-01 instead of HTTP-01?

Yes, and it is the better choice when the cluster is not reachable on port 80 or when you need a wildcard. Replace the http01 solver with a dns01 one configured for your DNS provider, with its API credential in a secret. cert-manager ships solvers for the major providers and supports others through webhooks.

Can I get a wildcard certificate?

Only with DNS-01, because no certificate authority validates a wildcard over HTTP. Configure a dns01 solver, then list the wildcard in dnsNames. Remember a wildcard covers one level, so *.example.com does not include example.com itself; list both if you need both.

Why does my EAB key not work?

Three usual causes. The value in the secret is not un-padded base64url, which is the encoding cert-manager requires. The secret is in the wrong namespace for the issuer type you chose. Or your CA expects an HMAC algorithm other than HS256, which cert-manager cannot use, since the deprecated keyAlgorithm field no longer has any effect and the algorithm is hardcoded.

When does cert-manager renew?

When one third of the issued certificate’s lifetime remains, using the actual lifetime rather than the requested one, and earlier than that only if you have enabled the alpha ACMEUseARI feature gate, which is what lets the window published by the CA override the calculation. The often-quoted 30 days is what that rule produces for a 90-day certificate rather than a rule in its own right. You can override it per certificate with renewBefore or renewBeforePercentage.

Is cert-manager suitable for production?

It is the standard tool for this job in Kubernetes and is widely deployed. The operational advice worth adding is mundane: keep it reasonably current, since installation instructions and chart flags change between releases, and test against your CA’s staging endpoint first so a misconfiguration does not consume production rate limits.

For more on the protocol behind all of this, see what the ACME protocol is and our ACME SSL certificates page. For the same job outside a cluster, see installing an ACME SSL certificate on Apache and NGINX.

Save 10% on SSL Certificates when ordering from SSL Dragon today!

Fast issuance, strong encryption, 99.99% browser trust, dedicated support, and 25-day money-back guarantee. Coupon code: SAVE10

A detailed image of a dragon in flight
Written by

I've been writing for SSL Dragon for over 10 years, focusing entirely on SSL certificates and digital security. My job is to take complex cybersecurity topics and strip away the jargon, making sure you get the clear, practical information you need to keep your website safe.

Avatar of Sergiu Rosca
Technical Review by Sergiu Rosca

Sergiu Rosca is the core web developer behind SSL Dragon. He manages the technical infrastructure, platform performance, and backend integrations that keep the site running smoothly and securely. At SSL Dragon, Sergiu shares practical insights on web development, site optimization, and technical troubleshooting.

All SSL Dragon installation guides are tested on live server environments and undergo a strict peer-review process to ensure your infrastructure remains secure. Read our full Editorial Policy.