Skip to main content

Nov 17, 2025

Migrate from ingress-nginx to the ngrok Operator

2,390 words

Topics:

Last week, the Kubernetes SIG Network and the Security Response Committee (SRC) announced the retirement of ingress-nginx. It will receive best-effort support until March 2026, at which point all maintenance will stop. The repository will be made read-only and that’s that. It is recommended you migrate away from ingress-nginx before March 2026.

We provide our own Kubernetes Operator at ngrok and have written in the past about what differentiates the ngrok Operator from others. In this post I’ll show you how to migrate from ingress-nginx to the ngrok operator.

Prerequisites

I’m going to assume you have a cluster with ingress-nginx running in it. For example, here it is in the cluster I’m using for this post.

1$ kubectl -n ingress-nginx get pods2NAME                                       READY   STATUS      RESTARTS   AGE3ingress-nginx-admission-create-dt6q4       0/1     Completed   0          37m4ingress-nginx-admission-patch-6j2q2        0/1     Completed   1          37m5ingress-nginx-controller-9cc49f96f-vcjq2   1/1     Running     0          37m

And to demonstrate that things are working correctly, I’ve created a small deployment using the testcontainers/helloworld image:

1apiVersion: apps/v12kind: Deployment3metadata:4  name: helloworld5  labels:6    app: helloworld7spec:8  replicas: 19  selector:10    matchLabels:11      app: helloworld12  template:13    metadata:14      labels:15        app: helloworld16    spec:17      containers:18        - name: helloworld19          image: testcontainers/helloworld:1.3.0-linux20          ports:21            - containerPort: 808022          readinessProbe:23            httpGet:24              path: /ping25              port: 808026            initialDelaySeconds: 527            periodSeconds: 528          livenessProbe:29            httpGet:30              path: /ping31              port: 808032            initialDelaySeconds: 1033            periodSeconds: 1034---35apiVersion: v136kind: Service37metadata:38  name: helloworld39  labels:40    app: helloworld41spec:42  selector:43    app: helloworld44  ports:45    - name: http46      port: 8047      targetPort: 808048      protocol: TCP49  type: ClusterIP50---51apiVersion: networking.k8s.io/v152kind: Ingress53metadata:54  name: helloworld55spec:56  ingressClassName: nginx57  rules:58    - host: test.samwho.dev59      http:60        paths:61          - path: /62            pathType: Prefix63            backend:64              service:65                name: helloworld66                port:67                  number: 80

If I request http://test.samwho.dev/ping using httpie, I see a PONG response:

1$ http test.samwho.dev/ping2HTTP/1.1 200 OK3Connection: keep-alive4Content-Length: 45Content-Type: text/plain; charset=utf-86Date: Fri, 14 Nov 2025 14:28:51 GMT78PONG

Sign up for ngrok

To use the ngrok Operator, sign up for an account at ngrok.com. Once you have an account, make note of your authtoken and you need to create an API key. You’ll find your authtoken in the Your Authtoken part of the dashboard. For your API key, go to the API Keys part of the dashboard and click + New API Key then follow the steps.

"Do I need to pay to use the ngrok operator?"

If you’re migrating an existing domain over to ngrok, yes, you will need to be on our pay-as-you-go plan. You can see the details on our pricing page. If you’re not migrating and want to use the free, randomly generated domain we assign to you, you won’t need to pay.

Install the ngrok Operator

The recommended way to install the ngrok Operator is through Helm. Substitute <YOUR_API_KEY> and <YOUR_AUTH_TOKEN> with your API key and authtoken from the previous step.

1$ helm repo add ngrok https://charts.ngrok.com2$ helm install ngrok-operator ngrok/ngrok-operator \3    --namespace ngrok \4    --create-namespace \5    --set credentials.apiKey=<YOUR_API_KEY> \6    --set credentials.authtoken=<YOUR_AUTH_TOKEN>

If all goes well you should see a message that ends like this:

1=== 🎉 Installation Complete 🎉 =========================================================2🎉 Thanks for installing the ngrok-operator!34➡️ Check out the next-steps docs: https://ngrok.com/docs/k8s5📣 Questions or feedback? Open an issue at https://github.com/ngrok/ngrok-operator/issues

And the pods should settle into the Running status:

1$ kubectl -n ngrok get pods2NAME                                     READY   STATUS    RESTARTS   AGE3ngrok-operator-agent-5cbb74c95-t4bhv     1/1     Running   0          29m4ngrok-operator-manager-b6c749988-8qq97   1/1     Running   0          4m38s

"Sam, why is your operator-manager pod newer than your operator-agent pod?"

Funny story. I typed out the helm install command by hand while writing this post (I don’t know what I was thinking) and I tried to --set credentials.authToken=$NGROK_AUTHTOKEN. I didn’t notice that it should actually be credentials.authtoken with a lowercase t. Took me a little while to figure out what I did wrong, and fixing it recreated the pod. Hopefully you don’t trip over the same thing!

Register a domain in ngrok

Before you create an ngrok Ingress resource, you need to register the domain you want to use in ngrok. Head over to the Domains section of the dashboard and register the domain you want to use by clicking the +New Domain button.

Note: You must already own the domain you want to use. ngrok is not a domain registrar, the registration process in ngrok is so that we know how to route traffic to you, it is not the same as buying a domain with a registrar.

I’m making the assumption here that you’re migrating in with a non-ngrok domain. In the Domains page of the ngrok dashboard, you can register both custom domains (e.g. test.samwho.dev) as well as ngrok domains (e.g. test.ngrok.io). This migration guide covers the process of migrating a non-ngrok domain into ngrok.

Without a certificate (involves downtime)

The easiest way to migrate is to register a domain and get ngrok to automatically provision a certificate for you. However, doing this will result in a short period of downtime. In my testing, this lasted about 5 minutes. This is because ngrok uses Let’s Encrypt to provision certificates, and it needs your DNS name to point at ngrok to pass the ACME HTTP01 challenge. While the certificate is being provisioned, your domain will be considered insecure and most clients/browsers will refuse to connect to it.

The next section will cover how to avoid this downtime, but the remainder of this section will focus on creating a domain with an ngrok-managed certificate.

Fill in the domain creation form with your desired domain name, and leave everything else default. Here’s what it looked like for me:

When you’ve created the domain, you’ll be prompted to set the domain’s DNS record to be a CNAME pointing at ngrok. I recommend holding off from doing this right now, because when you do it you’ll break the domain for users until you’ve created your new Ingress resource. You’ll make the DNS switch as the last step of this migration process.

With a certificate (no downtime)

To avoid any downtime, you need to create a domain in ngrok with a certificate. After migrating, you’ll be able to switch to a certificate managed by ngrok without incurring any downtime. This step is just required to avoid downtime during the migration.

To demonstrate, I’ve configured my cluster to use cert-manager and I’ve issued a certificate for test.samwho.dev with Let’s Encrypt. My helloworld Ingress resource now looks like this:

1apiVersion: networking.k8s.io/v12kind: Ingress3metadata:4  name: helloworld5  annotations:6    cert-manager.io/cluster-issuer: letsencrypt-prod7spec:8  ingressClassName: nginx9  rules:10    - host: test.samwho.dev11      http:12        paths:13          - path: /14            pathType: Prefix15            backend:16              service:17                name: helloworld18                port:19                  number: 8020  tls:21    - hosts:22        - test.samwho.dev23      secretName: letsencrypt-prod

I can verify it’s setup correctly by looking at the TLS section of the Ingress resource:

1$ kubectl get ingress helloworld -o jsonpath='{.spec.tls}'2[{"hosts":["test.samwho.dev"],"secretName":"letsencrypt-prod"}]

ngrok ask for 2 pieces of information when setting up a domain with your own certificate: the certificate itself, and the certificate private key. I can grab both of these from the secret that cert-manager created for my Ingress, which is called letsencrypt-prod as seen above. You should treat these as sensitive information and handle them securely.

1$ kubectl get secrets letsencrypt-prod -o jsonpath='{.data.tls\.crt}' | base64 -d2$ kubectl get secrets letsencrypt-prod -o jsonpath='{.data.tls\.key}' | base64 -d

If you’re using a different certificate manager, the way that you get this information may vary. You will know if you have the right values because the certificate looks like this:

1-----BEGIN CERTIFICATE-----2<loads of gibberish>3-----END CERTIFICATE-----

And the private key looks like this:

1-----BEGIN PRIVATE KEY-----2<loads of gibberish>3-----END PRIVATE KEY-----

With these in hand, navigate to the TLS certificates section of the ngrok dashboard and click +Create TLS Certificate. You’ll see a dialog that looks like this:

Put your certificate and private key in the appropriate boxes. In the description field, I recommend putting the domain name that the certificate applies to. In my case test.samwho.dev. Click save when you’re done.

Now to register the domain. Head over to the Domains section of the ngrok dashboard and click +Create Domain. Click on the certificate dropdown and select “Upload Certificate”. You should see your certificate:

Click save, and you’ll be shown instructions for setting up the CNAME record for your domain. Don’t do this just yet. You need to set up the ngrok Ingress resource first, which we’ll do next.

Create an ngrok Ingress

Rather than updating the existing Ingress resource, you’ll create a new one next to it that uses the ngrok ingress class. Here’s the Ingress resource I used in my cluster:

1apiVersion: networking.k8s.io/v12kind: Ingress3metadata:4  name: helloworld-ngrok5spec:6  ingressClassName: ngrok7  rules:8    - host: test.samwho.dev9      http:10        paths:11          - path: /12            pathType: Prefix13            backend:14              service:15                name: helloworld16                port:17                  number: 80

Save this as ngrok-ingress.yaml and apply it:

1$ kubectl apply -f ngrok-ingress.yaml

When the ngrok Operator picks up this Ingress, it will assign it an ngrok-cname.com address like so:

1$ kubectl get ingress2NAME               CLASS   HOSTS             ADDRESS                                               PORTS   AGE3helloworld         nginx   test.samwho.dev   192.168.49.2                                          80      41m4helloworld-ngrok   ngrok   test.samwho.dev   2awrvpxvfbuzgneam.5nqklokassiaw4xte.ngrok-cname.com   80      20m

That 2awrvpxvfbuzgneam.5nqklokassiaw4xte.ngrok-cname.com address is my ngrok-cname.com address, yours will be a different, unique value. You’ll use it later to update your DNS record.

Troubleshooting

If you don’t see an ngrok-cname.com adress, for example your “Address” field is empty, there are a few debugging steps you can follow to figure out the problem.

Your first port of call should be the Operator logs.

1$ kubectl -n ngrok get pods2NAME                                      READY   STATUS    RESTARTS   AGE3ngrok-operator-agent-5cbb74c95-6fhnm      1/1     Running   0          2d17h4ngrok-operator-manager-74b889db7c-8fbbw   1/1     Running   0          2d17h
1$ kubectl -n ngrok logs ngrok-operator-manager-74b889db7c-8fbbw --all-containers -f2$ kubectl -n ngrok logs ngrok-operator-agent-5cbb74c95-6fhnm --all-containers -f

These logs can be quite noisy, and you’re likely to see errors like “the object has been modified” but these are a normal part of the reconciliation model. If the logs don’t yield anything useful, we also put debugging information into the ngrok Operator’s resources. For example, “agent endpoints” are the connections the Operator makes to ngrok, that ngrok later uses to send traffic to your application. If these have failed to create, you can check their status:

1$ kubectl get agentendpoints2NAME                          URL                       UPSTREAM URL                   BINDINGS   READY   AGE3c3fe2-helloworld-default-80   https://test.samwho.dev   http://helloworld.default:80              True    28m

Then getting more detail about a specific endpoint:

1$ kubectl get agentendpoint c3fe2-helloworld-default-80 -o yaml

If something has gone wrong creating the endpoint, there will be an error message about it in this output. For example, early on in this post I had accidentally installed the ngrok operator with an authtoken from my work account and an API key from my personal account. The error message I found in my agentendpoint was:

1Failed to create endpoint: This domain is reserved for another account. Failed to create an endpoint with the domain 'test.samwho.dev' for the account 'Sam Rose'. If you have reserved this name, make sure that you are using an authtoken credential for the appropriate account. Reserve a name on your dashboard: https://dashboard.ngrok.com/domains/new ERR_NGROK_320

This gave me the clue I needed to realise that I had used the wrong API key. If you’re deeply stuck and can’t figure out what’s wrong, you’re welcome to file an issue and we’ll try to help you out.

Switch DNS

Now is the time to change your DNS over to point at ngrok. Every DNS registrar will have a different way to do this, but the end result should be that your domain is a CNAME record that points to your <gibberish>.ngrok-cname.com address. To find it, run:

1$ kubectl get ingress2NAME               CLASS   HOSTS             ADDRESS                                               PORTS     AGE3helloworld         nginx   test.samwho.dev   192.168.49.2                                          80, 443   2d20h4helloworld-ngrok   ngrok   test.samwho.dev   2awrvpxvfbuzgneam.5nqklokassiaw4xte.ngrok-cname.com   80        3h41m

Here, 2awrvpxvfbuzgneam.5nqklokassiaw4xte.ngrok-cname.com is the address I should use when setting up my CNAME. Yours will be different, but will end with .ngrok-cname.com.

For folks who have opted to have ngrok automatically manage their certificates, the moment you change over to the ngrok CNAME your clients will start seeing that your domain is insecure. This will persist until ngrok provisions a certificate for the domain, which usually takes a few minutes. You can check the status of this process by clicking on the domain in your dashboard:

When your domain looks like the above, with a certificate ID near the bottom, the downtime should stop.

For those who created a TLS certificate and attached it to the domain, clients will seamlessly transition without noticing any change.

When all has settled you will be able to send a request and get a response as expected:

1$ http https://test.samwho.dev/ping2HTTP/1.1 200 OK3Content-Length: 44Content-Type: text/plain; charset=utf-85Date: Fri, 14 Nov 2025 17:56:36 GMT67PONG

You can verify that this is going through ngrok by checking Traffic Inspector in your dashboard:

You can now clean up any old nginx Ingress resources, your traffic is flowing through ngrok. 🎉

Switch over to ngrok managed certificates

If you set up your own certificate during domain creation, you can now change to having ngrok manage your certificates for you. Head to the Domains page and switch to “Automated TLS certificates” like so:

ngrok will provision a certificate in the background, making sure to keep using your supplied certificate until the new one is ready. This ensures a seamless transition without downtime, and the job of making sure the certificate is valid and renewed is taken care of by ngrok.

Conclusion

The ngrok Operator makes it easy to manage ngrok endpoints inside Kubernetes clusters, and could be a great choice for you or your company if you’re looking to migrate from ingress-nginx.

If you have any questions about what you’ve read, or you’ve tried to migrate and you’re stuck, please visit our support page or email us at support@ngrok.com.