Using Custom Certificates
Starting from this release, you can configure DU-specific SSL/TLS certificates for your Self-hosted Private Cloud Director deployment, issued and automatically renewed by cert-manager. Previously, the system only used a single self-signed wildcard certificate generated during the deployment process.
Overview
By default, the system generates a self-signed wildcard certificate during installation and shares it across every DU namespace. You can now:
Configure
cert-managerto issue a certificate per DU namespace (for example, via Let's Encrypt)Revert back to the automatically generated self-signed wildcard certificate at any time
Platform9 does not generate or accept a certificate/key file directly (there is no "bring your own .crt/.key" upload flow) — cert-manager, configured with an issuer of your choice, is what actually issues the certificate. airctl only orchestrates the switch: it validates your issuer, requests a certificate per DU namespace, and manages the internal replication labels so the certificate isn't silently reverted to the self-signed wildcard on the next label sync.
airctl provision-certs replaces the previous airctl renew-certs command, which has been removed as of this release. If you are running an earlier release where provision-certs is not available, see Configuring DU-Specific Certificates Manually instead — it walks through the same cert-manager setup by hand.
Prerequisites
Before running airctl provision-certs, cert-manager must already be installed in the cluster with a working issuer — provision-certs does not install cert-manager or create the issuer for you.
cert-manager is installed in the cluster
kubectl get pods -n cert-manager
A ClusterIssuer exists and is Ready
kubectl get clusterissuer <name>
A DNS-01 solver is configured on the issuer if the node has no publicly reachable port 80
kubectl get svc -n ingress-nginx -o wide — if only 443/444/10254 are exposed, ACME HTTP-01 cannot work and the issuer must use a DNS-01 solver (for example, Route53) instead
The example below sets up a ClusterIssuer using Let's Encrypt with a Route53 DNS-01 solver — the common choice for nodes without inbound port 80 reachability. Any Ready cert-manager ClusterIssuer works; the solver and ACME server are entirely up to you.
Create the Route53 credentials secret
Only the AWS secret access key is treated as sensitive — the access key ID is a plain field on the ClusterIssuer spec.
kubectl create secret generic route53-credentials-secret -n cert-manager \
--from-literal=secret-access-key='<AWS_SECRET_ACCESS_KEY>' \
--dry-run=client -o yaml | kubectl apply -f -Create the ClusterIssuer (staging first)
Validate against Let's Encrypt's staging environment before spending a production rate-limit slot.
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: <cluster-issuer-name>
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory
email: <your-email>
privateKeySecretRef:
name: <cluster-issuer-name>-account-key
solvers:
- dns01:
route53:
region: <aws-region>
hostedZoneID: <hosted-zone-id>
accessKeyID: <aws-access-key-id>
secretAccessKeySecretRef:
name: route53-credentials-secret
key: secret-access-keykubectl apply -f clusterissuer.yaml
kubectl get clusterissuer <cluster-issuer-name>kubectl patch --type=merge on the solvers list replaces the entire list entry, not just the fields you pass — patching in only region/hostedZoneID can silently wipe accessKeyID and secretAccessKeySecretRef. Always kubectl apply -f the full ClusterIssuer spec when changing anything inside solvers.
Validate with a throwaway certificate
Before running provision-certs, confirm the DNS-01 → Route53 → issuance pipeline works end-to-end with a disposable certificate that doesn't share a secret name with anything real:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: route53-dns01-test
namespace: <any-namespace>
spec:
commonName: dns01test.<your-domain>
dnsNames:
- dns01test.<your-domain>
issuerRef:
name: <cluster-issuer-name>
kind: ClusterIssuer
secretName: route53-dns01-test-tlskubectl apply -f test-cert.yaml
kubectl get certificate route53-dns01-test -n <any-namespace> -wIssuance typically takes a couple of minutes end to end — the ACME challenge usually reports valid well before the Certificate itself flips to Ready; give it time past that point before assuming it's stuck. Clean up once confirmed:
kubectl delete certificate route53-dns01-test -n <any-namespace>
kubectl delete secret route53-dns01-test-tls -n <any-namespace>Configure DU-Specific Certificates via cert-manager
Once you have a Ready ClusterIssuer, hand it to airctl to provision certificates for every DU namespace:
This command:
Validates that
<cluster-issuer-name>exists and isReady— it fails fast, before touching any namespace, if the issuer is missing or not ready.Removes the internal
cert-manager-tlsreplication label from each DU namespace, so the shared self-signed wildcard certificate is not re-synced over the new certificate.Creates (or updates) a
cert-managerCertificateresource in every DU namespace, requesting<namespace>.<domain>from the issuer, and waits for it to becomeReady.Restarts any deployment in the namespace that mounts the certificate secret, so the new certificate is picked up immediately.
Persists
clusterIssuerNameto/opt/pf9/airctl/conf/airctl-config.yaml, so subsequentairctl upgraderuns know this namespace's certificate is managed bycert-managerand skip overwriting it with the self-signed wildcard cert.
Renewal after this point is handled entirely by cert-manager — no further manual steps are needed.
Switch from staging to production
Once staging issuance is proven end-to-end, re-apply the ClusterIssuer with acme.server pointed at the production endpoint (https://acme-v02.api.letsencrypt.org/directory) — always a full apply, never a partial patch (see the solvers warning above). cert-manager does not proactively reissue an already-Ready certificate just because the issuer's server field changed, so re-run:
Verify the certificate is actually being served
A Ready certificate only means cert-manager wrote the secret — confirm the ingress is serving it, and confirm real trust (not curl -k, which skips verification and proves nothing):
Staging certificates only verify successfully on a host that explicitly trusts the Let's Encrypt staging root — that's expected and not a sign anything is broken.
Reverting to the Self-Signed Certificate
To restore the automatically generated self-signed wildcard certificate at any time, run:
This command:
Deletes the
cert-managerCertificateresource for each DU namespace.Restores the original self-signed certificate and key directly into each namespace's certificate secret.
Re-adds the
cert-manager-tlsreplication label to each namespace, so it resumes receiving the shared self-signed wildcard certificate.Restarts any deployment that mounts the certificate secret.
Clears
clusterIssuerNamefrom/opt/pf9/airctl/conf/airctl-config.yaml.
Last updated
Was this helpful?
