Management > Private CA > ACME Certificate Renewal Guide (cert-manager)
The Private CA service supports the automatic certificate management environment (ACME) protocol, enabling automated issuance and renewal of certificates. Cert-manager is a Kubernetes-native controller that automates certificate management, allowing for issuance and periodic renewal without manual intervention.
This guide explains how to use a Private CA ACME server with cert-manager to issue and automatically renew certificates within a Kubernetes environment.
Notice
Notice
To manage certificates using Certbot or acme.sh in a standard server environment, refer to the ACME Certificate Renewal Guide (Certbot, acme.sh).
Before you can begin issuing certificates through ACME, you need to prepare the following:
The base certificate acts as a "template" that the ACME server references for automatic renewal.
In the Private CA console, verify the following information:
https://kr1-pca.api.nhncloudservice.com/acme/v2.0/cert/{certId}/directoryIn a Kubernetes environment, you can use cert-manager to automatically issue and renew certificates.
Install cert-manager on your Kubernetes cluster.
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.19.1/cert-manager.yaml
Verify Installation
kubectl get pods -n cert-manager
The HTTP-01 Challenge method requires an Ingress Controller.
Install ingress-nginx
helm upgrade --install ingress-nginx ingress-nginx \
--repo https://kubernetes.github.io/ingress-nginx \
--namespace ingress-nginx --create-namespace
Verify Installation
kubectl get pods -n ingress-nginx
Create a Kubernetes Secret containing the External Account Binding (EAB) information for ACME authentication.
kubectl create secret generic acme-eab-secret \
--from-literal=secret='YOUR_ACME_TOKEN_HMAC_KEY' \
--namespace default
Caution
-YOUR_ACME_TOKEN_HMAC_KEYmust be replaced with the ACME HMAC key issued in Private CA Console > ACME Management.
- The EAB Secret is sensitive information and must be managed securely.
- The namespace where the Secret is generated must be the same as the namespace where the Issuer will be located.
An Issuer is a cert-manager resource that defines the Certificate Authority (CA) for certificate issuance. You can choose between an Issuer, which operates at the namespace level, and a ClusterIssuer, which is available cluster-wide.
You can choose to use either the Ingress or Gateway API as the HTTP-01 Challenge validation method.
An example of an Issuer configuration when using an Ingress Controller.
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: my-acme-issuer-example-com
namespace: default
spec:
acme:
server: https://kr1-pca.api.nhncloudservice.com/acme/v2.0/cert/{certId}/directory
# --- Start adding EAB settings --- --- External Account Binding.
externalAccountBinding:
# keyID: "9998617a-2799-41dd-a75b-ff093f5472c7" # Example: "kid-1", "my-account-id", etc. ID received from the server.
keySecretRef:
name: acme-eab-secret # Secret name created above.
key: secret # Key name inside the Secret
# --- End of adding EAB settings
privateKeySecretRef:
name: my-acme-account-key-example-com # Generated automatically by cert-manager.
skipTLSVerify: true # required as this is a private certificate server
solvers:
- http01:
ingress:
class: nginx
An Issuer configuration example for use with the Kubernetes Gateway API.
Prerequisites
kubectl apply -f "https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/standard-install.yaml"
If you installed with Helm
helm upgrade --install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--set "extraArgs={--enable-gateway-api}"
If you installed with Manifest
kubectl edit deployment cert-manager -n cert-manager
Add the following:
spec:
template:
spec:
containers:
- name: cert-manager
args:
- --enable-gateway-api
# Add a Helm repository
helm repo add traefik https://traefik.github.io/charts
# Install Traefik
helm install traefik traefik/traefik -n traefik --create-namespace -f traefik-values.yaml
traefik-values.yaml
experimental:
kubernetesGateway:
enabled: true
providers:
kubernetesGateway:
enabled: true
# Automatically Generate a GatewayClass
gatewayClass: enabled: true name: traefik
# Disable Automatic Creation of Gateway (create them manually)
gateway: enabled: false
# Set Internal Port to 1024 or Higher
ports:
web:
port: 8000 # internal port
exposedPort: 80 # external port
websecure:
port: 8443 # internalport
exposedPort: 443 # external port
service:
type: LoadBalancer
ingressRoute:
dashboard:
enabled: true
logs:
general:
level: INFO
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: traefik
namespace: default
spec:
gatewayClassName: traefik
listeners:
# HTTP Listener (port 80)
- name: http
protocol: HTTP
port: 8000 # Matches Traefik's web entryPoint
allowedRoutes:
namespaces:
from: All
# HTTPS Listener (port 443)
- name: https
protocol: HTTPS
port: 8443 # Matches Traefik's websecure entryPoint
allowedRoutes:
namespaces:
from: All
tls:
mode: Terminate
certificateRefs:
- kind: Secret
name: test-server-tls-example-com # TLS Secret name
namespace: default
Apply Gateway.
kubectl apply -f gateway.yml
Configure an Issuer with Gateway API
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: my-acme-issuer-example-com
namespace: default
spec:
acme:
server: https://kr1-pca.api.nhncloudservice.com/acme/v2.0/cert/{certId}/directory
# --- Start adding EAB settings --- --- External Account Binding.
externalAccountBinding:
keyID: "9998617a-2799-41dd-a75b-ff093f5472c7"
keySecretRef:
name: acme-eab-secret
key: secret
# --- End of adding EAB settings ---.
privateKeySecretRef:
name: my-acme-account-key-example-com
skipTLSVerify: true
solvers:
- http01:
gatewayHTTPRoute:
parentRefs:
- name: traefik
namespace: default
kind: Gateway
Notice
Using the Gateway API, cert-manager automatically provisions HTTPRoute, Service, and Pod resources to address the challenge. Please note that the Gateway resource must be created in advance.
| Fields | Description | Required |
|------|------|------|------|
| spec.acme.server | ACME Directory URL. Contains the Private CA Base certificate ID. | O |
| spec.acme.externalAccountBinding.keyID | ACME Token ID. Enter the value issued by the Private CA console. | O |
| spec.acme . externalAccountBinding.keySecretRef.name | Secret name where the EAB HMAC key is stored. | O |
| spec.acme.externalAccountBinding.keySecretRef.key | Key name inside the Secret. | O |
| spec.acme.privateKeySecretRef.name | Name of the Secret to store the ACME account private key. It is automatically generated by cert-manager. | O |
| spec.acme.skipTLSVerify | Whether to skip TLS certificate verification. Set to truein private certificate environments. | X |
| spec.acme.solvers | ACME Challenge verification method. Choose from http01, dns01. | O |
| spec.acme.solvers.http01.ingress.class | Ingress method: Ingress Controller class name (e.g. nginx). | X |
| spec.acme.solvers.http01.gatewayHTTPRoute.parentRefs | Gateway API method: Gateway resource information to reference. | X |
Apply the Issuer resource.
kubectl apply -f my-acme-issuer-example-com.yml
Check the Issuer status.
kubectl get issuer -n default
kubectl describe issuers.cert-manager.io my-acme-issuer-example-com -n default
If the Ready status shows True, it's successfully enrolled.
Notice
dns01 solver requires DNS provider setup.skipTLSVerify: true option is required if the Private CA server uses private certificates.Before creating a Certificate, you must ensure that your environment is properly configured for a successful HTTP-01 challenge.
The HTTP-01 challenge requires cert-manager to send an HTTP request to your domain to verify ownership. In test environments where the domain is not registered in public DNS, you must configure hostAliases in the cert-manager Deployment to map the domain to the IP address of your Kubernetes cluster.
kubectl edit deployment cert-manager -n cert-manager
Add the following content under spec.template.spec:
spec:
template:
spec:
hostAliases:
- ip: "114.110.145.74" # External IP of the Kubernetes cluster (e.g., NKS Floating IP, LoadBalancer IP)
hostnames:
- "example.com" # Any domain you set in the certificate
- "sub.example.com"
This configuration ensures that requests from the cert-manager Pod to the domain are routed to the specified IP address.
Caution
(commonName and dnsNames) you set in the certificate to the hostnames.!!! tip "Notice" When using the Gateway API, cert-manager automatically creates the following resources to handle the challenge process.
1. **HTTPRoute**: For each domain, create an HTTPRoute to handle the Challenge path (`/.well-known/acme/v2.0-challenge/`).
2. **Service**: Create a Service to handle the Challenge.
3. **Pod**: Create a Pod to respond to the challenge.
These resources are automatically deleted after the Challenge completes.
The Certificate resource defines the properties of the certificate to be issued.
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: test-server-cert-example-com
namespace: default
spec:
secretName: test-server-tls-example-com # Secret where the certificate will be stored
renewBefore: 360h # 15d
commonName: example.com
dnsNames:
- example.com
- sub.example.com
# Issuer references are always required.
issuerRef:
name: my-acme-issuer-example-com
# We can reference ClusterIssuers by changing the kind here.
# The default value is Issuer(i.e. a locally namespaced Issuer)
kind: Issuer
| Fields | Description | Required |
|---|---|---|
spec.secretName |
Secret name to store the issued certificate and private key. | O |
spec.renewBefore |
Time to start renewal before the certificate expires. Example: 360h(15 days). |
X |
spec.commonName |
Common Name (CN) of the certificate. Must match the CN of the base certificate. | O |
spec.dnsNames |
Subject Alternative Names (SAN) of the certificate. Must match the SAN of the base certificate. | X |
spec.issuerRef.name |
Issuer or ClusterIssuer name to use. | O |
spec.issuerRef.kind |
Issuer type. Issuer or ClusterIssuer. |
X |
Apply a Certificate resource.
kubectl apply -f test-server-cert-example-com.yml
Check the certificate status.
kubectl get certificate -n default
kubectl describe certificate test-server-cert-example-com -n default
If the certificate issuance is successful, the Ready status is displayed as True.
View Detailed Certificate Issuance Progress
# Verify CertificateRequest (Create Order)
kubectl get certificaterequest -n default
# Confirm an Order (Fulfillment)
kubectl get order -n default
# Check Challenge (Challenge Verification)
kubectl get challenge -n default
Caution
in commonName and dnsNamesmust exactly match the CN and SAN set in the Base certificate.Once the certificate is successfully issued, it will be stored in the designated Secret.
kubectl get secret test-server-tls-example-com -n default
kubectl describe secret test-server-tls-example-com -n default
View Certificate Details
kubectl get secret test-server-tls-example-com -n default -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -text
Verify Certificate Validity
kubectl get secret test-server-tls-example-com -n default -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -dates
The issued certificate Secret has the following structure:
apiVersion: v1
kind: Secret
type: kubernetes.io/tls
data:
tls.crt: <base64-encoded certificate>
tls.key: <base64-encoded private key>
ca.crt: <base64-encoded CA certificate chain>
cert-manager automatically performs renewals when a certificate is nearing expiration.
renewBefore field is up to the expiration date.You can adjust when renewals start by modifying the renewBefore field in the certificate resource.
spec:
renewBefore: 720h # Start renewal 30 days ago
You can manually renew certificate resources as needed.
Method 1: Use the cmctl command
# Install cmctl (macOS)
brew install cmctl
# Renew Certificate
cmctl renew test-server-cert-example-com -n default
Method 2: Use kubectl annotations
# Trigger Manual Renewals with Certificate Annotations
kubectl annotate certificate test-server-cert-example-com -n default \
cert-manager.io/issue-temporary-certificate="true" --overwrite
Method 3: Regenerate a certificate resource
kubectl delete certificate test-server-cert-example-com -n default
kubectl apply -f test-server-cert-example-com.yml
Notice
renewBefore value too short, as you risk letting the certificate expire.To test that auto-renewal is working properly, you can use the following methods.
Method 1: Wait until the auto-renewal time
You can quickly verify the renewal process by setting a short TTL for the Base certificate and a smaller renewBefore value in the Certificate resource.
Example settings - Base certificate TTL: 2 days - Certificate renewBefore: 24h (Start renewal 24 hours before expiration)
spec:
renewBefore: 24h # Start renewal 24 hours before expiration
```
**Method 2: Test immediately with manual renewal**
You can use the manual update method above (cmctl or kubectl annotation) to test the update process out of the box.
```bash
# Renewal with cmctl
cmctl renew test-server-cert-example-com -n default
The issued certificate is stored as a Kubernetes Secret, allowing you to mount it in your application in various ways.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
namespace: default
spec:
ingressClassName: nginx
tls:
- hosts:
- example.com
- sub.example.com
secretName: test-server-tls-example-com # Secret rules created by the certificate
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-service
port:
number: 80
apiVersion: v1
kind: Pod
metadata:
name: my-app
namespace: default
spec:
containers:
- name: app
image: my-app:latest
volumeMounts:
- name: tls-certs
mountPath: /etc/tls
readOnly: true
volumes:
- name: tls-certs
secret:
secretName: test-server-tls-example-com
Mounted certificates are available at the following paths:
/etc/tls/tls.crt: Certificates/etc/tls/tls.key: Private key/etc/tls/ca.crt: CA Certificate Chainkubectl describe issuer my-acme-issuer-example-com -n default
Ready status is True.Verify that your ACME account registration was successful.
Check Certificate Satus
kubectl describe certificate test-server-cert-example-com -n default
In the Events section, check for error messages.
Verify ertificateRequest
kubectl get certificaterequest -n default
kubectl describe certificaterequest <request-name> -n default
kubectl get challenge -n default
kubectl describe challenge <challenge-name> -n default
| Error Message | Cause | Solution |
|---|---|---|
Failed to register ACME account |
EAB information is incorrect. | Check the keyID andSecret values. |
challenge failed |
Challenge verification failed. | Check the Ingress settings and network accessibility. |
domain not allowed |
Requested a domain that is not in the base certificate. | Match the commonName and dnsNamesof the certificate to the base certificate. |
secret not found |
EAB Secret not found. | Verify that the Secret was created in the correct namespace `. |
x509: certificate signed by unknown authority| TLS verification failed. | SetskipTLSVerify: true`for the Issuer. |
You can diagnose the problem by checking the detailed logs in cert-manager.
kubectl logs -n cert-manager deployment/cert-manager -f
Caution
renewBefore value must be carefully adjusted to account for the ACME server's Rate Limit policy.Through the ACME Directory URL (/directory) provided by the private CA, the ACME client automatically gets all the endpoint information it needs.
The ACME protocol workflow is fully automated by the client, requiring only the Directory URL from the user.
For more information about the ACME protocol, see RFC 8555.