KCert is a simple alternative to cert-manager:
- Deploys with around 100 lines of yaml (vs. thousands of lines for cert-manager)
- Does not create or need any CRDs (Custom Resource Definitions) to operate
- Runs a single service in your cluster, isolated in its own namespace
- KCert runs as a single-replica deployment in your cluster.
- By default, an ingress is managed to route
/.well-known/acme-challenge/
requests to the kcert service for HTTP-01 challenges. - Alternatively, KCert now supports DNS-01 challenges with AWS Route53 and Cloudflare, which may not require managing a public-facing ingress for challenges.
- Service provides a web UI for basic information and configuration details.
- Checks for certificates needing renewal every 6 hours.
- Automatically renews certificates with less than 30 days of validity.
- Watches for created and updated ingresses in the cluster.
- Automatically creates certificates for ingresses with the
kcert.dev/ingress=managed
label.
First, add the Helm repo with: helm repo add nabsul https://nabsul.github.io/helm
.
Then install with the following command (filling in your details):
kubectl create ns kcert
helm install kcert nabsul/kcert -n kcert --debug --set acmeTermsAccepted=true,acmeEmail=[YOUR EMAIL]
Note: This defaults to running KCert against Let's Encrypt's staging environment. After you've tested against staging, you can switch to production with:
helm install kcert nabsul/kcert -n kcert --debug --set acmeTermsAccepted=true,acmeEmail=[YOUR EMAIL],acmeDirUrl=https://acme-v02.api.letsencrypt.org/directory
For setting up SMTP email notifications and other parameters, please check the charts/kcert/values.yaml
file and set the values under smtp
accordingly.
The SMTP password must be stored in a secret. If you stick with the defaults, you can simply create that secret with the following command:
kubectl create secret -n [YOUR NAMESPACE] generic kcert-smpt-secret --from-literal=password=[YOUR PASSWORD]
KCert automatically looks for ingresses that reference a certicate.
If that certificate doesn't exist, it will create it (and renew it).
KCert only monitors ingresses with the kcert.dev/ingress: "managed"
label.
You can either create your own ingress manually, or use the kcert-ingress
chart:
helm install myingress1 nabsul/kcert-ingress -n kcert --debug --set name=[INGRESS_NAME],host=[DOMAIN],service=[SERVICE_NAME],port=[SERVICE_PORT]
KCert can create TLS certificates based on definitions found in Kubernetes ConfigMap
resources. This is useful if you need a certificate for a service that doesn't have an Ingress, or if you prefer to manage certificate definitions separately.
Key Fields:
data.hosts
: This field is required and triggers KCert to process the ConfigMap. It should contain a comma-separated list of hostnames to be included in the certificate (e.g.,service.example.com,api.example.com
).metadata.name
: The name of the ConfigMap resource will be used as the name for the generated KubernetesSecret
containing the TLS certificate and private key.
Discovery Process:
KCert discovers ConfigMaps that request TLS certificates based on a configurable label. The discovery is controlled by two environment variables:
KCERT_CONFIGMAP_WATCH_LABEL_KEY
: Defines the key of the label KCert will look for on ConfigMaps.- Default:
kcert.dev/configmap
- Default:
KCERT_CONFIGMAP_WATCH_LABEL_VALUE
: Defines the expected value for the label identified byKCERT_CONFIGMAP_WATCH_LABEL_KEY
.- Default:
""
(empty string)
- Default:
Matching Logic:
-
If
KCERT_CONFIGMAP_WATCH_LABEL_VALUE
is an empty string (default): KCert will process any ConfigMap that has a label key matching the value ofKCERT_CONFIGMAP_WATCH_LABEL_KEY
(e.g.,kcert.dev/configmap
), regardless of that label's value. The mere presence of the key is sufficient. -
If
KCERT_CONFIGMAP_WATCH_LABEL_VALUE
is set to a specific string (e.g.,"managed"
,"true"
): KCert will process only those ConfigMaps that have a label where the key matchesKCERT_CONFIGMAP_WATCH_LABEL_KEY
AND the value of that label exactly matchesKCERT_CONFIGMAP_WATCH_LABEL_VALUE
. -
If
NamespaceConstraints
are active (see "Namespace-constrained installations" section), the ConfigMap must reside in one of the specified namespaces.
Example: ConfigMap for service.example.com
This example demonstrates a ConfigMap that KCert would process.
Assuming default settings (KCERT_CONFIGMAP_WATCH_LABEL_KEY="kcert.dev/configmap"
and KCERT_CONFIGMAP_WATCH_LABEL_VALUE=""
), the presence of the kcert.dev/configmap
label (with any value, here "active") is sufficient.
apiVersion: v1
kind: ConfigMap
metadata:
name: my-service-tls # This name will be used for the resulting TLS Secret
namespace: my-app-namespace # Ensure this namespace is monitored by KCert
labels:
kcert.dev/configmap: "active" # Processed if KCERT_CONFIGMAP_WATCH_LABEL_VALUE is empty (default)
# or if KCERT_CONFIGMAP_WATCH_LABEL_VALUE is "active".
# The key can be changed via KCERT_CONFIGMAP_WATCH_LABEL_KEY.
data:
hosts: "service.example.com,service.internal.example.com"
# Optional: acmeRenewDays: "15" (Override global renewal threshold for this cert)
# Optional: acmeChallengeType: "dns-01" (Override global preferred challenge type for this cert)
KCert will detect this ConfigMap and attempt to create (or renew) a TLS Secret named my-service-tls
in the my-app-namespace
namespace, containing a certificate for service.example.com
and service.internal.example.com
.
The kcert-configmap
Helm chart can be used to create such ConfigMaps. You would need to ensure the chart allows setting the required label or customize its output.
# Example using helm, assuming the chart supports setting this label:
helm install my-cert-def nabsul/kcert-configmap -n [TARGET_NAMESPACE] \
--set name=my-service-tls \
--set hosts="service.example.com,service.internal.example.com" \
--set labels.kcert\.dev/configmap=active
# Adjust label key/value according to your KCert configuration and the chart's actual values structure.
Replace [TARGET_NAMESPACE]
with the namespace where you want the ConfigMap and the resulting Secret to reside.
Troubleshooting: If KCert doesn't seem to be processing your ConfigMap:
- Verify the
data.hosts
key exists and is correctly formatted. - Ensure the ConfigMap has the label that KCert is configured to watch for, based on the
KCERT_CONFIGMAP_WATCH_LABEL_KEY
andKCERT_CONFIGMAP_WATCH_LABEL_VALUE
environment variable settings. - Check KCert's logs. Enabling Debug logging (e.g., by setting the environment variable
Logging__LogLevel__KCert
toDebug
for the KCert deployment) will provide more detailed information. - Ensure the ConfigMap is in a namespace monitored by KCert, especially if
NamespaceConstraints
are used.
If you are using Rancher clusters and are assigned a specific namespace without access to cluster-wide resources, it is possible to instruct KCert to query only a list of namespaces.
To enable the namespace-constrained mode, set the environment variable KCERT__NAMESPACECONSTRAINTS
to a list of namespaces, separated by ",".
Example: KCERT__NAMESPACECONSTRAINTS=ns-1,ns-2,ns-3
.
Check resulting yaml files without deploying:
helm template kcert-test .\charts\kcert --values .\temp\kcert-values.yaml
If this is your first time using KCert you should probably start out with https://acme-staging-v02.api.letsencrypt.org/directory
.
Experiment and make sure everything is working as expected, then switch over to https://acme-v02.api.letsencrypt.org/directory
.
More information this topic can be found here.
KCert supports the EAB authentication protocol for providers requiring it. To set it up, set the following environment variables:
ACME__EABKEYID: Key identifier given by your ACME provider
ACME__EABHMACKEY: HMAC key given by your ACME provider
KCert now supports the DNS-01 challenge type with AWS Route53 and Cloudflare. This allows KCert to obtain certificates without requiring an externally accessible HTTP challenge endpoint, which can be beneficial in private networks or when managing wildcard certificates (though KCert does not explicitly request wildcard certificates itself yet).
All settings are configured via environment variables, following the .NET Core convention (e.g., KCert:Namespace
becomes KCERT__NAMESPACE
).
KCert:PreferredChallengeType
(KCERT__PREFERREDCHALLENGETYPE
env var):- Specifies the preferred ACME challenge type.
- Possible values:
"http-01"
(Default): Uses the traditional HTTP-01 challenge, requiring the KCert service to be reachable via an Ingress for challenge validation."dns-01"
: Uses the DNS-01 challenge, where KCert will create temporary TXT records in your configured DNS provider.
- If
dns-01
is preferred and a DNS provider is enabled, KCert will attempt the DNS-01 challenge first. If it fails, or if no DNS provider is configured/enabled, or if the ACME server does not offer a DNS-01 challenge for the identifier, KCert will fall back tohttp-01
if that challenge type is available from the ACME server.
KCert:Route53:EnableRoute53
(KCERT__ROUTE53__ENABLEROUTE53
): Set totrue
to enable AWS Route53 as a DNS provider.KCert:Route53:AccessKeyId
(KCERT__ROUTE53__ACCESSKEYID
): Your AWS Access Key ID.KCert:Route53:SecretAccessKey
(KCERT__ROUTE53__SECRETACCESSKEY
): Your AWS Secret Access Key. It's highly recommended to store this in a Kubernetes secret and mount it as an environment variable.KCert:Route53:Region
(KCERT__ROUTE53__REGION
): The AWS region where your Route53 hosted zones are managed (e.g.,us-east-1
). This is required if Route53 is enabled.
Recommended IAM Policy for AWS Route53:
The following IAM policy grants the necessary permissions for KCert to manage TXT records for DNS-01 challenges.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"route53:ListHostedZonesByName",
"route53:ListHostedZones",
"route53:ChangeResourceRecordSets"
],
"Resource": "*"
}
]
}
Note: For enhanced security, it is highly recommended to restrict the Resource
to the specific ARNs of the hosted zones that KCert will manage, rather than using *
. For example: "Resource": "arn:aws:route53:::hostedzone/YOUR_HOSTED_ZONE_ID"
KCert:Cloudflare:EnableCloudflare
(KCERT__CLOUDFLARE__ENABLECLOUDFLARE
): Set totrue
to enable Cloudflare as a DNS provider.KCert:Cloudflare:ApiToken
(KCERT__CLOUDFLARE__APITOKEN
): Your Cloudflare API Token. Store this securely, e.g., in a Kubernetes secret.KCert:Cloudflare:AccountId
(KCERT__CLOUDFLARE__ACCOUNTID
): Your Cloudflare Account ID. This is required if Cloudflare is enabled.
Required Cloudflare API Token Permissions:
The API Token needs the following permissions for the zones KCert will manage:
- Zone:
Read
(e.g.,Zone Resources:Read
) - DNS:
Edit
(e.g.,DNS:Edit
)
You can create a custom token with these specific permissions in your Cloudflare dashboard.
KCert supports two types of ACME challenges to verify domain ownership: HTTP-01 and DNS-01.
This is the default method. KCert configures an Ingress resource pointing to itself. The Let's Encrypt server (or other ACME provider) makes an HTTP request to a specific URL under your domain. If KCert successfully serves the expected challenge response, the domain is validated. This requires KCert to be accessible from the internet.
When KCert:PreferredChallengeType
is set to "dns-01"
and a DNS provider (AWS Route53 or Cloudflare) is enabled and configured:
- KCert requests a new certificate order from the ACME server.
- For each domain in the certificate, the ACME server provides a unique token for DNS-01 challenge.
- KCert creates a temporary TXT DNS record (
_acme-challenge.<yourdomain>
) with a value derived from this token. This is done using the configured DNS provider's API (AWS Route53 or Cloudflare). - After attempting to create the TXT record, KCert waits for a short period to allow for DNS propagation.
- KCert then asks the ACME server to validate the challenge. The ACME server performs a DNS lookup for the TXT record.
- Once the challenge is validated (or fails), KCert removes the temporary TXT record.
Behavior with PreferredChallengeType
:
- If
dns-01
is preferred and a DNS provider is configured: KCert attempts DNS-01 first. If this process fails (e.g., API error, validation timeout), or if the ACME server doesn't offer DNS-01 for a given identifier, KCert will automatically fall back to the HTTP-01 challenge if available. - If
http-01
is preferred (or is the default), KCert uses the HTTP-01 challenge.
Skipping HTTP Challenge Ingress:
If dns-01
is the preferred challenge type and a DNS provider is successfully configured and used, KCert will not create or manage the temporary HTTP challenge Ingress (kcert-ingress
by default) that is typically used for HTTP-01 challenges. This can simplify deployments where exposing KCert directly via an Ingress is undesirable or complex. If DNS-01 fails and KCert falls back to HTTP-01, it will then manage the HTTP challenge Ingress as needed.
DNS Provider Selection:
- If both AWS Route53 and Cloudflare are enabled, AWS Route53 will be used by default.
- It is generally recommended to enable and configure only one DNS provider to avoid ambiguity.
Auto-Renewal: The DNS-01 challenge mechanism is fully compatible with KCert's automatic certificate renewal process.
KCert supports issuing certificates for wildcard domains (e.g., *.example.com
). This allows a single certificate to cover multiple subdomains under a specific domain.
Key Requirements for Wildcard Certificates:
-
DNS-01 Challenge is Mandatory: Wildcard domain validation can only be performed using the DNS-01 challenge type. You must configure KCert to use DNS-01 by:
- Setting
KCert:PreferredChallengeType
(KCERT__PREFERREDCHALLENGETYPE
env var) to"dns-01"
. - Properly configuring a DNS provider (AWS Route53 or Cloudflare) as detailed in the "DNS Provider Configuration" section.
- If DNS-01 is not configured or fails for a wildcard domain, KCert will not fall back to HTTP-01 for that domain, and certificate issuance will fail.
- Setting
-
Explicitly List Both Wildcard and Apex/Base Domain: If you want a certificate that covers both the wildcard domain (e.g.,
*.example.com
) AND the apex/base domain (e.g.,example.com
), you must explicitly list both hostnames in your Ingressspec.tls[].hosts
array or ConfigMapdata.hosts
field. KCert requests certificates for exactly the hostnames provided.
Example: Ingress for Wildcard Certificate (*.example.com
and example.com
)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-wildcard-app
# namespace: my-namespace # Optional: specify if not default
labels:
kcert.dev/ingress: "managed" # Required for KCert to manage this Ingress
# annotations:
# kubernetes.io/ingress.class: "nginx" # Example Ingress controller
spec:
tls:
- hosts:
- "*.example.com"
- "example.com" # Important: Include the base domain for SAN coverage
secretName: my-example-wildcard-tls # Name of the secret to store the certificate
rules:
- host: "www.example.com" # Example specific subdomain covered by the wildcard
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app-service
port:
number: 80
# Optional: Rule for the apex domain if it also serves traffic directly
- host: "example.com"
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app-service
port:
number: 80
Note: Ensure kcert.dev/ingress: "managed"
label is present. To cover both *.example.com
and example.com
, list both in spec.tls[].hosts
.
Example: ConfigMap for Wildcard Certificate (*.example.com
and example.com
)
apiVersion: v1
kind: ConfigMap
metadata:
name: my-example-wildcard-tls # This ConfigMap name will be the Kubernetes Secret name
# namespace: my-namespace # Optional: specify if not default
labels:
kcert.dev/configmap: "managed" # Processed if KCERT_CONFIGMAP_WATCH_LABEL_KEY is "kcert.dev/configmap" (default)
# AND KCERT_CONFIGMAP_WATCH_LABEL_VALUE is "managed" (or empty, in which case any value for this key works).
data:
hosts: "*.example.com,example.com" # Comma-separated list
Note: The ConfigMap's metadata.name
is used as the Kubernetes Secret name for the certificate. Ensure data.hosts
is present and the label meets KCert's discovery criteria based on its configuration.
DNS Provider Setup: Remember to refer to the "DNS Provider Configuration" section to correctly set up AWS Route53 or Cloudflare for DNS-01 challenges. Without a functional DNS provider configuration, wildcard certificate issuance will fail.
To check that everything is running as expected:
- Run
kubectl -n kcert logs svc/kcert
and make sure there are no error messages - Run
kubectl -n kcert port-forward svc/kcert 8080
and go tohttp://localhost:8080
in your browser
To test your email configuration you can connect to the KCert dasboard by running
kubectl -n kcert port-forward svc/kcert 8080
and opening http://localhost:8080
in your browser.
From there, navigate to the configuration section.
Check that your settings are listed there, and then click "Send Test Email" to receive a test email.
By default KCert will generate a random secret key at startup. For many use cases this will be fine. If you would like to use a fixed key, you can provide it as an environment variable.
You can generate your own random key with the following:
docker run -it nabsul/kcert:v1.0.1 dotnet KCert.dll generate-key
Next you would need to put that generated key into a Kubernetes secret:
kubectl -n kcert create secret generic kcert-key --from-literal=key=[...]
Finally, add this to your deployment's environment variables:
- name: ACME__KEY
valueFrom:
secretKeyRef:
name: kcert-key
key: key
KCert watches for changes to ingresses in cluster and reacts to them accordingly.
KCert will ignore an ingress unless it is labelled with kcert.dev/ingress=managed
.
For example, you could configure an ingress as follows:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: test1-ingress
labels:
kcert.dev/ingress: "managed"
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
tls:
- hosts:
- test1.kcert.dev
- test2.kcert.dev
secretName: test1-tls
rules:
- host: test1.kcert.dev
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: hello-world
port:
number: 80
- host: test2.kcert.dev
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: hello-world
port:
number: 80
KCert should automatically detect this new ingress and generate a TLS secret called test1-tls
for the two domains listed above.
You could also create one certificate per host as follows:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: test1-ingress
labels:
kcert.dev/ingress: "managed"
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
tls:
- hosts:
- test1.kcert.dev
secretName: test1-tls
tls:
- hosts:
- test2.kcert.dev
secretName: test2-tls
rules:
- host: test1.kcert.dev
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: hello-world
port:
number: 80
- host: test2.kcert.dev
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: hello-world
port:
number: 80
Once every 6 hours KCert will check for certificates that are expiring in 30 days or less. It will attempt to automatically renew those certificates. If you have email notifications set up, you will receive a notifications of success of failure of the renewal process.
KCert uses the standard .NET Core configuration library to manage its settings. The appsettings.json contains the full list of settings with reasonable default values. Configuration for DNS providers (Route53, Cloudflare), preferred challenge type, and ConfigMap label filtering are typically set via environment variables in your Kubernetes deployment.
All settings shown in appsettings.json
can be modified via environment variables.
For example, you can override the value of the Acme:RenewalCheckTimeHours
setting
with a ACME__RENEWALCHECKTIMEHOURS
environment variable.
Settings with colons in their names, like KCert:Route53:EnableRoute53
, are transformed by replacing :
with __
(double underscore), e.g., KCERT__ROUTE53__ENABLEROUTE53
.
Key Environment Variables for ConfigMap Certificate Discovery:
KCERT_CONFIGMAP_WATCH_LABEL_KEY
:- Defines the sole label key KCert uses to discover ConfigMaps for certificate management.
- Default:
kcert.dev/configmap
KCERT_CONFIGMAP_WATCH_LABEL_VALUE
:- Defines the required value for the label specified by
KCERT_CONFIGMAP_WATCH_LABEL_KEY
. - Default:
""
(empty string). - If empty, KCert processes ConfigMaps that have the
KCERT_CONFIGMAP_WATCH_LABEL_KEY
present, regardless of its value. - If set to a specific string (e.g.,
"managed"
), the ConfigMap must have the label key with this exact value.
- Defines the required value for the label specified by
For more information see the official .NET Core documentation on .NET Core configuration and environment variables.
- Update Configuration: When deploying KCert, ensure you set the necessary environment variables for your chosen DNS provider (AWS Route53 or Cloudflare) and the
KCERT__PREFERREDCHallenGETYPE
if you wish to use DNS-01. Remember to store sensitive information like API keys and tokens securely, preferably using Kubernetes secrets. - Kubernetes RBAC: The existing Kubernetes Role-Based Access Control (RBAC) permissions for KCert (ClusterRoles for watching Ingresses and Secrets, and a Role for managing its own challenge Ingress if HTTP-01 is used) generally do not need to be changed for DNS-01 support. DNS provider interactions happen directly with the provider's API, not through Kubernetes resources for the challenge itself.
To build your own container image: docker build -t [your tag] .
For local development, I recommend using dotnet user-secrets
to configure all of KCert's required settings.
You can run KCert locally with dotnet run
.
KCert will use your local kubectl configuration to connect to a Kubernetes cluster.
It will behave as if it is running in the cluster and you will be able to explore any settings that might be there.
KCert does not create many resources, and most of them are restricted to the kcert namespace. Removing KCert from your cluster is as simple as executing these three commands:
kubectl delete namespace kcert
kubectl delete clusterrolebinding kcert
kubectl delete clusterrole kcert
Note that certificates created by KCert in other namespaces will NOT be deleted. You can keep those certificates or manually delete them.