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 method | External IPs | Domain-based routing |
|---|---|---|
| NodePort | One per node | No |
| LoadBalancer | One per service | No |
| Ingress | One for the whole cluster | Yes |
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.
Frequently asked questions
Can several Ingress controllers run in the same cluster at the same time?
Yes, a cluster allows several controllers, as long as each has its own class and every Ingress resource points to the right one through a matching class annotation. That is how teams separate external and internal traffic, or staging from production. Without an explicit class, the cluster falls back to a default controller, or nobody picks up the rule.
What happens if a certificate is requested before the domain's DNS record points to the Ingress Controller?
The HTTP-01 ownership check fails: the Let's Encrypt request lands on the old address instead of the controller's external IP, and issuance ends in an error. cert-manager keeps retrying on its own schedule, so it is worth waiting for the DNS record to propagate and confirming the domain resolves to the right address before creating the Ingress resource.
Does ingress-nginx work for TCP and UDP traffic, or only HTTP and HTTPS?
ingress-nginx is built primarily to route HTTP and HTTPS traffic by domain and path. An arbitrary TCP or UDP service cannot be described through a standard Ingress resource; such ports get exposed through a separate configuration on the controller itself, bypassing the usual rules, and that is an exception rather than the typical way the tool is used.
Do certificates issued through cert-manager cost money?
No, the ClusterIssuer setup backed by Let's Encrypt issues certificates for free and renews them on its own, without any action from the cluster administrator. Money only comes into play if you choose a different certificate authority offering paid extended-validation certificates instead; the standard setup described here is built entirely around free automatic issuance through the HTTP-01 challenge.