Skip to main content

Kubernetes Ingress: ingress-nginx and TLS Certificates

Cloud & DevOps · 29.09.2026

A LoadBalancer-type Service in Kubernetes creates a separate external IP for every application — on a VPS without a cloud load balancer this either does not work at all or requires manually setting up MetalLB. Ingress solves the problem differently: one controller and one external IP handle domain-based routing for every service in the cluster at once.

What Ingress is and why you need it in a cluster

Ingress is a routing rule for HTTP and HTTPS traffic into the cluster, based on domain name and URL path — not a separate network object. The rule by itself does nothing without an Ingress Controller, the program that reads these rules and configures a real proxy accordingly. ingress-nginx is the most common controller, built on top of plain nginx.

Without Ingress, every service would have to be published through a NodePort or a separate LoadBalancer, which for 10 applications in a cluster means 10 external addresses and ports instead of one domain and one IP with routing by subdomain.

How ingress-nginx works

Ingress Controller and the Ingress resource

The Controller is a pod that listens for external traffic on ports 80 and 443, reads Ingress objects through the Kubernetes API, and regenerates its internal nginx config on the fly. An Ingress resource is a YAML manifest with rules: which domain routes to which Service and port.

Routing by domain and by path

A single Ingress object can describe several rules at once: app.example.com routes to one Service, api.example.com to another, and /admin inside app.example.com to a third. The controller sorts rules by specificity on its own — you do not need to set the order manually.

Publishing methodExternal IPsDomain-based routing
NodePortOne per nodeNo
LoadBalancerOne per serviceNo
IngressOne for the whole clusterYes

Installing ingress-nginx with Helm

The official chart installs the controller, RBAC roles, and a Service of type LoadBalancer or NodePort, depending on the environment.

helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm install ingress-nginx ingress-nginx/ingress-nginx \
  --namespace ingress-nginx --create-namespace

On a VPS without a cloud load balancer, switch the Service to hostNetwork or NodePort and point the domain directly at the node's IP — MetalLB is only needed when you need one virtual IP shared across several nodes.

An Ingress resource with TLS through cert-manager

cert-manager automatically requests and renews certificates, and the Ingress resource simply states which domain it is for and which Secret to store it in.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  tls:
    - hosts: ["app.example.com"]
      secretName: app-tls
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: app-service
                port:
                  number: 80

Automatic Let's Encrypt certificate issuance

A ClusterIssuer describes how cert-manager proves domain ownership — through an HTTP-01 challenge, which passes right through the already configured Ingress.

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

If the cluster is deployed with k3s, ingress-nginx is often already included by default — Traefik, which ships with k3s out of the box, can be replaced with ingress-nginx or kept, depending on which features you need. It is convenient to provision the separate VPS instances for the cluster nodes with Terraform, describing their configuration as code ahead of time.

Checklist and common mistakes

  • The cert-manager.io/cluster-issuer annotation matches the ClusterIssuer name exactly — a typo produces no error at all, the certificate simply never gets issued.
  • The domain's DNS record already points to the Ingress Controller's external IP before requesting the certificate — otherwise the HTTP-01 challenge cannot pass verification.
  • pathType is set explicitly (Prefix or Exact) — without it, older Kubernetes versions fall back to deprecated routing behavior.
  • The Secret with the TLS certificate lives in the same namespace as the Ingress resource, not in the controller's namespace.
← Back to Knowledge Base Ask Support