<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Certera</title>
    <description>The latest articles on DEV Community by Certera (certera-llc).</description>
    <link>https://dev.to/certera-llc</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F14974%2F2f5faa82-de76-478b-b3f3-9638d47d86bd.jpg</url>
      <title>DEV Community: Certera</title>
      <link>https://dev.to/certera-llc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/certera-llc"/>
    <language>en</language>
    <item>
      <title>Automate SSL Certificate Renewal in Kubernetes with cert-manager</title>
      <dc:creator>Jessica howe</dc:creator>
      <pubDate>Tue, 06 Oct 2026 09:33:38 +0000</pubDate>
      <link>https://dev.to/certera-llc/automate-ssl-certificate-renewal-in-kubernetes-with-cert-manager-3k80</link>
      <guid>https://dev.to/certera-llc/automate-ssl-certificate-renewal-in-kubernetes-with-cert-manager-3k80</guid>
      <description>&lt;p&gt;If you run anything on Kubernetes, you've probably had this moment: a certificate quietly expires, an Ingress starts throwing browser warnings, and someone has to dig out the one person who knows how it was set up.&lt;/p&gt;

&lt;p&gt;You can stop that from ever happening. cert-manager is a Kubernetes add-on that issues certificates, stores them as Secrets and renews them before they expire. In this post, we'll set it up so you can &lt;strong&gt;&lt;a href="https://certera.com/automated-ssl-certificates" rel="noopener noreferrer"&gt;automate SSL certificate renewal&lt;/a&gt;&lt;/strong&gt; for good.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why SSL automation matters more every year
&lt;/h2&gt;

&lt;p&gt;Certificate lifetimes keep getting shorter. Publicly trusted certificates are capped at 200 days now, 100 days from March 2027 and 47 days from March 2029. Free certificates are moving too, with Let's Encrypt shortening its default lifetime from 90 days to 64 in February 2027 and 45 in February 2028.&lt;/p&gt;

&lt;p&gt;Renewing by hand at that pace doesn't scale. SSL automation is how teams keep up.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'll need
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A running Kubernetes cluster and kubectl access&lt;/li&gt;
&lt;li&gt;Helm 3&lt;/li&gt;
&lt;li&gt;An Ingress controller (the examples use NGINX)&lt;/li&gt;
&lt;li&gt;A domain pointing at your cluster&lt;/li&gt;
&lt;li&gt;About 15 minutes&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 1: Install cert-manager
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;helm repo add jetstack &lt;a href="https://charts.jetstack.io" rel="noopener noreferrer"&gt;https://charts.jetstack.io&lt;/a&gt;&lt;br&gt;
helm repo update&lt;br&gt;
helm install cert-manager jetstack/cert-manager \&lt;br&gt;
  --namespace cert-manager \&lt;br&gt;
  --create-namespace \&lt;br&gt;
  --set crds.enabled=true&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Pin a chart version with --version in production so upgrades are deliberate. Then check that the pods are running:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;kubectl get pods -n cert-manager&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You should see three pods in Running state: cert-manager, cainjector and webhook.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Create an Issuer
&lt;/h3&gt;

&lt;p&gt;An issuer tells cert-manager where to get certificates. We'll use a ClusterIssuer, which works across all namespaces. Start with the staging server so you don't hit rate limits while testing:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;apiVersion: cert-manager.io/v1&lt;br&gt;
kind: ClusterIssuer&lt;br&gt;
metadata:&lt;br&gt;
  name: letsencrypt-staging&lt;br&gt;
spec:&lt;br&gt;
  acme:&lt;br&gt;
    server: &lt;a href="https://acme-staging-v02.api.letsencrypt.org/directory" rel="noopener noreferrer"&gt;https://acme-staging-v02.api.letsencrypt.org/directory&lt;/a&gt;&lt;br&gt;
    email: &lt;a href="mailto:you@example.com"&gt;you@example.com&lt;/a&gt;&lt;br&gt;
    privateKeySecretRef:&lt;br&gt;
      name: letsencrypt-staging-key&lt;br&gt;
    solvers:&lt;br&gt;
      - http01:&lt;br&gt;
          ingress:&lt;br&gt;
            ingressClassName: nginx&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Apply it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;kubectl apply -f clusterissuer-staging.yaml&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When it works, create a second issuer named letsencrypt-prod with the production server URL: &lt;a href="https://acme-v02.api.letsencrypt.org/directory" rel="noopener noreferrer"&gt;https://acme-v02.api.letsencrypt.org/directory&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Request a certificate from an Ingress
&lt;/h3&gt;

&lt;p&gt;The simplest way is to add an annotation and a tls section to your Ingress:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;apiVersion: networking.k8s.io/v1&lt;br&gt;
kind: Ingress&lt;br&gt;
metadata:&lt;br&gt;
  name: my-app&lt;br&gt;
  annotations:&lt;br&gt;
    cert-manager.io/cluster-issuer: letsencrypt-staging&lt;br&gt;
spec:&lt;br&gt;
  ingressClassName: nginx&lt;br&gt;
  tls:&lt;br&gt;
    - hosts:&lt;br&gt;
        - app.example.com&lt;br&gt;
      secretName: app-example-com-tls&lt;br&gt;
  rules:&lt;br&gt;
    - host: app.example.com&lt;br&gt;
      http:&lt;br&gt;
        paths:&lt;br&gt;
          - path: /&lt;br&gt;
            pathType: Prefix&lt;br&gt;
            backend:&lt;br&gt;
              service:&lt;br&gt;
                name: my-app&lt;br&gt;
                port:&lt;br&gt;
                  number: 80&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;cert-manager sees the annotation, creates a Certificate, completes the validation challenge and stores the result in the secret named app-example-com-tls. Your Ingress controller picks it up automatically.&lt;/p&gt;

&lt;p&gt;Prefer explicit control? Create the Certificate yourself:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;apiVersion: cert-manager.io/v1&lt;br&gt;
kind: Certificate&lt;br&gt;
metadata:&lt;br&gt;
  name: app-example-com&lt;br&gt;
  namespace: default&lt;br&gt;
spec:&lt;br&gt;
  secretName: app-example-com-tls&lt;br&gt;
  duration: 2160h      # 90 days&lt;br&gt;
  renewBefore: 360h    # renew 15 days before expiry&lt;br&gt;
  dnsNames:&lt;br&gt;
    - app.example.com&lt;br&gt;
  issuerRef:&lt;br&gt;
    name: letsencrypt-staging&lt;br&gt;
    kind: ClusterIssuer&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The renewBefore field is what makes renewal automatic. If you leave it out, cert-manager chooses a sensible default and renews when about two thirds of the lifetime has passed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Check that it worked
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;kubectl get certificate&lt;br&gt;
kubectl describe certificate app-example-com&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Look for Ready: True. If it's stuck, trace the chain of resources:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;kubectl get certificaterequest,order,challenge&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can also read the expiry date straight from the Secret:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;kubectl get secret app-example-com-tls -o jsonpath='{.data.tls.crt}' \&lt;br&gt;
  | base64 -d | openssl x509 -noout -dates&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once staging works, switch the issuer name to letsencrypt-prod, delete the old Secret and let cert-manager reissue it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wildcard certificates with DNS-01
&lt;/h2&gt;

&lt;p&gt;HTTP-01 can't issue wildcards. For *.example.com, use the DNS-01 solver. Here's a Cloudflare example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;solvers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;dns01:
  cloudflare:
    apiTokenSecretRef:
      name: cloudflare-api-token
      key: api-token&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Create the token Secret first, with permission to edit DNS records for your zone only. Other providers, such as Route 53 and Google Cloud DNS, are supported too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using a commercial certificate authority
&lt;/h2&gt;

&lt;p&gt;You don't have to use a free CA. cert-manager works with any CA that supports ACME. Many commercial CAs require External Account Binding (EAB), which links your cert-manager account to your CA account:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;spec:&lt;br&gt;
  acme:&lt;br&gt;
    server: &lt;a href="https://YOUR-CA-ACME-DIRECTORY-URL" rel="noopener noreferrer"&gt;https://YOUR-CA-ACME-DIRECTORY-URL&lt;/a&gt;&lt;br&gt;
    email: &lt;a href="mailto:you@example.com"&gt;you@example.com&lt;/a&gt;&lt;br&gt;
    privateKeySecretRef:&lt;br&gt;
      name: commercial-ca-account-key&lt;br&gt;
    externalAccountBinding:&lt;br&gt;
      keyID: YOUR_KEY_ID&lt;br&gt;
      keySecretRef:&lt;br&gt;
        name: eab-hmac-secret&lt;br&gt;
        key: secret&lt;br&gt;
    solvers:&lt;br&gt;
      - http01:&lt;br&gt;
          ingress:&lt;br&gt;
            ingressClassName: nginx&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A commercial CA typically adds things free certificates don't: OV or EV validation, a warranty and a support team. [Add: Certera's ACME or API details, if supported, and a link to the setup guide.]&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't skip monitoring
&lt;/h2&gt;

&lt;p&gt;Automation can still fail silently, for example when DNS changes or an API token expires. cert-manager exposes Prometheus metrics, including certmanager_certificate_expiration_timestamp_seconds. This alert fires when a certificate has less than 7 days left:&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;alert: CertificateExpiringSoon
expr: (certmanager_certificate_expiration_timestamp_seconds - time()) &amp;lt; 7 * 24 * 3600
for: 15m
labels:
severity: warning
annotations:
summary: "Certificate {{ $labels.name }} expires in under 7 days"&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Treat this as your safety net. If it ever fires, something in your SSL automation broke and needs a human.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common problems and quick fixes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Challenge stuck in pending: check that the domain's DNS points at your Ingress and that port 80 is reachable from the internet.&lt;/li&gt;
&lt;li&gt;Wrong Ingress class: make sure ingressClassName in the solver matches your controller.&lt;/li&gt;
&lt;li&gt;Rate limit errors: use the staging issuer while testing.&lt;/li&gt;
&lt;li&gt;Certificate stays False: run kubectl describe on the Order and Challenge resources. The error message is usually clear.&lt;/li&gt;
&lt;li&gt;Need to force a renewal: cmctl renew app-example-com&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Wrapping up
&lt;/h3&gt;

&lt;p&gt;With cert-manager you declare what you want, and the cluster handles issuing and renewing. That's the point of SSL automation: you set it up once and stop thinking about expiry dates.&lt;/p&gt;

&lt;p&gt;If you're managing certificates across many domains or clients and want OV/EV validation, a warranty and a support team, take a look at what we offer at Certera.&lt;/p&gt;

&lt;p&gt;Have questions or a setup that doesn't fit this guide? Drop a comment and I'll help.&lt;/p&gt;

</description>
      <category>sslautomation</category>
      <category>security</category>
      <category>cybersecurity</category>
    </item>
  </channel>
</rss>
