diff --git a/go.mod b/go.mod
index e749ee76..7a9b0c4e 100644
--- a/go.mod
+++ b/go.mod
@@ -13,7 +13,7 @@ require (
 	github.com/onsi/gomega v1.27.10
 	github.com/spf13/cobra v1.7.0
 	github.com/stretchr/testify v1.8.4
-	golang.ngrok.com/ngrok v1.7.0
+	golang.ngrok.com/ngrok v1.9.1
 	golang.org/x/exp v0.0.0-20231006140011-7918f672742d
 	golang.org/x/sync v0.5.0
 	k8s.io/api v0.28.3
diff --git a/go.sum b/go.sum
index af5ae9d7..92275a63 100644
--- a/go.sum
+++ b/go.sum
@@ -582,6 +582,8 @@ golang.ngrok.com/muxado/v2 v2.0.0 h1:bu9eIDhRdYNtIXNnqat/HyMeHYOAbUH55ebD7gTvW6c
 golang.ngrok.com/muxado/v2 v2.0.0/go.mod h1:wzxJYX4xiAtmwumzL+QsukVwFRXmPNv86vB8RPpOxyM=
 golang.ngrok.com/ngrok v1.7.0 h1:xwcr8QWue+ehgn54hdQwTya4B6A1qXg6+IRim6WINmA=
 golang.ngrok.com/ngrok v1.7.0/go.mod h1:ruVcXZ7Rre5O9oeqqa8uZCB3Xtkt2PoyjF3eW9b7t6A=
+golang.ngrok.com/ngrok v1.9.1 h1:hZCZ7E0t4Jhf3m3AB7YZKSZKH5lEZ5Q6C+T2hlkt8jE=
+golang.ngrok.com/ngrok v1.9.1/go.mod h1:DrWT2BcTdcnHMsP/bHEIP/Ebs0pN5VVYDpbZ3bWrwY4=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
diff --git a/internal/mocks/conn.go b/internal/mocks/conn.go
deleted file mode 100644
index f3343148..00000000
--- a/internal/mocks/conn.go
+++ /dev/null
@@ -1,149 +0,0 @@
-// Code generated by MockGen. DO NOT EDIT.
-// Source: net (interfaces: Conn)
-
-// Package mocks is a generated GoMock package.
-package mocks
-
-import (
-	gomock "github.com/golang/mock/gomock"
-	net "net"
-	reflect "reflect"
-	time "time"
-)
-
-// MockConn is a mock of Conn interface
-type MockConn struct {
-	ctrl     *gomock.Controller
-	recorder *MockConnMockRecorder
-}
-
-// MockConnMockRecorder is the mock recorder for MockConn
-type MockConnMockRecorder struct {
-	mock *MockConn
-}
-
-// NewMockConn creates a new mock instance
-func NewMockConn(ctrl *gomock.Controller) *MockConn {
-	mock := &MockConn{ctrl: ctrl}
-	mock.recorder = &MockConnMockRecorder{mock}
-	return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use
-func (m *MockConn) EXPECT() *MockConnMockRecorder {
-	return m.recorder
-}
-
-// Close mocks base method
-func (m *MockConn) Close() error {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "Close")
-	ret0, _ := ret[0].(error)
-	return ret0
-}
-
-// Close indicates an expected call of Close
-func (mr *MockConnMockRecorder) Close() *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockConn)(nil).Close))
-}
-
-// LocalAddr mocks base method
-func (m *MockConn) LocalAddr() net.Addr {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "LocalAddr")
-	ret0, _ := ret[0].(net.Addr)
-	return ret0
-}
-
-// LocalAddr indicates an expected call of LocalAddr
-func (mr *MockConnMockRecorder) LocalAddr() *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LocalAddr", reflect.TypeOf((*MockConn)(nil).LocalAddr))
-}
-
-// Read mocks base method
-func (m *MockConn) Read(arg0 []byte) (int, error) {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "Read", arg0)
-	ret0, _ := ret[0].(int)
-	ret1, _ := ret[1].(error)
-	return ret0, ret1
-}
-
-// Read indicates an expected call of Read
-func (mr *MockConnMockRecorder) Read(arg0 interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockConn)(nil).Read), arg0)
-}
-
-// RemoteAddr mocks base method
-func (m *MockConn) RemoteAddr() net.Addr {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "RemoteAddr")
-	ret0, _ := ret[0].(net.Addr)
-	return ret0
-}
-
-// RemoteAddr indicates an expected call of RemoteAddr
-func (mr *MockConnMockRecorder) RemoteAddr() *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoteAddr", reflect.TypeOf((*MockConn)(nil).RemoteAddr))
-}
-
-// SetDeadline mocks base method
-func (m *MockConn) SetDeadline(arg0 time.Time) error {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "SetDeadline", arg0)
-	ret0, _ := ret[0].(error)
-	return ret0
-}
-
-// SetDeadline indicates an expected call of SetDeadline
-func (mr *MockConnMockRecorder) SetDeadline(arg0 interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDeadline", reflect.TypeOf((*MockConn)(nil).SetDeadline), arg0)
-}
-
-// SetReadDeadline mocks base method
-func (m *MockConn) SetReadDeadline(arg0 time.Time) error {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "SetReadDeadline", arg0)
-	ret0, _ := ret[0].(error)
-	return ret0
-}
-
-// SetReadDeadline indicates an expected call of SetReadDeadline
-func (mr *MockConnMockRecorder) SetReadDeadline(arg0 interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetReadDeadline", reflect.TypeOf((*MockConn)(nil).SetReadDeadline), arg0)
-}
-
-// SetWriteDeadline mocks base method
-func (m *MockConn) SetWriteDeadline(arg0 time.Time) error {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "SetWriteDeadline", arg0)
-	ret0, _ := ret[0].(error)
-	return ret0
-}
-
-// SetWriteDeadline indicates an expected call of SetWriteDeadline
-func (mr *MockConnMockRecorder) SetWriteDeadline(arg0 interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetWriteDeadline", reflect.TypeOf((*MockConn)(nil).SetWriteDeadline), arg0)
-}
-
-// Write mocks base method
-func (m *MockConn) Write(arg0 []byte) (int, error) {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "Write", arg0)
-	ret0, _ := ret[0].(int)
-	ret1, _ := ret[1].(error)
-	return ret0, ret1
-}
-
-// Write indicates an expected call of Write
-func (mr *MockConnMockRecorder) Write(arg0 interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockConn)(nil).Write), arg0)
-}
diff --git a/internal/mocks/dialer.go b/internal/mocks/dialer.go
deleted file mode 100644
index 155f7c62..00000000
--- a/internal/mocks/dialer.go
+++ /dev/null
@@ -1,50 +0,0 @@
-// Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/ngrok/kubernetes-ingress-controller/pkg/tunneldriver (interfaces: Dialer)
-
-// Package mocks is a generated GoMock package.
-package mocks
-
-import (
-	context "context"
-	gomock "github.com/golang/mock/gomock"
-	net "net"
-	reflect "reflect"
-)
-
-// MockDialer is a mock of Dialer interface
-type MockDialer struct {
-	ctrl     *gomock.Controller
-	recorder *MockDialerMockRecorder
-}
-
-// MockDialerMockRecorder is the mock recorder for MockDialer
-type MockDialerMockRecorder struct {
-	mock *MockDialer
-}
-
-// NewMockDialer creates a new mock instance
-func NewMockDialer(ctrl *gomock.Controller) *MockDialer {
-	mock := &MockDialer{ctrl: ctrl}
-	mock.recorder = &MockDialerMockRecorder{mock}
-	return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use
-func (m *MockDialer) EXPECT() *MockDialerMockRecorder {
-	return m.recorder
-}
-
-// DialContext mocks base method
-func (m *MockDialer) DialContext(arg0 context.Context, arg1, arg2 string) (net.Conn, error) {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "DialContext", arg0, arg1, arg2)
-	ret0, _ := ret[0].(net.Conn)
-	ret1, _ := ret[1].(error)
-	return ret0, ret1
-}
-
-// DialContext indicates an expected call of DialContext
-func (mr *MockDialerMockRecorder) DialContext(arg0, arg1, arg2 interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DialContext", reflect.TypeOf((*MockDialer)(nil).DialContext), arg0, arg1, arg2)
-}
diff --git a/internal/mocks/gen.go b/internal/mocks/gen.go
deleted file mode 100644
index 29a772ca..00000000
--- a/internal/mocks/gen.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package mocks
-
-//go:generate go run github.com/golang/mock/mockgen -package mocks -destination conn.go net Conn
-
-//go:generate go run github.com/golang/mock/mockgen -package mocks -destination tunnel.go golang.ngrok.com/ngrok Tunnel
-
-//go:generate go run github.com/golang/mock/mockgen -package mocks -destination dialer.go github.com/ngrok/kubernetes-ingress-controller/pkg/tunneldriver Dialer
diff --git a/internal/mocks/tunnel.go b/internal/mocks/tunnel.go
deleted file mode 100644
index 3ee50931..00000000
--- a/internal/mocks/tunnel.go
+++ /dev/null
@@ -1,191 +0,0 @@
-// Code generated by MockGen. DO NOT EDIT.
-// Source: golang.ngrok.com/ngrok (interfaces: Tunnel)
-
-// Package mocks is a generated GoMock package.
-package mocks
-
-import (
-	context "context"
-	gomock "github.com/golang/mock/gomock"
-	ngrok "golang.ngrok.com/ngrok"
-	net "net"
-	reflect "reflect"
-)
-
-// MockTunnel is a mock of Tunnel interface
-type MockTunnel struct {
-	ctrl     *gomock.Controller
-	recorder *MockTunnelMockRecorder
-}
-
-// MockTunnelMockRecorder is the mock recorder for MockTunnel
-type MockTunnelMockRecorder struct {
-	mock *MockTunnel
-}
-
-// NewMockTunnel creates a new mock instance
-func NewMockTunnel(ctrl *gomock.Controller) *MockTunnel {
-	mock := &MockTunnel{ctrl: ctrl}
-	mock.recorder = &MockTunnelMockRecorder{mock}
-	return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use
-func (m *MockTunnel) EXPECT() *MockTunnelMockRecorder {
-	return m.recorder
-}
-
-// Accept mocks base method
-func (m *MockTunnel) Accept() (net.Conn, error) {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "Accept")
-	ret0, _ := ret[0].(net.Conn)
-	ret1, _ := ret[1].(error)
-	return ret0, ret1
-}
-
-// Accept indicates an expected call of Accept
-func (mr *MockTunnelMockRecorder) Accept() *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Accept", reflect.TypeOf((*MockTunnel)(nil).Accept))
-}
-
-// Addr mocks base method
-func (m *MockTunnel) Addr() net.Addr {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "Addr")
-	ret0, _ := ret[0].(net.Addr)
-	return ret0
-}
-
-// Addr indicates an expected call of Addr
-func (mr *MockTunnelMockRecorder) Addr() *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Addr", reflect.TypeOf((*MockTunnel)(nil).Addr))
-}
-
-// Close mocks base method
-func (m *MockTunnel) Close() error {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "Close")
-	ret0, _ := ret[0].(error)
-	return ret0
-}
-
-// Close indicates an expected call of Close
-func (mr *MockTunnelMockRecorder) Close() *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockTunnel)(nil).Close))
-}
-
-// CloseWithContext mocks base method
-func (m *MockTunnel) CloseWithContext(arg0 context.Context) error {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "CloseWithContext", arg0)
-	ret0, _ := ret[0].(error)
-	return ret0
-}
-
-// CloseWithContext indicates an expected call of CloseWithContext
-func (mr *MockTunnelMockRecorder) CloseWithContext(arg0 interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloseWithContext", reflect.TypeOf((*MockTunnel)(nil).CloseWithContext), arg0)
-}
-
-// ForwardsTo mocks base method
-func (m *MockTunnel) ForwardsTo() string {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "ForwardsTo")
-	ret0, _ := ret[0].(string)
-	return ret0
-}
-
-// ForwardsTo indicates an expected call of ForwardsTo
-func (mr *MockTunnelMockRecorder) ForwardsTo() *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ForwardsTo", reflect.TypeOf((*MockTunnel)(nil).ForwardsTo))
-}
-
-// ID mocks base method
-func (m *MockTunnel) ID() string {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "ID")
-	ret0, _ := ret[0].(string)
-	return ret0
-}
-
-// ID indicates an expected call of ID
-func (mr *MockTunnelMockRecorder) ID() *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ID", reflect.TypeOf((*MockTunnel)(nil).ID))
-}
-
-// Labels mocks base method
-func (m *MockTunnel) Labels() map[string]string {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "Labels")
-	ret0, _ := ret[0].(map[string]string)
-	return ret0
-}
-
-// Labels indicates an expected call of Labels
-func (mr *MockTunnelMockRecorder) Labels() *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Labels", reflect.TypeOf((*MockTunnel)(nil).Labels))
-}
-
-// Metadata mocks base method
-func (m *MockTunnel) Metadata() string {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "Metadata")
-	ret0, _ := ret[0].(string)
-	return ret0
-}
-
-// Metadata indicates an expected call of Metadata
-func (mr *MockTunnelMockRecorder) Metadata() *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Metadata", reflect.TypeOf((*MockTunnel)(nil).Metadata))
-}
-
-// Proto mocks base method
-func (m *MockTunnel) Proto() string {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "Proto")
-	ret0, _ := ret[0].(string)
-	return ret0
-}
-
-// Proto indicates an expected call of Proto
-func (mr *MockTunnelMockRecorder) Proto() *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Proto", reflect.TypeOf((*MockTunnel)(nil).Proto))
-}
-
-// Session mocks base method
-func (m *MockTunnel) Session() ngrok.Session {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "Session")
-	ret0, _ := ret[0].(ngrok.Session)
-	return ret0
-}
-
-// Session indicates an expected call of Session
-func (mr *MockTunnelMockRecorder) Session() *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Session", reflect.TypeOf((*MockTunnel)(nil).Session))
-}
-
-// URL mocks base method
-func (m *MockTunnel) URL() string {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "URL")
-	ret0, _ := ret[0].(string)
-	return ret0
-}
-
-// URL indicates an expected call of URL
-func (mr *MockTunnelMockRecorder) URL() *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "URL", reflect.TypeOf((*MockTunnel)(nil).URL))
-}
diff --git a/pkg/tunneldriver/driver.go b/pkg/tunneldriver/driver.go
index 9edef029..28502c20 100644
--- a/pkg/tunneldriver/driver.go
+++ b/pkg/tunneldriver/driver.go
@@ -5,10 +5,8 @@ import (
 	"crypto/tls"
 	"crypto/x509"
 	"encoding/json"
-	"errors"
 	"fmt"
-	"io"
-	"net"
+	"net/url"
 	"os"
 	"path/filepath"
 	"strings"
@@ -18,7 +16,6 @@ import (
 	ingressv1alpha1 "github.com/ngrok/kubernetes-ingress-controller/api/ingress/v1alpha1"
 	"github.com/ngrok/kubernetes-ingress-controller/internal/version"
 	"golang.org/x/exp/maps"
-	"golang.org/x/sync/errgroup"
 	"sigs.k8s.io/controller-runtime/pkg/log"
 
 	"golang.ngrok.com/ngrok"
@@ -47,7 +44,7 @@ const (
 // TunnelDriver is a driver for creating and deleting ngrok tunnels
 type TunnelDriver struct {
 	session atomic.Pointer[sessionState]
-	tunnels map[string]ngrok.Tunnel
+	tunnels map[string]ngrok.Forwarder
 }
 
 // TunnelDriverOpts are options for creating a new TunnelDriver
@@ -123,7 +120,7 @@ func New(ctx context.Context, logger logr.Logger, opts TunnelDriverOpts) (*Tunne
 	}
 
 	td := &TunnelDriver{
-		tunnels: make(map[string]ngrok.Tunnel),
+		tunnels: make(map[string]ngrok.Forwarder),
 	}
 
 	td.session.Store(&sessionState{
@@ -267,18 +264,22 @@ func (td *TunnelDriver) CreateTunnel(ctx context.Context, name string, spec ingr
 		defer td.stopTunnel(context.Background(), tun)
 	}
 
-	tun, err := session.Listen(ctx, td.buildTunnelConfig(spec.Labels, spec.ForwardsTo, spec.AppProtocol))
+	protocol := "tcp"
+	if spec.BackendConfig != nil {
+		protocol = spec.BackendConfig.Protocol
+	}
+
+	destUrlStr := fmt.Sprintf("%s://%s", strings.ToLower(protocol), spec.ForwardsTo)
+	destUrl, err := url.Parse(destUrlStr)
 	if err != nil {
 		return err
 	}
-	td.tunnels[name] = tun
 
-	protocol := ""
-	if spec.BackendConfig != nil {
-		protocol = spec.BackendConfig.Protocol
+	tun, err := session.ListenAndForward(ctx, destUrl, td.buildTunnelConfig(spec.Labels, spec.ForwardsTo, spec.AppProtocol))
+	if err != nil {
+		return err
 	}
-
-	go handleConnections(ctx, &net.Dialer{}, tun, spec.ForwardsTo, protocol, spec.AppProtocol)
+	td.tunnels[name] = tun
 	return nil
 }
 
@@ -301,7 +302,7 @@ func (td *TunnelDriver) DeleteTunnel(ctx context.Context, name string) error {
 	return nil
 }
 
-func (td *TunnelDriver) stopTunnel(ctx context.Context, tun ngrok.Tunnel) error {
+func (td *TunnelDriver) stopTunnel(ctx context.Context, tun ngrok.Forwarder) error {
 	if tun == nil {
 		return nil
 	}
@@ -317,83 +318,3 @@ func (td *TunnelDriver) buildTunnelConfig(labels map[string]string, destination,
 	opts = append(opts, config.WithAppProtocol(appProtocol))
 	return config.LabeledTunnel(opts...)
 }
-
-func handleConnections(ctx context.Context, dialer Dialer, tun ngrok.Tunnel, dest string, protocol string, appProtocol string) {
-	logger := log.FromContext(ctx).WithValues("id", tun.ID(), "protocol", protocol, "dest", dest)
-	for {
-		conn, err := tun.Accept()
-		if err != nil {
-			logger.Error(err, "Error accepting connection")
-			// Right now, this can only be "Tunnel closed" https://github.com/ngrok/ngrok-go/blob/e1d90c382/internal/tunnel/client/tunnel.go#L81-L89
-			// Since that's terminal, that means we should give up on this loop to
-			// ensure we don't leak a goroutine after a tunnel goes away.
-			// Unfortunately, it's not an exported error, so we can't verify with
-			// more certainty that's what's going on, but at the time of writing,
-			// that should be true.
-			return
-		}
-		connLogger := logger.WithValues("remoteAddr", conn.RemoteAddr())
-		connLogger.Info("Accepted connection")
-
-		go func() {
-			ctx := log.IntoContext(ctx, connLogger)
-			err := handleConn(ctx, dest, protocol, appProtocol, dialer, conn)
-			if err == nil || errors.Is(err, net.ErrClosed) {
-				connLogger.Info("Connection closed")
-				return
-			}
-
-			connLogger.Error(err, "Error handling connection")
-		}()
-	}
-}
-
-func handleConn(ctx context.Context, dest string, protocol string, appProtocol string, dialer Dialer, conn net.Conn) error {
-	log := log.FromContext(ctx)
-	next, err := dialer.DialContext(ctx, "tcp", dest)
-	if err != nil {
-		return err
-	}
-
-	// Support HTTPS backends
-	if protocol == "HTTPS" {
-		host, _, err := net.SplitHostPort(dest)
-		if err != nil {
-			host = dest
-		}
-		var nextProtos []string
-		if appProtocol == "http2" {
-			nextProtos = []string{"h2", "http/1.1"}
-		}
-
-		next = tls.Client(next, &tls.Config{
-			ServerName:         host,
-			InsecureSkipVerify: true,
-			Renegotiation:      tls.RenegotiateFreelyAsClient,
-			NextProtos:         nextProtos,
-		})
-	}
-
-	var g errgroup.Group
-	g.Go(func() error {
-		defer func() {
-			if err := next.Close(); err != nil {
-				log.Info("Error closing connection to destination: %v", err)
-			}
-		}()
-
-		_, err := io.Copy(next, conn)
-		return err
-	})
-	g.Go(func() error {
-		defer func() {
-			if err := conn.Close(); err != nil {
-				log.Info("Error closing connection from ngrok: %v", err)
-			}
-		}()
-
-		_, err := io.Copy(conn, next)
-		return err
-	})
-	return g.Wait()
-}
diff --git a/pkg/tunneldriver/driver_test.go b/pkg/tunneldriver/driver_test.go
deleted file mode 100644
index 21c21405..00000000
--- a/pkg/tunneldriver/driver_test.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package tunneldriver
-
-import (
-	"context"
-	"io"
-	"net"
-	"sync"
-	"testing"
-
-	"github.com/golang/mock/gomock"
-	"github.com/ngrok/kubernetes-ingress-controller/internal/mocks"
-)
-
-func TestConnectionIsClosed(t *testing.T) {
-	ctx, cancel := context.WithCancel(context.Background())
-	defer cancel()
-	ctrl := gomock.NewController(t)
-	mockTun := mocks.NewMockTunnel(ctrl)
-	mockDialer := mocks.NewMockDialer(ctrl)
-	mockNgrokConn := mocks.NewMockConn(ctrl)
-	mockBackendConn := mocks.NewMockConn(ctrl)
-
-	bothClosed := sync.WaitGroup{}
-	bothClosed.Add(2)
-
-	gomock.InOrder(
-		mockTun.EXPECT().ID().Return("logging id"),
-		// It should ask ngrok for a connection
-		mockTun.EXPECT().Accept().Return(mockNgrokConn, nil),
-		// dial the backend
-		mockNgrokConn.EXPECT().RemoteAddr().Return(&net.TCPAddr{}),
-		mockDialer.EXPECT().DialContext(gomock.Any(), "tcp", "target:port").Return(mockBackendConn, nil),
-	)
-
-	// both conns should receive a read, and if they EOF get closed.
-	// This is not in order because it depends on goroutine scheduling which
-	// happens first
-	for _, c := range []*mocks.MockConn{mockNgrokConn, mockBackendConn} {
-		c.EXPECT().Read(gomock.Any()).Return(0, io.EOF)
-		c.EXPECT().Close().Do(func() {
-			bothClosed.Done()
-		}).Return(nil)
-	}
-	mockTun.EXPECT().Accept().Do(func() {
-		select {}
-	}).AnyTimes()
-
-	go handleConnections(ctx, mockDialer, mockTun, "target:port", "", "")
-
-	bothClosed.Wait()
-	ctrl.Finish()
-}
diff --git a/tools.go b/tools.go
index a5a65674..3abf6cfa 100644
--- a/tools.go
+++ b/tools.go
@@ -3,7 +3,6 @@
 package main
 
 import (
-	_ "github.com/golang/mock/mockgen"
 	_ "sigs.k8s.io/controller-runtime/tools/setup-envtest"
 	_ "sigs.k8s.io/controller-tools/cmd/controller-gen"
 	_ "sigs.k8s.io/kustomize/kustomize/v3"