Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
IT Security Toolsconpot v1.0.0(21.09.2026 um 07:32 Uhr)
IT Security ToolsZircolite v4.0.0(21.09.2026 um 08:27 Uhr)
IT Security NachrichtenWaterPlum Hackers Steal $10.7M in Crypto From IT Workers(21.09.2026 um 08:52 Uhr)
Sicherheitslücken (CVE)Die größte Schwachstelle sitzt am Schreibtisch - kommunal.at(21.09.2026 um 07:36 Uhr)
IT Security NachrichtenIT Security News Hourly Summary 2026-09-21 08h : 6 posts(21.09.2026 um 08:00 Uhr)
IT Security Toolsconpot v1.0.0(21.09.2026 um 07:32 Uhr)
IT Security ToolsZircolite v4.0.0(21.09.2026 um 08:27 Uhr)
IT Security NachrichtenWaterPlum Hackers Steal $10.7M in Crypto From IT Workers(21.09.2026 um 08:52 Uhr)
Sicherheitslücken (CVE)Die größte Schwachstelle sitzt am Schreibtisch - kommunal.at(21.09.2026 um 07:36 Uhr)
IT Security NachrichtenIT Security News Hourly Summary 2026-09-21 08h : 6 posts(21.09.2026 um 08:00 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

Complete Guide: Let's Encrypt SSL with cert-manager and Hetzner DNS on Kubernetes

Reagiere als Erste:r — dein Feedback zählt!

Introduction

This guide walks you through setting up automatic SSL certificates from Let's Encrypt on a Kubernetes cluster using cert-manager with Hetzner DNS for DNS-01 challenge validation. This is the most reliable method for issuing and renewing SSL certificates automatically, especially if you have multiple domains or subdomains.

By the end of this guide, you'll have:

  • Automatic SSL certificate issuance via Let's Encrypt
  • DNS-based validation (no need to expose HTTP validation endpoints)
  • Automatic certificate renewal before expiration
  • HTTPS redirect middleware in Traefik

Prerequisites

  • A Kubernetes cluster (k3s, kubeadm, EKS, etc.)
  • kubectl configured to access your cluster
  • A domain with DNS hosted at Hetzner DNS
  • Helm installed on your local machine
  • Traefik ingress controller (this guide uses Traefik)

Architecture Overview

Here's how the process works:

  1. You create a Certificate resource in Kubernetes
  2. cert-manager reads this and contacts Let's Encrypt
  3. Let's Encrypt returns a DNS challenge token
  4. cert-manager calls the Hetzner webhook with this token
  5. The Hetzner webhook uses the Hetzner DNS API to create a TXT record
  6. Let's Encrypt queries the DNS and validates the TXT record
  7. Let's Encrypt issues the certificate
  8. cert-manager stores it in a Kubernetes Secret
  9. Your Ingress uses this Secret for HTTPS

Step 1: Install cert-manager

cert-manager is the core component that manages certificate lifecycle.

kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.19.2/cert-manager.yaml

Wait for it to be ready:

kubectl wait --for=condition=ready pod -l app.kubernetes.io/instance=cert-manager -n cert-manager --timeout=300s

Verify installation:

kubectl get pods -n cert-manager

You should see three pods running:

  • cert-manager
  • cert-manager-cainjector
  • cert-manager-webhook

Step 2: Create Hetzner DNS API Token

  1. Log in to Hetzner DNS Control Panel
  2. Navigate to "API Tokens" (usually under account settings)
  3. Click "Create new token"
  4. Give it a name like "cert-manager"
  5. Copy the token immediately (you won't see it again)

Important: Keep this token secret. Anyone with this token can modify your DNS records.

Step 3: Install the Hetzner Webhook for cert-manager

The Hetzner webhook is a cert-manager extension that knows how to talk to the Hetzner DNS API.

Add the Helm repository:

helm repo add cert-manager-webhook-hetzner https://vadimkim.github.io/cert-manager-webhook-hetzner
helm repo update

Install the webhook:

helm install cert-manager-webhook-hetzner \
  --namespace cert-manager \
  cert-manager-webhook-hetzner/cert-manager-webhook-hetzner

Verify the webhook pod is running:

kubectl get pods -n cert-manager | grep hetzner

Step 4: Create the Hetzner API Secret

Create a file called hetzner-secret.yaml:

apiVersion: v1
kind: Secret
metadata:
  name: hcloud-api-token-secret
  namespace: cert-manager
type: Opaque
stringData:
  api-key: "your-hetzner-api-token-here"

Important: The key must be named api-key (not token). This is what the webhook expects.

Apply it:

kubectl apply -f hetzner-secret.yaml

Verify the secret was created:

kubectl get secret hcloud-api-token-secret -n cert-manager

Step 5: Verify the Webhook's API Group Registration

Before creating the ClusterIssuer, you need to know the exact API group name that the webhook registered. This is crucial and often a source of problems.

Run this command:

kubectl api-resources | grep hetzner

You should see output like:

hetzner    hetzner.cert-mananger-webhook.noshoes.xyz/v1alpha1    false    ChallengePayload

Note the exact API group name - in this example it's hetzner.cert-mananger-webhook.noshoes.xyz. This is what you'll use in your ClusterIssuer. Pay attention to the spelling - some versions have a typo: "mananger" instead of "manager".

Step 6: Set Up RBAC Permissions

cert-manager needs permission to call the webhook, and the webhook needs permission to read the Hetzner API secret.

Create a file called rbac.yaml:

# Allow webhook to read the Hetzner API token
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: cert-manager-webhook-hetzner-secret-access
  namespace: cert-manager
rules:
- apiGroups: [""]
  resources: ["secrets"]
  resourceNames: ["hcloud-api-token-secret"]
  verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: cert-manager-webhook-hetzner-secret-access
  namespace: cert-manager
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: cert-manager-webhook-hetzner-secret-access
subjects:
- kind: ServiceAccount
  name: cert-manager-webhook-hetzner
  namespace: cert-manager
---
# Allow cert-manager to call the webhook
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cert-manager-webhook-hetzner
rules:
- apiGroups:
  - hetzner.cert-mananger-webhook.noshoes.xyz
  resources:
  - hetzner
  verbs:
  - create
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: cert-manager-webhook-hetzner
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cert-manager-webhook-hetzner
subjects:
- kind: ServiceAccount
  name: cert-manager
  namespace: cert-manager

Important: Replace hetzner.cert-mananger-webhook.noshoes.xyz in the ClusterRole with whatever kubectl api-resources | grep hetzner showed you.

Apply it:

kubectl apply -f rbac.yaml

Step 7: Create the ClusterIssuer

The ClusterIssuer tells cert-manager how to request certificates from Let's Encrypt using Hetzner DNS.

Create a file called cluster-issuer.yaml:

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-dns-hetzner
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: [email protected]
    privateKeySecretRef:
      name: letsencrypt-dns-hetzner-key
    solvers:
      - dns01:
          webhook:
            groupName: hetzner.cert-mananger-webhook.noshoes.xyz
            solverName: hetzner
            config:
              secretName: hcloud-api-token-secret
              zoneName: example.com
              apiUrl: https://dns.hetzner.com/api/v1

Replace:

  • [email protected] with your email
  • groupName with the exact value from kubectl api-resources | grep hetzner
  • zoneName with your root domain (e.g., example.com)

For testing/staging (to avoid Let's Encrypt rate limits), use this server instead:

server: https://acme-staging-v02.api.letsencrypt.org/directory

Apply it:

kubectl apply -f cluster-issuer.yaml

Verify the issuer is ready:

kubectl describe clusterissuer letsencrypt-dns-hetzner

Look for Status: True in the conditions.

Step 8: Create Certificate Resources

Now create a Certificate resource for each domain you want to secure.

Create a file called certificates.yaml:

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: example-tls
  namespace: default
spec:
  secretName: example-tls
  issuerRef:
    name: letsencrypt-dns-hetzner
    kind: ClusterIssuer
  commonName: example.com
  dnsNames:
    - example.com
    - "*.example.com"
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: api-tls
  namespace: default
spec:
  secretName: api-tls
  issuerRef:
    name: letsencrypt-dns-hetzner
    kind: ClusterIssuer
  commonName: api.example.com
  dnsNames:
    - api.example.com

Replace:

  • Namespace with your application's namespace
  • Domain names with your actual domains
  • Certificate names as appropriate

Apply it:

kubectl apply -f certificates.yaml

Step 9: Monitor Certificate Creation

Watch the certificates being created:

kubectl get certificate -A -w

Check detailed status:

kubectl describe certificate example-tls -n default

Watch the challenges:

kubectl get challenge -n default -w

Check the webhook logs to see DNS records being created:

kubectl logs -n cert-manager -l app=cert-manager-webhook-hetzner -f --tail=50

You should see messages like:

Added TXT record result: {"record":{"id":"...","type":"TXT","name":"_acme-challenge.example",...}}
Presented txt record _acme-challenge.example.com.
Delete TXT record result: {"error":{}}

Once the certificate shows Ready: True, the secret is ready to use.

Step 10: Use Certificates in Traefik Ingress

For standard Ingress with Traefik:

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

For Traefik IngressRoute with HTTP-to-HTTPS redirect:

apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: redirect-https
  namespace: default
spec:
  redirectScheme:
    scheme: https
    permanent: true
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: example-http
  namespace: default
spec:
  entryPoints:
    - web
  routes:
    - match: Host(`example.com`)
      kind: Rule
      middlewares:
        - name: redirect-https
          namespace: default
      services:
        - name: my-service
          port: 80
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: example-https
  namespace: default
spec:
  entryPoints:
    - websecure
  routes:
    - match: Host(`example.com`)
      kind: Rule
      services:
        - name: my-service
          port: 80
  tls:
    secretName: example-tls

Troubleshooting Guide

Problem 1: Certificate Stuck in "Pending" State

Symptom: kubectl get certificate shows READY: False for hours

Solution:

Check the certificate details:

kubectl describe certificate example-tls -n default

Look at the events section. The most common causes are listed below.

Problem 2: "Unable to get secret" Error

Error message:

unable to get secret `hcloud-api-token-secret/cert-manager`; secrets "hcloud-api-token-secret" is forbidden

Cause: The RBAC permissions are not set up correctly. The webhook's service account can't read the Hetzner API token secret.

Solution:

  1. Verify the secret exists:
kubectl get secret hcloud-api-token-secret -n cert-manager
  1. Reapply the RBAC rules:
kubectl apply -f rbac.yaml
  1. Make sure the Role has the correct secret name:
rules:
- apiGroups: [""]
  resources: ["secrets"]
  resourceNames: ["hcloud-api-token-secret"]  # Exact match required
  verbs: ["get"]

Problem 3: "Key 'api-key' not found in secret"

Error message:

unable to get api-key from secret `hcloud-api-token-secret`; key "api-key" not found in secret data

Cause: The secret key is named wrong. The webhook expects exactly api-key.

Solution:

Delete and recreate the secret with the correct key name:

kubectl delete secret hcloud-api-token-secret -n cert-manager

Create it again with api-key:

apiVersion: v1
kind: Secret
metadata:
  name: hcloud-api-token-secret
  namespace: cert-manager
type: Opaque
stringData:
  api-key: "your-token-here"

Problem 4: "cannot create resource 'hetzner' is forbidden"

Error message:

User "system:serviceaccount:cert-manager:cert-manager" cannot create resource "hetzner" in API group "hetzner.cert-manager-webhook.noshoes.xyz"

Cause: cert-manager service account doesn't have permission to call the webhook.

Solution:

Reapply the ClusterRole and ClusterRoleBinding:

kubectl apply -f rbac.yaml

Verify the ClusterRoleBinding exists:

kubectl get clusterrolebinding | grep hetzner

Problem 5: "the server could not find the requested resource"

Error message:

the server could not find the requested resource (post hetzner.cert-manager-webhook.noshoes.xyz)

Cause: The groupName in your ClusterIssuer doesn't match what the webhook registered.

Solution:

Check the actual API group:

kubectl api-resources | grep hetzner

This outputs something like:

hetzner    hetzner.cert-mananger-webhook.noshoes.xyz/v1alpha1    false    ChallengePayload

Copy the exact API group (e.g., hetzner.cert-mananger-webhook.noshoes.xyz) and update your ClusterIssuer:

solvers:
  - dns01:
      webhook:
        groupName: hetzner.cert-mananger-webhook.noshoes.xyz  # Use exact match
        solverName: hetzner

Problem 6: The Webhook Has a Typo in the API Group Name

Issue: The webhook registers as hetzner.cert-mananger-webhook.noshoes.xyz (with "mananger" - missing 'e')

Cause: This is a known issue in some versions of the Hetzner webhook Helm chart.

Solution:

You have two options:

Option A (Quickest): Just use the misspelled group name in your ClusterIssuer. It works fine despite the typo:

groupName: hetzner.cert-mananger-webhook.noshoes.xyz

Option B (Proper fix): Reinstall the webhook with a corrected version or use a different webhook implementation that doesn't have this typo.

Problem 7: Challenge Not Being Presented

Symptom: Challenge status shows Presented: false but no errors in logs

Cause: Webhook pod might have crashed or become unhealthy

Solution:

Check webhook health:

kubectl get pods -n cert-manager | grep hetzner

Check pod logs:

kubectl logs -n cert-manager -l app=cert-manager-webhook-hetzner

Restart the webhook:

kubectl rollout restart deployment/cert-manager-webhook-hetzner -n cert-manager

Delete the challenge to trigger a retry:

kubectl delete challenge -n default --all

Problem 8: DNS Record Not Being Created in Hetzner

Symptom: No TXT record appears in Hetzner DNS console

Cause: Usually an invalid API token or zone name mismatch

Solution:

  1. Verify the API token is correct:
kubectl get secret hcloud-api-token-secret -n cert-manager -o jsonpath='{.data.api-key}' | base64 -d
  1. Verify the zone name matches your domain:
zoneName: example.com  # Must match your actual domain
  1. Check the webhook logs for errors:
kubectl logs -n cert-manager -l app=cert-manager-webhook-hetzner -f | grep -i error
  1. Test the API token manually:
curl -H "Auth-API-Token: your-token" https://dns.hetzner.com/api/v1/zones

Problem 9: Certificate Ready but HTTPS Still Shows Old/Invalid Certificate

Cause: Ingress is not using the new secret or old ingress resource is still active

Solution:

  1. Verify the secret exists:
kubectl get secret example-tls -n default
  1. Check the Ingress is referencing it:
kubectl describe ingress example-ingress -n default
  1. If using both Ingress and IngressRoute, delete the old Ingress:
kubectl delete ingress example-ingress -n default
  1. Wait for the load balancer to update (usually 30 seconds to 2 minutes)

Verification Checklist

Once everything is set up, verify with this checklist:

# 1. cert-manager is running
kubectl get pods -n cert-manager

# 2. Webhook is running
kubectl get pods -n cert-manager | grep hetzner

# 3. ClusterIssuer is ready
kubectl describe clusterissuer letsencrypt-dns-hetzner

# 4. Certificate is ready
kubectl get certificate -A

# 5. Secret is created
kubectl get secret example-tls -n default

# 6. Ingress/IngressRoute is configured
kubectl get ingress,ingressroute -A

# 7. DNS records exist
nslookup _acme-challenge.example.com

# 8. HTTPS works
curl -I https://example.com

Automatic Renewal

cert-manager automatically renews certificates 30 days before expiration. You don't need to do anything.

Monitor renewal status:

kubectl describe certificate example-tls -n default

Check cert-manager logs for renewal activity:

kubectl logs -n cert-manager -l app=cert-manager | grep -i renew

Best Practices

  1. Use ClusterIssuer, not Issuer - ClusterIssuer is namespace-agnostic and reusable
  2. Use production Let's Encrypt - Only use staging for testing to avoid rate limits
  3. Keep API token secret - Don't commit it to git, use sealed secrets or external secret management
  4. Monitor certificate expiration - Set up alerts for certificates not renewing
  5. Use separate secrets per domain - Makes it easier to rotate certs individually
  6. Test in staging first - Use the Let's Encrypt staging server before production
  7. Keep webhook updated - Regularly update the webhook Helm chart for security patches

Conclusion

You now have a fully automated SSL certificate system that:

  • Issues certificates automatically from Let's Encrypt
  • Validates using DNS (no need for HTTP validation)
  • Renews certificates automatically before expiration
  • Works across all your subdomains under one root domain
  • Integrates seamlessly with Traefik

If you encounter issues, the troubleshooting guide above covers the most common problems and their solutions.

Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Complete Guide: Let's Encrypt SSL with cert-manager and Hetzner DNS on Kubernetes

Thematisch verwandte Begriffe: Complete, Guide, Lets, Encrypt · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-94030 | A security vulnerability has been detected in SerenityOS up to 3d83e4509…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel Rechts: nächster Artikel unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick