Skip to content

Wave 4 — Issuers

cert-manager (wave 2) is installed. This wave only adds issuers: who is allowed to sign a Certificate.

You almost always want one of these for day one, not neither.

letsencrypt (public names)

Let's Encrypt ClusterIssuer using ACME HTTP-01 and ingress class nginx. Challenge type: HTTP-01.

Must change: email in values/letsencrypt/issuer.yaml. The template server is already the staging ACME directory. After a test Ingress on a public hostname reaches Ready, switch server to https://acme-v02.api.letsencrypt.org/directory and sync. There is one ClusterIssuer named letsencrypt — do not invent letsencrypt-staging. Staging certs are not trusted by browsers; that is expected.

HTTP-01 means: Let's Encrypt connects to http://<name>/.well-known/acme-challenge/... and that must hit this ingress VIP on port 80. On a typical homelab that is a WAN port-forward (or 1:1 NAT) of 80 → 10.0.0.30, not a node and not the API VIP. The name must already resolve in public DNS. A LAN-only name will sit in Pending forever.

Every public Ingress needs:

acme.cert-manager.io/http01-edit-in-place: "true"
cert-manager.io/issue-temporary-certificate: "true"

Without edit-in-place, cert-manager spins up a separate solver Ingress. That object is not the LoadBalancer that owns the MetalLB ingress /32, and F5 nginx will not serve the challenge on the same host as your real Ingress. The challenge never reaches .30. Edit-in-place patches your Ingress instead. The temporary cert keeps nginx from ignoring the host while the secret is still empty. Details and a full example: day-2.

Skip if you will not publish names. Delete applications/letsencrypt.yaml.

Validation

Do not create a public Ingress until:

kubectl get clusterissuer letsencrypt
kubectl describe clusterissuer letsencrypt
# Ready=True. If ACME registration failed, the email is still you@example.com or server is the wrong directory.

HTTP-01 also needs wave 3 EXTERNAL-IP and WAN 80 → that VIP. A Ready issuer with no public DNS is not enough to issue.

step-issuer (LAN names)

This is optional in the Application list and not optional if you want https://grafana.k8s.home.example.com without a browser warning.

The step-issuer chart is only the controller (cert-manager tutorial). The StepClusterIssuer is raw YAML in values/step-issuer/manifests/, applied as a third Argo source next to the chart (Application sources). It points at Step-CA outside the cluster. The CA, provisioner password, caBundle, client trust, and the correct Ingress annotations are all on the Step-CA page — read that before filling YAML.

Must change in values/step-issuer/manifests/issuer.yaml:

Field Where it comes from
spec.url https://<unraid-or-ca-ip>:9005
spec.caBundle base64 -w0 < root_ca.crt
provisioner.name Same as step ca init --provisioner
provisioner.kid step ca provisioner list
Secret step-issuer-provisioner-password Seal; do not commit plaintext

Skip if you are Let's Encrypt-only. Delete applications/step-issuer.yaml.

Validation

Do not add a LAN Ingress (or first app) until:

kubectl -n step-issuer get deploy   # Ready
kubectl get stepclusterissuer       # Ready=True
curl -vk https://10.0.0.2:9005/health   # your CA; timeout = pods cannot reach Unraid

A Ready issuer with no Certificate objects is normal until you create an Ingress. If the issuer is not Ready, fix url / caBundle / provisioner Secret — a later Certificate will sit in Issuing.

After the first Ingress: kubectl get certificate -AReady=True.