Set Up Nginx Ingress Controller with SSL on Kubernetes
The Nginx Ingress Controller is one of the most widely used Ingress solutions for Kubernetes. It leverages Nginx as both a reverse proxy and load balancer to securely channel external requests to services inside your cluster. Acting as a central gateway, it provides SSL/TLS termination, load balancing, session persistence, and path-based routing for various applications running in Kubernetes.
In this tutorial, you will configure an Nginx Ingress Controller with SSL certificates inside a Kubernetes Engine cluster. You’ll deploy two sample applications and secure them using Let’s Encrypt certificates managed by Cert Manager. The guide also demonstrates how to integrate commercial SSL certificates for TLS encryption.
Prerequisites
Before proceeding, ensure you have the following ready:
- A running Kubernetes cluster with at least two nodes.
- Kubectl installed for managing your cluster.
- The Helm package manager set up on your local system.
- A valid domain name, for instance example.com.
Install the Nginx Ingress Controller
First, add the Nginx Ingress Helm repository.
$ helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
Update Helm repositories to fetch the latest charts.
$ helm repo update
Install the Nginx Ingress Controller into your Kubernetes environment.
$ helm install ingress-nginx ingress-nginx/ingress-nginx
After installation, a LoadBalancer service is automatically provisioned for the controller.
$ kubectl get services ingress-nginx-controller
Output example:
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 several minutes for the service to obtain an EXTERNAL-IP, depending on your cloud provider.
Install Cert Manager
Deploy the latest release of Cert Manager using the command below.
$ kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.17.2/cert-manager.yaml
You can always check the official Cert Manager release page for newer versions.
Verify Cert Manager resources within the cert-manager namespace.
$ kubectl get all -n cert-manager
You should see several pods, services, replicasets, and deployments related to Cert Manager.
Deploy Backend Applications
Next, you’ll deploy two simple applications—app1 and app2—for testing the Ingress configuration. Both will use the hashicorp/http-echo image that serves a text response on an HTML page.
Create the Deployment for App1
$ nano app1-deploy.yaml
Add the following configuration:
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
Create the Deployment for App2
$ nano app2-deploy.yaml
Insert this configuration:
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
Apply both deployment manifests:
$ kubectl apply -f app1-deploy.yaml
$ kubectl apply -f app2-deploy.yaml
Confirm that the deployments are active:
$ kubectl get deployments
Create Service Manifests
Service for App1
$ nano app1-svc.yaml
Insert the following YAML:
apiVersion: v1
kind: Service
metadata:
  name: app1-svc
spec:
  ports:
    - name: http
      port: 80
      targetPort: 8080
  selector:
    app: app1
Service for App2
$ nano app2-svc.yaml
Add this configuration:
apiVersion: v1
kind: Service
metadata:
  name: app2-svc
spec:
  ports:
    - name: http
      port: 80
      targetPort: 8080
  selector:
    app: app2
Deploy both services:
$ kubectl apply -f app1-svc.yaml
$ kubectl apply -f app2-svc.yaml
Check that all services are running:
$ kubectl get services
Configure DNS Records
Log in to your DNS provider and open your domain management console. Create an A record for app1 pointing to your LoadBalancer’s external IP address. Repeat the process for app2, using the same IP.
Expose Applications via Nginx Ingress Controller
Now create Ingress resources to expose your backend applications publicly.
Ingress for App1
$ nano app1-ingress.yaml
Insert this content:
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
Ingress for App2
$ nano app2-ingress.yaml
Use this YAML content:
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
Apply both Ingress manifests:
$ kubectl apply -f app1-ingress.yaml
$ kubectl apply -f app2-ingress.yaml
Confirm the Ingress resources:
$ kubectl get ingress
You should see both ingress-app1 and ingress-app2 entries. Wait until the ADDRESS column shows your LoadBalancer IP.
Enable SSL Certificates for Nginx Ingress
To enable HTTPS, you can configure Cert Manager to automatically manage SSL certificates for your cluster. Cert Manager extends Kubernetes with custom resources for certificate issuance and lifecycle management from trusted Certificate Authorities like Let’s Encrypt.
- Issuer: Defines configuration for a certificate issuer, such as the type (ACME) and challenge method (DNS01 or HTTP01) within a namespace.
- ClusterIssuer: Similar to Issuer but can issue certificates cluster-wide, suitable for multi-namespace configurations.
- Certificate: Specifies certificate properties including domain names, secret storage, and the chosen issuer.
List available CRDs for Cert Manager using the following command:
$ kubectl get crd -l app.kubernetes.io/name=cert-manager
The output should include resources like issuers.cert-manager.io, certificates.cert-manager.io, and clusterissuers.cert-manager.io, confirming Cert Manager’s CRDs are available.
Set Up Let’s Encrypt Certificates
Create a new Issuer manifest named cert-issuer.yaml to configure Let’s Encrypt certificate management.
$ nano cert-issuer.yaml
Add the following YAML configuration:
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
This configuration defines an ACME Issuer with the following parameters:
- email: The contact email associated with the ACME account. Avoid using @example.comas it will prevent the issuer from becoming active.
- server: The API endpoint for the Let’s Encrypt ACME service.
- privateKeySecretRef: Specifies the Kubernetes Secret where the ACME account’s private key is stored.
- solvers: Defines how Let’s Encrypt validates domain ownership. Typically, the http01method is used unless wildcard certificates are required.
Replace the placeholder email address with your valid one, then save and close the file.
Apply the Issuer resource to your cluster:
$ kubectl apply -f cert-issuer.yaml
Check if the Issuer resource is properly created and initialized:
$ kubectl get issuer
Sample output:
NAME                READY   AGE
letsencrypt-nginx   False   4s
The READY column will initially show False. After a short time, it should change to True.
Configure Ingress Resources for TLS
Next, link your Ingress resources with the Let’s Encrypt certificates.
Check the current state of Ingress resources:
$ kubectl get ingress
Edit the app1-ingress.yaml file to include a TLS section:
$ nano app1-ingress.yaml
Add this snippet under the spec section:
tls:
  - hosts:
    - app1.example.com
    secretName: letsencrypt-nginx-app1
The full manifest now appears as follows:
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
Apply the same TLS section to the app2-ingress.yaml manifest:
tls:
  - hosts:
    - app2.example.com
    secretName: letsencrypt-nginx-app2
Apply the updated manifests to enable TLS on both hosts:
$ kubectl apply -f app1-ingress.yaml
$ kubectl apply -f app2-ingress.yaml
Verify that your Ingress resources have port 443 activated:
$ kubectl get ingress
Example 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
Check Certificate Resources
Confirm that the SSL certificates were generated successfully:
$ kubectl get certificates
Example output:
NAME              READY   SECRET                   AGE
letsencrypt-app1  True    letsencrypt-nginx-app1   5m
letsencrypt-app2  True    letsencrypt-nginx-app2   5m
If the READY column reads True, your certificates are correctly issued. If False, double-check your Ingress configuration and reapply the manifests to renew certificates.
Test the SSL Configuration
Once certificates are active, verify HTTPS access through a browser:
If accessed via HTTP, the Ingress Controller automatically redirects traffic to HTTPS.
Import Commercial SSL Certificates
If you purchased an SSL certificate from a commercial Certificate Authority, you can manually import it. First, convert both the certificate and private key files into base64 format.
$ base64 -w 0 /path/ssl-certificate.pem
$ base64 -w 0 /path/cert-private-key.pem
Replace /path with the actual directory containing your SSL files.
Next, create a Kubernetes Secret manifest:
$ nano ssl-secret.yaml
Insert the following YAML configuration:
apiVersion: v1
kind: Secret
metadata:
  name: prod-ssl-secret
type: kubernetes.io/tls
data:
  tls.crt: 
  tls.key: 
Ensure that:
- tls.crt holds the base64-encoded SSL certificate.
- tls.key contains the base64-encoded private key.
Save the file and apply it. Then, reference this Secret under spec.tls.secretName in your Ingress manifest to activate the commercial SSL certificate.
Troubleshooting
- Nginx 502 Gateway Error: Verify the Ingress rules and ensure all backend services are reachable.
- Let’s Encrypt Certificates not generating (READY = False): Confirm your Issuer and Certificate configurations, use a valid email address, and review the Issuer logs for errors.
Conclusion
In this guide, you successfully installed and configured the Nginx Ingress Controller with SSL on a Kubernetes Engine cluster. Your setup now routes external traffic securely over HTTPS, using either Let’s Encrypt or commercial SSL certificates for encrypted access to your Kubernetes services.


