Skip to content

Bring Your Own Reverse Proxy ​

This builds on the Getting Started guide, and it is recommended to read that first.

This example configures cloudflared to send all traffic to a reverse proxy, and then configure routing in the reverse proxy.

Motivation ​

Cloudflare tunnels can forward traffic to an existing reverse proxy. This is useful when:

  • You have significant existing reverse proxy configuration
  • You require reverse proxy functionality that cloudflared does not have
  • You want to keep reverse proxy configuration modular
  • You use a service mesh with its own ingress implementation
  • You have multiple load balancers pointing at your ingress (e.g., metallb + DNS entries for same-network access)

What we'll set up ​

  1. Deploy cloudflare-operator and configure a cluster tunnel
  2. Deploy ingress-nginx as a reference reverse proxy, pointing the tunnel to it
  3. Deploy an example application behind nginx with an ingress resource

TIP

We use a ClusterTunnel in this example, but a namespaced Tunnel works the same way. We also skip HTTPS with the reverse proxy for simplicity — commented snippets are included to enable it.

Prerequisites ​

  1. kubectl is installed
  2. kustomize is installed
  3. helm is installed
  4. Authentication secret deployed
  5. Cloudflare-operator installed
  6. Tunnel/ClusterTunnel deployed

Manifests ​

Cloudflare Operator resources ​

yaml
apiVersion: networking.cfargotunnel.com/v1alpha2
kind: ClusterTunnel
metadata:
  name: example-tunnel
spec:
  newTunnel:
    name: my-k8s-tunnel
  cloudflare:
    email: email@example.com
    domain: example.com
    secret: cloudflare-secrets
    # accountId and accountName cannot be both empty.
    # If both are provided, Account ID is used if valid, else falls back to Account Name.
    accountName: <Cloudflare account name>
    accountId: <Cloudflare account ID>
yaml
apiVersion: networking.cfargotunnel.com/v1alpha1
kind: TunnelBinding
metadata:
  name: ingress-nginx
subjects:
  # this example assumes you are exposing services with your reverse proxy without certificates.
  # In general this is not recommended
  - name: wildcard
    spec:
      fqdn: "*.<domain>"
      target: http://ingress-nginx-controller.ingress-nginx.svc.cluster.local:80
      noTlsVerify: true

#  # this example assumes you are generating certificates for your reverse proxy using e.g. cert-manager
#  # you should use only one of the following two configurations, not both.
#  - name: wildcard
#    spec:
#      fqdn: "*.<domain>"
#      target: https://ingress-nginx-controller.ingress-nginx.svc.cluster.local:443

tunnelRef:
  kind: ClusterTunnel
  name: example-tunnel

Ingress-nginx (reverse proxy) ​

yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: ingress-nginx

resources:
  - resources/namespace.yaml

helmCharts:
  - includeCRDs: true
    name: ingress-nginx
    namespace: ingress-nginx
    releaseName: ingress-nginx
    repo: https://kubernetes.github.io/ingress-nginx
    valuesFile: values.yaml
    version: 4.12.1
yaml
apiVersion: v1
kind: Namespace
metadata:
  name: ingress-nginx
spec: {}
yaml
defaultBackend:
  enabled: true

controller:
  service:
    type: ClusterIP

#   # this config allows your services to read the IP address from connecting clients
#   config:
#     enable-real-ip: "true"                     # tell nginx to trust the header we pick up from Cloudflare
#     forwarded-for-header: "CF-Connecting-IP"   # remap this header (from CF tunnel) to x-forwarded-for
#     use-forwarded-headers: "true"              # keep/pass X-Forwarded-*
#     # trust setting the x-forwarded-for header from all pods in k8s.
#     # Ideally this would be only the CF tunnel pods.
#     proxy-real-ip-cidr: <your-cluster-pod-cidr>

Example application (hello) ​

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-demo
  labels:
    app: hello-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: hello-demo
  template:
    metadata:
      labels:
        app: hello-demo
    spec:
      containers:
        - name: hello
          image: nginxdemos/hello:latest
          ports:
            - containerPort: 80
          readinessProbe:
            httpGet:
              path: /
              port: 80
            initialDelaySeconds: 3
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /
              port: 80
            initialDelaySeconds: 10
            periodSeconds: 30
yaml
apiVersion: v1
kind: Service
metadata:
  name: hello-demo
  labels:
    app: hello-demo
spec:
  type: ClusterIP
  selector:
    app: hello-demo
  ports:
    - name: http
      port: 80
      targetPort: 80
yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: hello-demo
  annotations: {}
#    # uncomment if you are using cert-manager for TLS
#    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  ingressClassName: nginx
  rules:
    - host: hello.<domain>
      http:
        paths:
          - path: /(.*)
            pathType: Prefix
            backend:
              service:
                name: hello-svc
                port:
                  number: 80
#  # optional: enable if you have cert manager or TLS secret
#  tls:
#    - hosts:
#        - hello.example.com
#      secretName: hello-tls

Steps ​

  1. Replace all <like-this> placeholder values in the manifests above.

  2. Deploy the TunnelBinding:

    shell
    kubectl apply -f tunnel-binding.yaml
  3. Deploy the reverse proxy:

    shell
    kustomize build --enable-helm ingress-nginx/ | kubectl apply -f -
  4. Deploy the example application:

    shell
    kubectl apply -f hello/
  5. Access the service on hello.<domain>

Extending this example ​

Custom SSO ​

Example using ingress-nginx and authelia:

yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: <app>
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/auth-method: "GET"
    nginx.ingress.kubernetes.io/auth-url: "http://authelia.authelia.svc.cluster.local:8080/api/authz/auth-request"
    nginx.ingress.kubernetes.io/auth-signin: "https://authelia.<domain>?rm=$request_method"
    nginx.ingress.kubernetes.io/auth-response-headers: "Remote-User,Remote-Name,Remote-Groups,Remote-Email"

Using cloudflared for routing alongside ingress-nginx ​

You can add routes that bypass the reverse proxy when needed:

yaml
apiVersion: networking.cfargotunnel.com/v1alpha1
kind: TunnelBinding
metadata:
  name: authelia
  namespace: authelia
subjects:
  - name: authelia
tunnelRef:
  kind: ClusterTunnel
  name: example-tunnel
---
apiVersion: networking.cfargotunnel.com/v1alpha1
kind: TunnelBinding
metadata:
  # Prefix with zz- to ensure this is the last route in cloudflared's config
  # (cloudflared processes routes in order)
  name: zz-ingress-nginx
subjects:
  - name: wildcard
    spec:
      fqdn: "*.<domain>"
      target: https://ingress-nginx-controller.ingress-nginx.svc.cluster.local:443
tunnelRef:
  kind: ClusterTunnel
  name: example-tunnel

Released under the Apache-2.0 License.