NGINX Ingress TLS is how you terminate HTTPS at the edge of a Kubernetes cluster: the ingress-nginx controller presents a certificate, decrypts incoming traffic, and routes the plaintext request to the right service, all driven by an Ingress resource and a TLS secret. Getting it right means more than pasting a certificate into a secret. It means automating renewal, pinning modern protocols, and forcing every request onto the encrypted path so nothing quietly falls back to HTTP.
This is about the community ingress-nginx controller, the one most clusters run. The mechanics are the same whether your certificate comes from Let's Encrypt, an internal CA, or a commercial issuer.
The certificate secret and the Ingress
TLS termination needs two things: a Kubernetes secret of type kubernetes.io/tls holding the certificate and private key, and an Ingress resource whose tls block references it for the hostnames it serves.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- app.example.com
secretName: app-example-tls
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: app-svc
port:
number: 80
The secretName must point at a secret in the same namespace as the Ingress. A frequent failure is a working certificate in the wrong namespace, which the controller silently falls back from, serving its default self-signed certificate instead. If browsers see a "Kubernetes Ingress Controller Fake Certificate," that mismatch is almost always the cause.
Automate renewal with cert-manager
Manually rotating certificates is how outages happen at 2 a.m. when one expires. cert-manager is the standard fix: it issues and renews certificates automatically and writes them into the TLS secret your Ingress already references.
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: security@example.com
privateKeySecretRef:
name: letsencrypt-prod-key
solvers:
- http01:
ingress:
class: nginx
Add the annotation cert-manager.io/cluster-issuer: letsencrypt-prod to the Ingress and cert-manager provisions the certificate, completes the ACME challenge through the same ingress, and renews well before expiry. Watch the Certificate resource's status; a stuck ACME challenge usually means the HTTP-01 path is not reachable from the public internet.
Harden the protocols
Terminating TLS is only half the job; terminating it with weak settings leaves you exposed to downgrade and protocol attacks. Configure the controller, through its ConfigMap, to offer only modern protocols and strong ciphers:
apiVersion: v1
kind: ConfigMap
metadata:
name: ingress-nginx-controller
namespace: ingress-nginx
data:
ssl-protocols: "TLSv1.2 TLSv1.3"
ssl-ciphers: "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384"
ssl-prefer-server-ciphers: "true"
Drop TLS 1.0 and 1.1 entirely; they are deprecated and offer nothing modern clients need. Prefer server cipher ordering so a client cannot negotiate down to the weakest mutually supported suite. If you must state a range, keep it to TLS 1.2 and above and verify with a scan afterward.
Force HTTPS and add HSTS
An encrypted endpoint that also answers plaintext HTTP is a downgrade waiting to happen. The ssl-redirect: "true" annotation makes the controller 308-redirect HTTP to HTTPS. Layer HTTP Strict Transport Security on top so browsers refuse plaintext for your domain after the first visit:
data:
hsts: "true"
hsts-max-age: "31536000"
hsts-include-subdomains: "true"
Only enable hsts-include-subdomains once every subdomain genuinely serves HTTPS, since the browser will refuse plaintext for all of them. HSTS is sticky by design, so a mistake here is hard to unwind.
Verify what you shipped
Do not trust the YAML; test the endpoint. Check the negotiated protocol and certificate chain from outside the cluster:
openssl s_client -connect app.example.com:443 -servername app.example.com </dev/null 2>/dev/null | openssl x509 -noout -dates -issuer
A DAST scan against the live host confirms the redirect fires, the HSTS header is present, and no weak protocol is still offered, which is the honest end-to-end check that the pieces line up. Keep the ingress-nginx controller itself patched, since it is internet-facing infrastructure and has shipped security advisories; our software composition analysis coverage explains how to track the controller image and its dependencies for known issues.
FAQ
Why does my Ingress serve a fake certificate?
The controller falls back to its default self-signed certificate when it cannot find the TLS secret named in the Ingress. Confirm the secret exists, is type kubernetes.io/tls, and lives in the same namespace as the Ingress.
Do I need cert-manager for NGINX Ingress TLS?
No, but it is strongly recommended. Without it you manage certificate issuance and renewal by hand, which invites expiry outages. cert-manager writes renewed certificates straight into the secret your Ingress already uses.
How do I force all traffic to HTTPS?
Set the nginx.ingress.kubernetes.io/ssl-redirect: "true" annotation so HTTP requests are redirected to HTTPS, and add HSTS through the controller ConfigMap so browsers refuse plaintext on return visits.
Which TLS versions should the ingress allow?
Offer only TLS 1.2 and TLS 1.3. Disable TLS 1.0 and 1.1, which are deprecated, and prefer server cipher ordering so clients cannot negotiate a weaker suite.