The Nginx Ingress Controller is a popular Kubernetes Ingress controller that uses Nginx as a reverse proxy and load balancer to route external traffic into a cluster, acting as a single entry point with SSL/TLS termination, load balancing, session handling, and path-based routing. This guide sets up the Nginx Ingress Controller on a Kubernetes cluster, deploys two sample applications behind it, and issues Let's Encrypt certificates with cert-manager to secure them, plus covers importing commercial SSL certificates instead. By the end, you'll have two applications reachable over HTTPS through a shared Ingress controller. Before you begin, you'll need a Kubernetes cluster with at least 2 nodes, kubectl installed and configured to access it, the Helm package manager installed on your computer, and a domain name (for example, example.com). 1. Install the Nginx Ingress Controller 1. Add the Nginx Ingress Helm repository: $ helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx 2. Update Helm: $ helm repo update 3. Install the Nginx Ingress Controller: $ helm install ingress-nginx ingress-nginx/ingress-nginx 4. Check the load balancer that's automatically provisioned: $ kubectl get services ingress-nginx-controller Output: NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE ingress-nginx-controller LoadBalancer 10.101.22.249 <pending> 80:31915/TCP,443:30217/TCP 106s It may take a few minutes for the service to get an EXTERNAL-IP, depending on your cloud provider. Some cloud providers also require a provider-specific annotation on the LoadBalancer service to configure things like health checks — check your provider's Kubernetes documentation if the external IP doesn't provision as expected. 2. Install cert-manager 1. Install the latest cert-manager release: $ kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.17.2/cert-manager.yaml Check the official cert-manager releases page for the latest version. 2. Inspect the cert-manager resources: $ kubectl get all -n cert-manager You should see pods, services, replicasets, and deployments related to cert-manager. 3. Deploy Backend Applications Deploy two sample applications, app1 and app2, using the http-echo image, which returns its command-line argument on an HTML page. 1. Create a manifest for the app1 Deployment: $ nano app1-deploy.yaml Add the following: apiVersion: apps/v1 kind: Deployment metadata: name: app1 spec: replicas: 1 selector: matchLabels: app: app1 template: metadata: labels: app: app1 spec: containers: - name: app1 image: hashicorp/http-echo args: ["-text=Hello from App1"] ports: - containerPort: 5678 Save and close the file. 2. Create a manifest for the app2 Deployment: $ sudo nano app2-deploy.yaml Add the following: apiVersion: apps/v1 kind: Deployment metadata: name: app2 spec: replicas: 1 selector: matchLabels: app: app2 template: metadata: labels: app: app2 spec: containers: - name: app2 image: hashicorp/http-echo args: ["-text=Hello from App2"] ports: - containerPort: 5678 Save and close the file. 3. Apply the app1 deployment: $ kubectl apply -f app1-deploy.yaml 4. Apply the app2 deployment: $ kubectl apply -f app2-deploy.yaml 5. Verify both deployments succeeded: $ kubectl get deployments You should see app1 and app2 listed. 6. Create a service manifest for app1: $ nano app1-svc.yaml Add the following: apiVersion: v1 kind: Service metadata: name: app1-svc spec: ports: - name: http port: 80 targetPort: 8080 selector: app: app1 Save and close the file. 7. Create a service manifest for app2: $ nano app2-svc.yaml Add the following: apiVersion: v1 kind: Service metadata: name: app2-svc spec: ports: - name: http port: 80 targetPort: 8080 selector: app: app2 Save and close the file. 8. Deploy the app1-svc service: $ kubectl apply -f app1-svc.yaml 9. Deploy the app2-svc service: $ kubectl apply -f app2-svc.yaml 10. Verify both services are running: $ kubectl get services You should see app1-svc and app2-svc listed. 4. Set Up DNS Records 1. Log in to your DNS provider's account and access your domain. 2. Create a new A subdomain record app1 pointing to your load balancer's external IP address. 3. Create another A subdomain record app2 pointing to the same IP address. 5. Configure the Nginx Ingress Controller to Expose the Backend Applications 1. Create a manifest for an Ingress resource for the app1 Deployment: $ sudo nano app1-ingress.yaml Add the following: apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ingress-app1 annotations: cert-manager.io/issuer: letsencrypt-nginx spec: ingressClassName: nginx rules: - host: app1.example.com http: paths: - pathType: Prefix path: "/" backend: service: name: app1-svc port: number: 80 Replace app1.example.com with your domain name. Save and close the file. 2. Create a manifest for an Ingress resource for the app2 Deployment: $ sudo nano app2-ingress.yaml Add the following: apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ingress-app2 annotations: cert-manager.io/issuer: letsencrypt-nginx spec: ingressClassName: nginx rules: - host: app2.example.com http: paths: - pathType: Prefix path: "/" backend: service: name: app2-svc port: number: 80 Replace app2.example.com with your domain name. Save and close the file. 3. Apply the ingress-app1 resource: $ kubectl apply -f app1-ingress.yaml 4. Apply the ingress-app2 resource: $ kubectl apply -f app2-ingress.yaml 5. Verify the Ingress resources are available: $ kubectl get ingress You should see ingress-app1 and ingress-app2 listed. Wait for the ADDRESS column to populate with your load balancer's IP address. 6. Set Up Production-Ready SSL Certificates cert-manager creates Custom Resource Definitions (CRDs) to handle certificate issuance from a CA such as Let's Encrypt: Issuer: Defines an issuer configuration (e.g., ACME), the challenge method (DNS01 or HTTP01), and credentials, scoped to a single namespace. Cluster Issuer: Works like Issuer but can issue certificates to any namespace in the cluster. Certificate: A namespaced resource describing the desired TLS certificate — domain names, the secret to store it in, and the Issuer/ClusterIssuer to use. 1. Inspect the available CRDs: $ kubectl get crd -l app.kubernetes.io/name=cert-manager You should see resources such as issuers.cert-manager.io, certificates.cert-manager.io, and clusterissuers.cert-manager.io. Set Up Let's Encrypt Certificates 1. Create an issuer manifest: $ nano cert-issuer.yaml Add the following: apiVersion: cert-manager.io/v1 kind: Issuer metadata: name: letsencrypt-nginx spec: acme: email: hello@example.com server: https://acme-v02.api.letsencrypt.org/directory privateKeySecretRef: name: letsencrypt-nginx-prod solvers: - http01: ingress: class: nginx Replace the email with a real, active address — @example.com addresses will keep the Issuer from becoming active. Save and close the file. 2. Apply the Issuer resource: $ kubectl apply -f cert-issuer.yaml 3. Verify the Issuer is ready: $ kubectl get issuer Output: NAME READY AGE letsencrypt-nginx False 4s The READY status changes to True after some time. 4. Check the state of your Ingress resources: $ kubectl get ingress 5. Edit app1-ingress.yaml: $ nano app1-ingress.yaml Add a spec.tls section: tls: - hosts: - app1.example.com secretName: letsencrypt-nginx-app1 The full manifest should look like: apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ingress-app1 annotations: cert-manager.io/issuer: letsencrypt-nginx spec: ingressClassName: nginx rules: - host: app1.example.com http: paths: - pathType: Prefix path: "/" backend: service: name: app1-svc port: number: 80 tls: - hosts: - app1.example.com secretName: letsencrypt-nginx-app1 Save and close the file. 6. Add the equivalent tls block to app2-ingress.yaml: tls: - hosts: - app1.example.com secretName: letsencrypt-nginx-app2 7. Apply both configurations to enable TLS: $ kubectl apply -f app1-ingress.yaml $ kubectl apply -f app2-ingress.yaml 8. Verify TLS port 443 is active in the PORTS column: $ kubectl get ingress Output: NAME CLASS HOSTS ADDRESS PORTS AGE app1-ingress nginx app1.example.com 192.0.2.1 80,443 10m app2-ingress nginx app2.example.com 192.0.2.1 80,443 10m 9. Verify the certificate resources: $ kubectl get certificates Output: NAME READY SECRET AGE letsencrypt-app1 True letsencrypt-nginx-app1 5m letsencrypt-app2 True letsencrypt-nginx-app2 5m If READY is True, the certificates propagated successfully. If False, check your Ingress configuration and reapply. Test the SSL Configuration Verify you can reach both services securely over HTTPS: https://app1.example.com https://app2.example.com HTTP requests to either host are automatically redirected to HTTPS. Import Commercial SSL Certificates To use a commercial SSL certificate from a trusted CA, convert the certificate and private key to base64, store them in a Kubernetes secret, and reference that secret from your Ingress resources. 1. Convert the certificate and private key to base64: $ base64 -w 0 /path/ssl-certificate.pem $ base64 -w 0 /path/cert-private-key.pem Replace /path with the actual path to your certificate and private key. Copy both resulting values. 2. Create a Kubernetes secret manifest: $ nano ssl-secret.yaml Add the following: apiVersion: v1 kind: Secret metadata: name: prod-ssl-secret type: kubernetes.io/tls data: tls.crt: <paste-base64-values> tls.key: <paste-base64-values> Paste your base64-encoded certificate into tls.crt and the private key into tls.key. Save and close the file. 3. Reference the prod-ssl-secret secret name in your Ingress resource's spec.tls.secretName field to use the commercial certificate. 7. Troubleshooting Nginx 502 Gateway Error: Verify your Ingress configuration is correct and the referenced services are running. Let's Encrypt certificate stuck with READY = False: Verify your Issuer and Certificate configurations are correct. Confirm you provided a valid email address. Check the Issuer resource logs for more detail. Next Steps Add rate limiting and WAF rules at the Ingress layer for public-facing services. Set up a ClusterIssuer instead of a per-namespace Issuer if you'll issue certificates across multiple namespaces. Configure automatic certificate renewal monitoring and alerting. Layer in canary or blue-green routing using Ingress annotations or a service mesh. For the full guide with additional tips, visit the original article on Vultr Docs.