Domain and TLS
Set up the NGINX ingress controller, DNS record, and cert-manager issuer the platform needs before the Helm install.
Prepare the cluster entry point: an NGINX ingress controller, a DNS record for the platform domain, and a cert-manager issuer that requests Let’s Encrypt certificates for the platform’s ingresses.
Before You Begin
kubectlandhelmaccess to the cluster, and theguardiannamespace (kubectl create namespace guardian)- A clone of the deployment repository for the helper scripts
- A domain you control, such as
guardian.example.com, and access to its DNS zone - An email address for Let’s Encrypt expiry notices
- Inbound port 80 from the internet to the ingress controller, which the Let’s Encrypt HTTP-01 challenge uses
Without public HTTP-01, such as in an air-gapped cluster or with a private CA, skip Steps 3 and 4: create the TLS secret named by each ingress’s tls[].secretName yourself with kubectl create secret tls (guardian-tls in Chart Values, <env>-tls-secret from guardctl), and remove the cert-manager.io/issuer annotations from values.yaml.
All four platform services share the one domain under different paths. Auth0 and Microsoft Entra ID need no extra domain. Keycloak can run on the same domain under /keycloak or on its own domain, such as keycloak.example.com; see Keycloak.
Step 1: Install the NGINX Ingress Controller
Skip this step if the cluster already runs ingress-nginx. From the deployment repository root, run scripts/nginx.sh:
./scripts/nginx.sh # installs into ingress-nginx; change with --namespaceThe script also sets the Azure load balancer health probe path to /healthz. On AKS, set the service.beta.kubernetes.io/azure-load-balancer-health-probe-request-path annotation to /healthz on the controller service if you install the controller another way.
Step 2: Create the DNS Record
Find the controller’s external address in the EXTERNAL-IP column:
kubectl get svc -n ingress-nginx ingress-nginx-controllerCreate a record for the platform domain that points at that address:
| Cloud | EXTERNAL-IP shows | Record type |
|---|---|---|
| EKS | A hostname, such as xxx.elb.amazonaws.com | CNAME |
| AKS | An IP address | A |
| GKE | An IP address | A |
If Keycloak runs on its own domain, add a second record of the same type for it.
Create the record now: the certificate request at install fails until the domain resolves to the controller. Confirm it resolves before you install:
dig +short guardian.example.comStep 3: Install cert-manager
Skip this step if the cluster already runs cert-manager. Run scripts/cert-issuer.sh, which installs cert-manager with its CRDs:
./scripts/cert-issuer.sh # installs into cert-manager; change with --namespaceStep 4: Create the Let’s Encrypt Issuer
The platform’s ingresses use a namespace-scoped Issuer named letsencrypt-prod in the release namespace:
kubectl apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: letsencrypt-prod
namespace: guardian
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: <your-email> # Let's Encrypt expiry notices
privateKeySecretRef:
name: letsencrypt-production
solvers:
- http01:
ingress:
ingressClassName: nginx
EOFTo share one issuer across namespaces, create a ClusterIssuer instead: set kind: ClusterIssuer and remove metadata.namespace. Then use the cert-manager.io/cluster-issuer annotation in Step 5 in place of cert-manager.io/issuer.
Step 5: Point the Ingresses at the Issuer
The ingress blocks in Chart Values already name the letsencrypt-prod Issuer and share one guardian-tls secret across all four services. If you renamed the Issuer or created a ClusterIssuer, match the annotation on each. cert-manager requests the certificate when the chart is installed and renews it before it expires.
If you enable the Gateway Stack ingress, set gateway-stack.ingress.className: nginx and add DNS records for its hosts that aren’t the platform domain. Add cert-manager.io/issuer: "letsencrypt-prod" under gateway-stack.ingress.annotations, or set gateway-stack.ingress.certManager.clusterIssuer to the ClusterIssuer name. The Gateway Stack names its own TLS secrets.
Verify
After the Helm Deployment, check the certificate in the release namespace. It is named after the secretName in your values (guardctl uses <env>-tls-secret):
kubectl get certificate -n guardianNAME READY SECRET AGE
guardian-tls True guardian-tls 2mThen open https://guardian.example.com and confirm the browser trusts the certificate.
Troubleshooting
If READY stays False, read the events in kubectl describe certificate guardian-tls -n guardian.
| Symptom | Likely cause | Resolution |
|---|---|---|
Issuer letsencrypt-prod not found | Issuer in another namespace, or annotation kind mismatch | Create the Issuer in the release namespace, or match issuer vs cluster-issuer to the kind you created |
| HTTP-01 challenge pending or failing | DNS not propagated, or port 80 not reachable | Confirm dig returns the controller address and the controller accepts traffic on port 80 |
| Let’s Encrypt rate limit error | Too many certificate requests for the domain | Wait for the limit to reset; test repeated installs against the Let’s Encrypt staging server |
| No Certificate resource exists | cert-manager not running, or ingress lacks the annotation | Check kubectl get pods -n cert-manager and the ingress annotations in values.yaml |
Related
- Helm Deployment — installing the chart once the domain and issuer are ready
- Chart Values — the full ingress block for each service
- Keycloak — the Keycloak ingress host and path
- Prerequisites — the other cluster and cloud resources to prepare