How to Configure Ingress SSL Certificates in Kubernetes

How to Configure Ingress SSL Certificates in Kubernetes
Published on Apr 26, 2026 Updated on Apr 27, 2026

When you expose a Kubernetes service over plain HTTP, you reveal every header, cookie, and form submission. Intermediaries, such as proxies and load balancers, can read or alter data. Browsers warn about “Not secure” connections, and search engines lower rankings. Using SSL/TLS encrypts the connection and protects your applications.

A TLS certificate encrypts the stream between the client and the Kubernetes Ingress. The Ingress controller then ends TLS and sends requests to the correct service. A valid certificate on an Ingress keeps sensitive data safe. It also moves encryption tasks away from your pods.

In this tutorial, you’ll install the NGINX Ingress Controller. Next, you’ll deploy a simple app and add TLS. You will create your own TLS secret and reference it in the Ingress. Then, you’ll install cert-manager. It will help you request and renew certificates automatically with Let’s Encrypt.

#Prerequisites

  • You need a running Kubernetes cluster. A dedicated bare metal server helps with performance, though any cluster works for this guide.

  • kubectl must be installed and talking to your cluster.

  • An Ingress controller should already be running. We use NGINX Ingress Controller in this tutorial.

  • Your domain name with DNS A records pointing to your Ingress controller's external IP.

  • You should also know your way around Kubernetes objects such as Deployments, Services, and Secrets.

  • Remove taint from your cluster (if it’s a single-node cluster).

Rent Dedicated Servers

Deploy custom or pre-built dedicated bare metal. Get full root access, AMD EPYC and Ryzen CPUs, and 24/7 technical support from humans, not bots.

#What is Kubernetes Ingress?

Ingress is a Kubernetes API object for managing external access to services. It routes HTTP and HTTPS traffic based on hostnames and URL paths.

Kubernetes already has other ways to expose services externally. NodePort is one. LoadBalancer is another. So Ingress needs some justification.

NodePort opens a specific port on every node in your cluster. Traffic hits that port and gets forwarded to your service. This works for testing, but not for production. You’ll end up with a lot of port numbers. Port 30080 for this service, port 31443 for that one. Six months later, nobody remembers which is which.

LoadBalancer takes a cleaner approach. Kubernetes asks your cloud provider to spin up an external load balancer for each service. Simple. Until you have fifteen services and fifteen load balancers on your cloud bill. That adds up fast.

Ingress solves both problems. You get a single external IP address. All traffic enters through that one point. Ingress looks at each incoming request and decides where to send it based on the hostname or URL path.

A few examples of what you can do:

  • Route api.example.com to your API service and app.example.com to your frontend

  • Send /api requests to one backend and /dashboard requests to another

  • Terminate TLS at the edge so your backend services receive plain HTTP

One load balancer. One IP. Multiple services behind it.

#How Ingress works

An Ingress resource is just a configuration file in YAML format. You define routing rules in it. Hostnames, paths, backend services. Then you apply it to your cluster with kubectl.

And then nothing happens.

Nothing happens because the Ingress resource itself is passive. It stores rules. It does not enforce them. Kubernetes needs a separate component to read those rules and actually route traffic. That component is the Ingress Controller.

The Ingress Controller runs as a pod inside your cluster. Sometimes multiple pods for high availability. Inside each pod is a reverse proxy. NGINX is what most people use. Traefik and HAProxy are solid alternatives. The controller watches the Kubernetes API for Ingress resources. Create one, and the controller notices. Update one, same thing. Delete one, and the controller removes those routing rules from its configuration.

Here is how traffic actually flows:

A user types your domain into their browser. DNS resolves it to the external IP of your Ingress Controller. That IP might come from a cloud load balancer sitting in front of the controller pods. On bare metal setups, it might be a node IP with some external routing configured.

The request arrives at the controller. The reverse proxy examines two things:

  • The Host header in the HTTP request

  • The URL path

It compares these values against every Ingress resource in the cluster. When it finds a matching rule, it forwards the request to the backend service defined in that rule.

The service maintains a list of endpoints. Each endpoint is the IP address of a pod running your application. The service picks one and sends the traffic there. Your pod processes the request and sends a response back through the same chain.

#Ingress vs other service types

Kubernetes offers several ways to expose applications:

Service Type External Access Use Case
ClusterIP Internal only Inter-service communication
NodePort Via node IP:port Development, simple testing
LoadBalancer Via external IP Production traffic with cloud provider integration
Ingress Via HTTP/HTTPS routing Multiple services, TLS termination, path-based routing

Ingress excels when you need to expose multiple services through a single external IP, implement host-based or path-based routing, or terminate TLS at the edge.

#How Ingress TLS works

When a client connects via HTTPS:

  1. The client initiates a TLS handshake with the Ingress Controller

  2. The controller presents the certificate from the configured TLS secret

  3. After encryption is established, the controller routes the request to the backend service

  4. Backend communication typically occurs over unencrypted HTTP within the cluster

The Ingress Controller terminates TLS, which means it handles both encryption and decryption. Backend pods receive plain HTTP traffic. This architecture centralizes certificate management at the Ingress layer rather than requiring each application to handle TLS.

#Installing the NGINX Ingress Controller

The NGINX Ingress Controller is widely used and well-documented. It processes Ingress resources and routes external traffic to services.

#Deploy the controller

Apply the NGINX Ingress Controller manifest:

Command Line
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.14.0/deploy/static/provider/cloud/deploy.yaml

Verify that the controller pod is running:

Command Line
kubectl get pods -n ingress-nginx
OutputNAME 									                    READY  STATUS   RESTARTS  AGE  
ingress-nginx-controller-5c6d4449f-wsl67  1/1	   Running	0	        11s

#Verify external IP assignment

Check that the Ingress Controller received an external IP:

Command Line
kubectl get svc -n ingress-nginx ingress-nginx-controller
OutputNAME                     TYPE         CLUSTER-IP   EXTERNAL-IP PORT(S)                    AGE  
ingress-nginx-controller LoadBalancer 10.108.40.97 <pending>   80:31194/TCP,443:32479/TCP 8m28s

The EXTERNAL-IP column should display an IP address. If it shows <pending>, your cluster needs a load balancer implementation. Cloud providers handle this automatically, but bare metal and on-premises clusters require MetalLB.

#Install MetalLB (if needed)

Skip this section if your Ingress Controller already has an external IP.

MetalLB provides load-balancer functionality for clusters without cloud provider integration. Apply the MetalLB manifest:

Command Line
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.15.3/config/manifests/metallb-native.yaml

Verify that the MetalLB pods are running:

Command Line
kubectl get pods -n metallb-system
OutputNAME                        READY  STATUS  RESTARTS AGE  
controller-d54bf7b99-gdzm2  1/1    Running 0        28s  
speaker-kpx7b               1/1    Running 0        28s

Next, configure an IP address pool. Create the file, metallb-config.yaml and add the content below. Replace the value of addresses below with your server's public IP or an available range:

metallb-config.yaml
apiVersion: metallb.io/v1beta1  
kind: IPAddressPool  
metadata:  
	name: default-pool  
	namespace: metallb-system  
spec:  
	addresses:  
	-  84.32.32.137/32  
---  
apiVersion: metallb.io/v1beta1  
kind: L2Advertisement  
metadata:  
	name: default  
	namespace: metallb-system  
spec:  
	ipAddressPools:  
	-  default-pool

Apply the configuration:

Command Line
kubectl apply -f metallb-config.yaml

Verify the Ingress Controller now has an external IP:

Command Line
kubectl get svc -n ingress-nginx ingress-nginx-controller
OutputNAME                     TYPE         CLUSTER-IP   EXTERNAL-IP  PORT(S)                    AGE  
ingress-nginx-controller LoadBalancer 10.108.40.97 84.32.32.137 80:31194/TCP,443:32479/TCP 14m

The EXTERNAL-IP column should now display an IP from your configured pool.

#Deploying a sample application

Create a simple application to test Ingress and SSL configuration.

#Create the deployment

Create a file app-deployment.yaml and paste the content below:

app-deployment.yaml
apiVersion: apps/v1  
kind: Deployment  
metadata:  
	name: demo-app  
	labels:  
		app: demo  
spec:  
	replicas: 2  
	selector:  
		matchLabels:  
			app: demo  
	template:  
		metadata:  
			labels:  
			app: demo  
		spec:  
			containers:  
			- name: nginx  
				image: nginxdemos/hello:latest  
				ports:  
				- containerPort: 80  
				resources:  
					requests:  
						memory: "64Mi"  
						cpu: "100m"  
					limits:  
						memory: "128Mi"  
						cpu: "200m"

Apply the deployment:

Command Line
kubectl apply -f app-deployment.yaml

#Create the service

Paste the content below into a file named app-service.yaml:

app-service.yaml
apiVersion: v1  
kind: Service  
metadata:  
	name: demo-service  
spec:  
	selector:  
		app: demo  
	ports:  
	- protocol: TCP  
		port: 80  
		targetPort: 80  
	type: ClusterIP

Apply the service:

Command Line
kubectl apply -f app-service.yaml

#Create the Ingress resource

Create a file, app-ingress.yaml, and add the below content:

app-ingress.yaml
apiVersion: networking.k8s.io/v1  
kind: Ingress  
metadata:  
	name: demo-ingress  
spec:  
	ingressClassName: nginx  
	rules:  
	- host: app.example.com  
	http:  
		paths:  
		- path: /  
			pathType: Prefix  
			backend:  
				service:  
					name: demo-service  
					port:  
						number: 80

Replace app.example.com with your actual domain name.

Apply the Ingress:

Command Line
kubectl apply -f app-ingress.yaml

#Verify the deployment

Check that all resources are running:

Command Line
kubectl get pods,svc,ingress
OutputNAME                            READY STATUS   RESTARTS AGE  
pod/demo-app-5659b7bbbc-ddttv   1/1   Running  0        6m41s  
pod/demo-app-5659b7bbbc-r82q4   1/1   Running  0        6m41s  
  
NAME                 TYPE      CLUSTER-IP   EXTERNAL-IP PORT(S)  AGE  
service/demo-service ClusterIP 10.96.229.28 <none>      80/TCP   5m6s  
service/kubernetes   ClusterIP 10.96.0.1    <none>      443/TCP  26m  
  
NAME                                   CLASS  HOSTS                ADDRESS      PORTS AGE  
ingress.networking.k8s.io/demo-ingress nginx  emminextechdocs.xyz  84.32.32.137 80    2m17s

Test HTTP access:

Command Line
curl -H "Host: app.example.com" http://84.32.32.137

Use your actual domain name and IP address. You should receive an HTML response from the NGINX demo application.

#SSL certificate options

Three common approaches exist for obtaining SSL certificates:

Method Use Case Validity Renewal
Self-signed Development, testing Custom Manual
CA-issued Production, compliance requirements 1-2 years Manual
Let's Encrypt Production, public-facing sites 90 days Automated with cert-manager
  1. Self-signed certificates are certificates you create yourself using OpenSSL. No certificate authority signs them. You generate the key, the cert, and install it. The problem is trust. Browsers keep a list of authorities they trust. Your self-signed cert is not on that list. Users see a warning page and have to click through before anything loads. For a dev environment or internal tools, that is fine. For a production site customer visit, that is completely unacceptable.

  2. CA-issued certificates come from commercial certificate authorities. DigiCert, Sectigo, GlobalSign, and others. You pay for them. You renew them manually, usually once a year. The upside is compliance. Some industries and auditors require certificates from specific authorities. Some organizations need Extended Validation (EV) certificates that display the company name in the browser address bar. If your compliance team or customers demand these, CA-issued certificates are your only option.

  3. Let's Encrypt is a free certificate authority that changed the game when it launched in 2015. Certificates are valid for 90 days, which sounds like a hassle until you realize the entire renewal process can be automated. Cert-manager handles this for you in Kubernetes. No cost, no manual intervention. Let's Encrypt works for most production deployments. The only exceptions are situations where compliance rules specifically require a commercial CA.

#Configuring Ingress SSL certificates in Kubernetes

The Ingress controller handles TLS termination for your services. Requests arrive encrypted. The controller decrypts them and forwards plain HTTP to your backend pods.

Setting this up requires three things:

  • An SSL certificate and its private key

  • A Kubernetes Secret containing both

  • An Ingress resource configured to use that Secret

The sections below walk through manual configuration first, then automated certificate management with cert-manager.

#Method 1: Manual SSL certificate configuration

This method works regardless of where your certificate originates. Self-signed, purchased from a CA, exported from another system. The steps are the same either way.

#Generate a self-signed certificate

The following command creates a self-signed certificate valid for one year:

Command Line
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \  
-keyout tls.key \  
-out tls.crt \  
-subj "/CN=app.example.com/O=MyOrganization"

This creates two files:

  • tls.key: Private key

  • tls.crt: Certificate

Replace app.example.com with your domain name.

For CA-issued certificates, you would receive these files from your certificate authority after submitting a Certificate Signing Request (CSR).

#Create a Kubernetes TLS secret

Store the certificate and key in a Kubernetes secret:

Command Line
kubectl create secret tls demo-tls-secret \  
--cert=tls.crt \  
--key=tls.key

Verify the secret:

Command Line
kubectl get secret demo-tls-secret
OutputNAME             TYPE               DATA AGE  
demo-tls-secret  kubernetes.io/tls  2    13s

#Update the Ingress resource

Modify the Ingress to enable TLS. Open the file named app-ingress.yaml, clear the content and paste everything below:

app-ingress.yaml
apiVersion: networking.k8s.io/v1  
kind: Ingress  
metadata:  
	name: demo-ingress  
	annotations:  
		nginx.ingress.kubernetes.io/ssl-redirect:  "true"  
spec:  
	ingressClassName: nginx  
	tls:  
	- hosts:  
		-  app.example.com  
		secretName: demo-tls-secret  
rules:  
- host: app.example.com  
	http:  
	paths:  
	- path: /  
		pathType: Prefix  
		backend:  
			service:  
				name: demo-service  
				port:  
					number: 80

The tls section specifies which hosts should use TLS and which secret contains the certificate. The ssl-redirect annotation automatically redirects HTTP requests to HTTPS.

Apply the updated Ingress:

Command Line
kubectl apply -f app-ingress.yaml

#Test your manual SSL setup

Verify your certificate works by testing HTTPS connectivity. Replace app.example.com with your actual domain name:

Command Line
curl -vk https://app.example.com 2>&1 | grep -E "SSL|subject|issuer|expire"
Output* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519 / RSASSA-PSS  
* subject: CN=emminextechdocs.xyz; O=MyOrganization  
* expire date: Jan 20 04:27:24 2027 GMT  
* issuer: CN=emminextechdocs.xyz; O=MyOrganization  
* SSL certificate verify result: self-signed certificate (18), continuing anyway.  
* old SSL session ID is stale, removing  
< expires: Tue, 20 Jan 2026 04:52:06 GMT

The -k flag bypasses certificate verification, necessary for self-signed certificates. Remove this flag when using valid CA-issued certificates.

Check that HTTP redirects to HTTPS:

Command Line
curl -I http://app.example.com
OutputHTTP/1.1 308 Permanent Redirect  
Date: Tue, 20 Jan 2026 04:52:33 GMT  
Content-Type: text/html  
Content-Length: 164  
Connection: keep-alive  
Location: https://emminextechdocs.xyz

Verify the secret contains valid certificate data:

Command Line
kubectl get secret demo-tls-secret -o jsonpath='{.data}' | jq -r '.["tls.crt"]' | base64 -d | openssl x509 -text -noout | head -15
OutputCertificate:  
Data:  
Version: 3 (0x2)  
Serial Number:  
7f:1c:de:22:45:1b:8b:13:1f:3d:23:73:1f:94:6b:3c:c7:43:bc:d4  
Signature Algorithm: sha256WithRSAEncryption  
Issuer: CN = emminextechdocs.xyz, O = MyOrganization  
Validity  
Not Before: Jan 20 04:27:24 2026 GMT  
Not After : Jan 20 04:27:24 2027 GMT  
Subject: CN = emminextechdocs.xyz, O = MyOrganization  
Subject Public Key Info:  
Public Key Algorithm: rsaEncryption  
Public-Key: (2048 bit)  
Modulus:

The output shows the certificate details decoded from the secret. Check that the Common Name (CN) matches your domain. Make sure the expiration date looks right.

Self-signed certificates trigger browser warnings. Users have to click through and manually accept the certificate before the page loads.

Browser warning from self-signed certificate

![NGINX page]((/v3/assets/blog/2026-04-27/img-002.png)

CA-issued certificates from trusted authorities behave differently. Browsers show a padlock icon. No warnings, no extra clicks.

#Method 2: Automated SSL with cert-manager

Managing certificates manually is tedious work. You generate a certificate, base64-encode it, create a secret, and update your Ingress. Ninety days later, you do it again. Multiply that by however many domains you have. One missed renewal, and your users get a browser warning page instead of your application.

Cert-manager takes this off your plate. You tell it which domains need certificates and which CA to use. It handles the rest.

#Install cert-manager

Deploy cert-manager along with its custom resource definitions:

Command Line
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.19.1/cert-manager.yaml

Verify that the cert-manager pods are running:

Command Line
kubectl get pods -n cert-manager
OutputNAME                                      READY  STATUS   RESTARTS AGE  
cert-manager-74ff484cbd-s7rbp             1/1    Running  0        17s  
cert-manager-cainjector-5595cb5f78-xb2k4  1/1    Running  0        17s  
cert-manager-webhook-58756b95c-m6clh      1/1    Running  0        17s

#Create a staging ClusterIssuer

Start with Let's Encrypt staging to avoid rate limits during testing. Staging certificates are not trusted by browsers, but they still verify that your configuration works.

Create a file named staging-issuer.yaml, and add the following content:

staging-issuer.yaml
apiVersion: cert-manager.io/v1  
kind: ClusterIssuer  
metadata:  
	name: letsencrypt-staging  
spec:  
	acme:  
		email: your-email@example.com  
		server: https://acme-staging-v02.api.letsencrypt.org/directory  
		privateKeySecretRef:  
			name: letsencrypt-staging-key  
		solvers:  
		- http01:  
			ingress:  
				class: nginx

Replace your-email@example.com with your actual email address. Let's Encrypt sends expiration warnings to this address.

Apply the issuer:

Command Line
kubectl apply -f staging-issuer.yaml

Verify the issuer is ready:

Command Line
kubectl get clusterissuer letsencrypt-staging
OutputNAME                 READY  AGE  
letsencrypt-staging  True   3m20s

#Update Ingress for cert-manager

Create the file app-ingress-certmanager.yaml and add the cert-manager annotation to request a certificate automatically:

app-ingress-certmanager.yaml
apiVersion: networking.k8s.io/v1  
kind: Ingress  
metadata:  
	name: demo-ingress  
	annotations:  
		nginx.ingress.kubernetes.io/ssl-redirect:  "true"  
		cert-manager.io/cluster-issuer:  "letsencrypt-staging"  
spec:  
	ingressClassName: nginx  
	tls:  
	- hosts:  
		-  app.example.com  
		secretName: demo-tls-staging  
rules:  
- host: app.example.com  
	http:  
		paths:  
		- path: /  
			pathType: Prefix  
			backend:  
				service:  
					name: demo-service  
					port:  
						number: 80

The cert-manager.io/cluster-issuer annotation tells cert-manager which issuer to use. Cert-manager automatically creates the TLS secret specified in secretName.

Apply the Ingress:

Command Line
kubectl apply -f app-ingress-certmanager.yaml

#Monitor certificate issuance

Check certificate status:

Command Line
kubectl get certificate
OutputNAME              READY  SECRET           AGE  
demo-tls-staging  True   demo-tls-staging 46s

The READY column should show True after cert-manager completes the ACME challenge.

If the certificate shows False, check the certificate details:

Command Line
kubectl describe certificate demo-tls-staging

Look at the Events section for error messages. Common issues include DNS misconfiguration or firewall blocking port 80.

#Switch to production issuer

After confirming staging works, create a production issuer. Paste the following content in the file production-issuer.yaml:

production-issuer.yaml
apiVersion: cert-manager.io/v1  
kind: ClusterIssuer  
metadata:  
	name: letsencrypt-production  
spec:  
	acme:  
		email: your-email@example.com  
		server: https://acme-v02.api.letsencrypt.org/directory  
		privateKeySecretRef:  
			name: letsencrypt-production-key  
		solvers:  
		- http01:  
			ingress:  
				class: nginx

Apply the production issuer:

Command Line
kubectl apply -f production-issuer.yaml

Update the Ingress to use the production issuer. Paste the below content in the file, app-ingress-production.yaml:

app-ingress-production.yaml
apiVersion: networking.k8s.io/v1  
kind: Ingress  
metadata:  
	name: demo-ingress  
	annotations:  
		nginx.ingress.kubernetes.io/ssl-redirect:  "true"  
		cert-manager.io/cluster-issuer:  "letsencrypt-production"  
spec:  
	ingressClassName: nginx  
	tls:  
	- hosts:  
		-  app.example.com  
		secretName: demo-tls-production  
	rules:  
	- host: app.example.com  
		http:  
			paths:  
			- path: /  
				pathType: Prefix  
				backend:  
					service:  
						name: demo-service  
						port:  
							number: 80

Apply the updated Ingress:

Command Line
kubectl apply -f app-ingress-production.yaml

Verify the production certificate:

Command Line
kubectl get certificate
OutputNAME                 READY  SECRET               AGE  
demo-tls-production  True   demo-tls-production  28s

Your application now has a valid, browser-trusted SSL certificate that cert-manager will automatically renew.

#Test your cert-manager setup

After the certificate issues, verify HTTPS connectivity with curl:

Command Line
curl -v https://app.example.com 2>&1 | grep -E "SSL|subject|issuer|expire"
Output* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519 / RSASSA-PSS  
* subject: CN=emminextechdocs.xyz  
* expire date: Apr 20 04:37:11 2026 GMT  
* subjectAltName: host "emminextechdocs.xyz" matched cert's "emminextechdocs.xyz"  
* issuer: C=US; O=Let's Encrypt; CN=R12  
* SSL certificate verify ok.  
* old SSL session ID is stale, removing  
< expires: Tue, 20 Jan 2026 05:36:47 GMT

Production certificates from Let's Encrypt show "Let's Encrypt" as the issuer.

Monitor the Certificate resource for renewal status:

Command Line
kubectl get certificate demo-tls-production -o jsonpath='{.status.conditions[0]}' | jq
Output{  
"lastTransitionTime": "2026-01-20T05:35:44Z",  
"message": "Certificate is up to date and has not expired",  
"observedGeneration": 1,  
"reason": "Ready",  
"status": "True",  
"type": "Ready"  
}

Check the certificate's actual expiration date:

Command Line
kubectl get secret demo-tls-production -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -enddate -noout
OutputnotAfter=Apr 20 04:37:11 2026 GMT

cert-manager renews certificates 30 days before expiration. For a 90-day Let's Encrypt certificate, renewal occurs around day 60.

Test from a browser by visiting your domain. A valid Let's Encrypt production certificate displays a padlock icon. Click the padlock to view certificate details:

![Verified by Let's Encrypt]((/v3/assets/blog/2026-04-27/img-003.png)

For comprehensive SSL testing, use online tools like SSL Labs. These services check certificate chain validity, protocol support, cipher strength, and common vulnerabilities.

#Troubleshooting

#Certificate is stuck in pending

Check the certificate request status:

Command Line
kubectl get certificaterequests -n <namespace>

Common causes:

  • DNS record not pointing to your Ingress Controller's public IP

  • Port 80 blocked by a firewall or security group (required for HTTP-01 challenge)

  • Ingress is not using the correct Ingress class or ingressClassName

  • cert-manager pods not running or in CrashLoopBackOff

  • Wrong issuerRef (Issuer or ClusterIssuer name) in the Certificate spec

Verify DNS resolution:

Command Line
dig +short app.example.com

The output should match your Ingress Controller's external IP.

Check if port 80 is accessible:

Command Line
curl -I http://app.example.com/.well-known/acme-challenge/test

A 404 (or any other HTTP status code) indicates that port 80 is reachable and the request is being routed to your Ingress. Connection timeouts or 'connection refused' errors usually indicate firewall or networking issues.

#Ingress not responding

Check the Ingress Controller logs:

Command Line
kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx

Verify the Ingress configuration:

Command Line
kubectl describe ingress demo-ingress

Look for error messages in the Events section.

Common issues:

  • ingressClassName not set or incorrect

  • Backend service not found

  • Backend pods not running

#Browser shows an untrusted certificate warning

If using staging certificates, this is expected. Staging certificates are not trusted by browsers.

If using production certificates, check:

Command Line
kubectl get secret demo-tls-production -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -issuer

The issuer should show "Let's Encrypt" for production certificates, not "Fake LE".

#NGINX default certificate shown

The NGINX Ingress Controller uses a default self-signed certificate when TLS is misconfigured.

Check if the TLS secret exists:

Command Line
kubectl get secret demo-tls-production

Verify the secret contains valid certificate data:

Command Line
kubectl get secret demo-tls-production -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -text | head -20

Confirm the certificate's Common Name or Subject Alternative Names match your domain.

#Let's Encrypt rate limits

Let's Encrypt enforces rate limits:

  • 50 certificates per registered domain per week

  • 5 duplicate certificates per week

  • 5 failed validations per hour per account

Use staging for testing to avoid hitting production limits. Staging has much higher rate limits.

#Best practices

  1. Use cert-manager for production. Automated renewal prevents certificate expiration outages. Cert-manager renews certificates 30 days before expiry and handles the entire ACME workflow without manual intervention.

  2. Start with staging certificates. Always test with Let's Encrypt staging before switching to production. Staging certificates verify your configuration without consuming production rate limits. The staging environment intentionally issues untrusted certificates, making it obvious when you are still in testing mode.

  3. Monitor certificate expiration. Set up alerts for certificates approaching expiration. Check certificate status regularly. Configure monitoring tools like Prometheus with the cert-manager metrics endpoint to alert on certificate issues proactively.

  4. Enable HTTPS redirects. The nginx.ingress.kubernetes.io/ssl-redirect: "true" annotation automatically redirects HTTP traffic to HTTPS. Enforce HTTPS for all production traffic to prevent accidental unencrypted connections.

  5. Use separate secrets per domain. Each domain should have its own TLS secret. This simplifies certificate rotation, troubleshooting, and allows different renewal schedules or issuers per domain.

  6. Back up CA-issued certificates. Store copies of manually obtained certificates securely outside the cluster. Kubernetes secrets can be deleted accidentally. Keep backups in a secure location like a password manager or secrets vault.

  7. Test certificate renewal. Periodically verify that cert-manager successfully renews certificates. Delete a staging certificate and confirm it reissues automatically. This validates that your entire certificate pipeline works correctly.

  8. Document your certificate configuration. Record which issuers, secrets, and Ingress resources relate to each domain. Clear documentation speeds troubleshooting and helps team members understand the certificate infrastructure.

#Conclusion

SSL certificates protect data in transit between clients and your Kubernetes applications. The NGINX Ingress Controller handles TLS termination, centralizing certificate management at the cluster edge.

Manual certificate configuration works with any certificate type and gives you full control. Cert-manager automates the lifecycle with Let's Encrypt, eliminating manual renewal tasks and reducing the risk of outages related to expiration.

Start with staging certificates to validate your configuration, then switch to production for browser-trusted certificates. Monitor certificate status and set up alerts to catch renewal failures before they affect users.

Cherry Servers provides dedicated servers and cloud infrastructure suitable for hosting Kubernetes clusters with full root access and flexible networking options.

Bare Metal Servers - 15 Minute Deployment

Get 100% dedicated resources for high-performance workloads.

Share this article

Related Articles

Published on Oct 10, 2025 Updated on Mar 9, 2026

OpenShift vs Kubernetes on Bare Metal: Which One to Choose

Compare Kubernetes vs OpenShift on bare metal. Learn how each platform performs, their setup, features, costs, and which suits your infrastructure best.

Read More
Published on Sep 26, 2025 Updated on Nov 7, 2025

How to Create a Kubernetes Cluster with Minikube and Kubeadm

Learn how to create a Kubernetes cluster using Minikube for local development and Kubeadm for production-ready setups with step-by-step guidance.

Read More
Published on Sep 24, 2025 Updated on Mar 9, 2026

How to Install Calico on Kubernetes: Step-by-Step Tutorial

Install Calico on Kubernetes with Helm or YAML for secure, scalable networking. Learn setup steps, benefits, and performance boosts for your cluster.

Read More
No results found for ""
Recent Searches
Navigate
Go
ESC
Exit