Introduction

In this guide, we will learn how to expose a Kubernetes application securely to the internet using Cloudflare Tunnel and Nginx Ingress. This setup allows you to leverage Cloudflare’s security features while managing your application traffic efficiently.

intro-expose-k8s-apps-to-internet.png

We are going to use:

  • Cloudflare Tunnel to expose our application securely to the internet.
  • Kubernetes Nginx Ingress to route traffic to our application.

Prerequisites

  • A Cloudflare account with the domain added.
  • A Kubernetes cluster set up with Nginx Ingress Controller installed.
  • Root or sudo access to the Kubernetes cluster.

Deployment Guide

Step 1: Install Cloudflare Tunnel

sudo mkdir -p --mode=0755 /usr/share/keyrings
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
echo "deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main" | sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt-get update && sudo apt-get install cloudflared

Step 2: Authenticate Cloudflare Tunnel

sudo cloudflared tunnel login

Don’t worry, if you see a login url in the server terminal, just copy it and paste it in your personal browser. After logging in, you will see a success message in the server terminal.


Step 3: Create a Tunnel

sudo cloudflared tunnel create <tunnel-name>

Example:

sudo cloudflared tunnel create nginx-tunnel

Once the tunnel is created, you will see a message with the tunnel ID and a certificate file path.

Example output:

vijay@controlplane:~$ cloudflared tunnel create nginx-tunnel
Tunnel credentials written to /root/.cloudflared/lmnop-0ce8-efgh-8c67-abcd.json. cloudflared chose this file based on where your origin certificate was found. Keep this file secret. To revoke these credentials, delete the tunnel.

Created tunnel nginx-tunnel with id lmnop-0ce8-efgh-8c67-abcd

You can check the list of tunnels created by running:

sudo cloudflared tunnel list
sudo cloudflared tunnel info nginx-tunnel

Copy the tunnel ID and certificate file path for later use. I would recommend renaming the certificate file to something more meaningful, like nginx-tunnel-credential.json.

sudo mkdir /home/vijay/.cloudflared && sudo chown vijay:vijay /home/vijay/.cloudflared && sudo chmod 700 /home/vijay/.cloudflared && sudo chmod 600 /home/vijay/.cloudflared/*
sudo cp /root/.cloudflared/lmnop-0ce8-efgh-8c67-abcd.json /home/vijay/.cloudflared/nginx-tunnel-credential.json

Step 4: Create a Kubernetes Secret for Cloudflare Tunnel Credentials

I am creating a secret in the cloudflare namespace, but you can create it in any namespace you prefer. Make sure to update the ConfigMap and Deployment accordingly. Feel free to change the path to the credential file if you have it in a different location.

kubectl create namespace cloudflare

kubectl -n cloudflare create secret generic cloudflared-creds \
 --from-file=nginx-tunnel-credential.json=/home/vijay/.cloudflared/nginx-tunnel-credential.json

The next step is to create a ConfigMap with the Cloudflare Tunnel configuration.


Step 5: Create a Kubernetes ConfigMap for Cloudflare Tunnel

# cloudflared-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: cloudflared-config
  namespace: cloudflare
data:
  config.yaml: |
    tunnel: nginx-tunnel
    credentials-file: /etc/cloudflared/creds/nginx-tunnel-credential.json
    ingress:
      - hostname: '*.yourdomain.com'
        originRequest:
          noTLSVerify: true
        service: https://ingress-nginx-controller.ingress-nginx.svc.cluster.local:443
      - service: http_status:404

Explanation:

  • tunnel: The name of the tunnel you created.
  • credentials-file: Don’t be confused with the path to the credential file, this is the path inside the container where the credential file will be mounted.
  • ingress: This section defines the routing rules for the tunnel.
    • hostname: The domain or subdomain you want to expose. Replace yourdomain.com with your actual domain.
    • originRequest.noTLSVerify: Set to true to skip TLS verification for the service.
    • service: The service URL that the tunnel will route traffic to. In this case, it routes to the Nginx Ingress Controller service which is typically named ingress-nginx-controller in the ingress-nginx namespace. Adjust the service name and namespace as per your setup.
    • http_status:404: This is a catch-all rule that returns a 404 status for any unmatched requests.

Step 6: Create Deployment for Cloudflare Tunnel

# cloudflared-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: cloudflared
  namespace: cloudflare
spec:
  replicas: 1
  selector:
    matchLabels:
      app: cloudflared
  template:
    metadata:
      labels:
        app: cloudflared
    spec:
      containers:
        - name: cloudflared
          image: cloudflare/cloudflared:2025.6.1
          args: ["tunnel", "--config", "/etc/cloudflared/config.yaml", "run"]
          volumeMounts:
            - name: config
              mountPath: /etc/cloudflared/config.yaml
              subPath: config.yaml
            - name: creds
              mountPath: /etc/cloudflared/creds
      volumes:
        - name: config
          configMap:
            name: cloudflared-config
        - name: creds
          secret:
            secretName: cloudflared-creds

Explanation:

  • args: The command to run the Cloudflare Tunnel with the specified configuration file.
  • volumeMounts: Mounts the ConfigMap and secret containing the Cloudflare Tunnel configuration and credentials. This is the path above ConfigMap uses /etc/cloudflared/creds/nginx-tunnel-credential.json.

Step 7: Apply the ConfigMap and Deployment

kubectl apply -f cloudflared-config.yaml
kubectl apply -f cloudflared-deployment.yaml

Make sure your Secret, ConfigMap, and Deployment are in the same namespace or adjust the commands accordingly. Otherwise, your Deployment won’t be able to access the ConfigMap and Secret.


Step 8: CNAME Configuration in Cloudflare

Go to your Cloudflare dashboard and navigate to the DNS settings for your domain. Create a CNAME record for the subdomain you want to expose, pointing it to *.yourdomain.com.

In the Target field, enter the tunnel-id.cfargotunnel.com Example: lmnop-0ce8-efgh-8c67-abcd.cfargotunnel.com

cloudflare-tunnel-cname-setup.png

Without this step, the Cloudflare Tunnel won’t be able to route traffic to your application.


Step 9: Expose Nginx Deployment using Ingress

kubectl create namespace nginx
kubectl -n nginx create deployment nginx --image=nginx:alpine
kubectl -n nginx expose deployment nginx --port 80

Step 10: Create an Ingress Resource

kubectl -n nginx create ingress nginx-ingress \
  --rule="nginx.yourdomain.com/*=nginx:80"

Step 11: Verify the Ingress Resource

kubectl -n nginx get ingress nginx-ingress

You should see the Ingress resource with the hostname and backend service.

Whola! Your application is now exposed securely to the internet.

expose-k8s-apps-to-internet.png


Conclusion

In this guide, we have successfully exposed a Kubernetes application securely to the internet using Cloudflare Tunnel and Nginx Ingress. This setup allows you to leverage Cloudflare’s security features while managing your application traffic efficiently.