Nov 17, 2025
By Sam Rose
Nov 17, 2025
By Sam Rose
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 are listed as a recommended ingress controller on the Kubernetes website. We've 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.
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.
$ kubectl -n ingress-nginx get pods
NAME READY STATUS RESTARTS AGE
ingress-nginx-admission-create-dt6q4 0/1 Completed 0 37m
ingress-nginx-admission-patch-6j2q2 0/1 Completed 1 37m
ingress-nginx-controller-9cc49f96f-vcjq2 1/1 Running 0 37mAnd to demonstrate that things are working correctly, I've created a small
deployment using the testcontainers/helloworld image:
apiVersion: apps/v1
kind: Deployment
metadata:
name: helloworld
labels:
app: helloworld
spec:
replicas: 1
selector:
matchLabels:
app: helloworld
template:
metadata:
labels:
app: helloworld
spec:
containers:
- name: helloworld
image: testcontainers/helloworld:1.3.0-linux
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /ping
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
livenessProbe:
httpGet:
path: /ping
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: helloworld
labels:
app: helloworld
spec:
selector:
app: helloworld
ports:
- name: http
port: 80
targetPort: 8080
protocol: TCP
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: helloworld
spec:
ingressClassName: nginx
rules:
- host: test.samwho.dev
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: helloworld
port:
number: 80If I request http://test.samwho.dev/ping using httpie, I see a PONG
response:
$ http test.samwho.dev/ping
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 4
Content-Type: text/plain; charset=utf-8
Date: Fri, 14 Nov 2025 14:28:51 GMT
PONGTo 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.
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.
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.
$ helm repo add ngrok https://charts.ngrok.com
$ helm install ngrok-operator ngrok/ngrok-operator \
--namespace ngrok \
--create-namespace \
--set credentials.apiKey=<YOUR_API_KEY> \
--set credentials.authtoken=<YOUR_AUTH_TOKEN>If all goes well you should see a message that ends like this:
=== 🎉 Installation Complete 🎉 =========================================================
🎉 Thanks for installing the ngrok-operator!
➡️ Check out the next-steps docs: https://ngrok.com/docs/k8s/installation/install/
📣 Questions or feedback? Open an issue at https://github.com/ngrok/ngrok-operator/issuesAnd the pods should settle into the Running status:
$ kubectl -n ngrok get pods
NAME READY STATUS RESTARTS AGE
ngrok-operator-agent-5cbb74c95-t4bhv 1/1 Running 0 29m
ngrok-operator-manager-b6c749988-8qq97 1/1 Running 0 4m38sFunny 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!
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.
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.
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:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: helloworld
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx
rules:
- host: test.samwho.dev
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: helloworld
port:
number: 80
tls:
- hosts:
- test.samwho.dev
secretName: letsencrypt-prodI can verify it's setup correctly by looking at the TLS section of the Ingress resource:
$ kubectl get ingress helloworld -o jsonpath='{.spec.tls}'
[{"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.
$ kubectl get secrets letsencrypt-prod -o jsonpath='{.data.tls\.crt}' | base64 -d
$ kubectl get secrets letsencrypt-prod -o jsonpath='{.data.tls\.key}' | base64 -dIf 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:
-----BEGIN CERTIFICATE-----
<loads of gibberish>
-----END CERTIFICATE-----And the private key looks like this:
-----BEGIN PRIVATE KEY-----
<loads of gibberish>
-----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.
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:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: helloworld-ngrok
spec:
ingressClassName: ngrok
rules:
- host: test.samwho.dev
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: helloworld
port:
number: 80Save this as ngrok-ingress.yaml and apply it:
$ kubectl apply -f ngrok-ingress.yamlWhen the ngrok Operator picks up this Ingress, it will assign it an
ngrok-cname.com address like so:
$ kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
helloworld nginx test.samwho.dev 192.168.49.2 80 41m
helloworld-ngrok ngrok test.samwho.dev 2awrvpxvfbuzgneam.5nqklokassiaw4xte.ngrok-cname.com 80 20mThat 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.
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.
$ kubectl -n ngrok get pods
NAME READY STATUS RESTARTS AGE
ngrok-operator-agent-5cbb74c95-6fhnm 1/1 Running 0 2d17h
ngrok-operator-manager-74b889db7c-8fbbw 1/1 Running 0 2d17h$ kubectl -n ngrok logs ngrok-operator-manager-74b889db7c-8fbbw --all-containers -f
$ kubectl -n ngrok logs ngrok-operator-agent-5cbb74c95-6fhnm --all-containers -fThese 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:
$ kubectl get agentendpoints
NAME URL UPSTREAM URL BINDINGS READY AGE
c3fe2-helloworld-default-80 https://test.samwho.dev http://helloworld.default:80 True 28mThen getting more detail about a specific endpoint:
$ kubectl get agentendpoint c3fe2-helloworld-default-80 -o yamlIf 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:
Failed 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_320This 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.
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:
$ kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
helloworld nginx test.samwho.dev 192.168.49.2 80, 443 2d20h
helloworld-ngrok ngrok test.samwho.dev 2awrvpxvfbuzgneam.5nqklokassiaw4xte.ngrok-cname.com 80 3h41mHere, 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:
$ http https://test.samwho.dev/ping
HTTP/1.1 200 OK
Content-Length: 4
Content-Type: text/plain; charset=utf-8
Date: Fri, 14 Nov 2025 17:56:36 GMT
PONGYou 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. 🎉
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.
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.