diff --git a/services/apiserver.go b/services/apiserver.go
index 8dbaf508abb66504c70539e08e694131fce1ef07..98a17a0a7032bae731c58dc00e82d67c191cb9fb 100644
--- a/services/apiserver.go
+++ b/services/apiserver.go
@@ -8,7 +8,6 @@ import (
 	"os"
 	"time"
 
-	"github.com/google/uuid"
 	extensions "k8s.io/apiextensions-apiserver/pkg/apiserver"
 	meta "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
@@ -32,8 +31,6 @@ import (
 	"k8s.io/apiserver/pkg/util/flowcontrol/request"
 	"k8s.io/apiserver/pkg/util/notfoundhandler"
 	"k8s.io/apiserver/pkg/util/openapi"
-	"k8s.io/client-go/kubernetes"
-	"k8s.io/client-go/rest"
 	"k8s.io/client-go/tools/clientcmd"
 	"k8s.io/client-go/tools/clientcmd/api"
 	"k8s.io/component-base/version"
@@ -128,18 +125,12 @@ func buildConfig(c *Cluster) (config *server.Config, clients *Clients, err error
 	}
 
 	// Setup loopback clients (no authorization at this point, handled later)
-	loopback, err := config.SecureServing.NewLoopbackClientConfig(uuid.NewString(), nil)
-	loopback.TLSClientConfig.CAFile = c.pki.TLS.CertPath()
-	if err != nil {
-		err = fmt.Errorf("could not setup loopback config: %w", err)
-		return
-	}
-	config.LoopbackClientConfig = loopback
-	clients, err = newClientsForKC(config.LoopbackClientConfig)
+	clients, err = newLoopbackClients(c)
 	if err != nil {
 		err = fmt.Errorf("could not setup loopback clients: %w", err)
 		return
 	}
+	config.LoopbackClientConfig = clients.KubeConfig
 
 	// Setup authentication
 	authConfig := authenticator.Config{
@@ -181,7 +172,7 @@ func buildConfig(c *Cluster) (config *server.Config, clients *Clients, err error
 	}
 
 	// Finally authorize loopback clients
-	server.AuthorizeClientBearerToken(loopback, &config.Authentication, &config.Authorization)
+	server.AuthorizeClientBearerToken(clients.KubeConfig, &config.Authentication, &config.Authorization)
 
 	// Setup service resolver
 	localHost, _ := url.Parse(config.LoopbackClientConfig.Host)
@@ -310,6 +301,32 @@ var kubeApiserver = &Unit{
 			return fmt.Errorf("could not initialize generic apiserver: %w", err)
 		}
 
+		// Finally start the apiserver
+		server := apiServer.GenericAPIServer.PrepareRun()
+		go clients.Start(ctx)
+		return server.Run(ctx.Done())
+	},
+	Ready: func(u *Unit, c *Cluster) bool {
+		u.Logger.Info("checking if apiserver is ready")
+		clients, err := newLoopbackClients(c)
+		if err != nil {
+			return false
+		}
+		_, err = clients.Client.CoreV1().Nodes().List(context.Background(), meta.ListOptions{})
+		if err != nil {
+			return false
+		}
+		return true
+	},
+}
+
+var loopback = &Unit{
+	Dependencies: []*Unit{kubeApiserver},
+	Start: func(u *Unit, c *Cluster, ctx context.Context) error {
+		clients, err := newLoopbackClients(c)
+		if err != nil {
+			return fmt.Errorf("could not get loopback config: %w", err)
+		}
 		// Write a loopback config (different for every start)
 		name := "loopback"
 		clientConfig := api.Config{
@@ -321,11 +338,11 @@ var kubeApiserver = &Unit{
 				AuthInfo: name,
 			}},
 			Clusters: map[string]*api.Cluster{name: {
-				Server:               config.LoopbackClientConfig.Host,
-				CertificateAuthority: config.LoopbackClientConfig.TLSClientConfig.CAFile,
+				Server:               clients.KubeConfig.Host,
+				CertificateAuthority: clients.KubeConfig.TLSClientConfig.CAFile,
 			}},
 			AuthInfos: map[string]*api.AuthInfo{name: {
-				Token: config.LoopbackClientConfig.BearerToken,
+				Token: clients.KubeConfig.BearerToken,
 			}},
 		}
 		err = os.MkdirAll("/root/.kube", 0755)
@@ -336,32 +353,6 @@ var kubeApiserver = &Unit{
 		if err != nil {
 			return fmt.Errorf("could not write privileged kubeconfig: %w", err)
 		}
-
-		// Finally start the apiserver
-		server := apiServer.GenericAPIServer.PrepareRun()
-		go clients.Start(ctx)
-		return server.Run(ctx.Done())
-	},
-	Ready: func(u *Unit, c *Cluster) bool {
-		u.Logger.Info("checking if apiserver is ready")
-		// Use the scheduler certificate for readiness test, which is more relevant than
-		// using the internal privileged API token
-		kc := &rest.Config{
-			Host: fmt.Sprintf("https://[%s]:%d", c.networking.NodeAddress.IP.String(), apiserverPort),
-			TLSClientConfig: rest.TLSClientConfig{
-				CAFile:   c.pki.TLS.CertPath(),
-				CertFile: c.masterCerts.SchedulerAPI.CertPath(),
-				KeyFile:  c.masterCerts.SchedulerAPI.KeyPath(),
-			},
-		}
-		client, err := kubernetes.NewForConfig(rest.AddUserAgent(kc, "scheduler"))
-		if err != nil {
-			return false
-		}
-		_, err = client.CoreV1().Nodes().List(context.Background(), meta.ListOptions{})
-		if err != nil {
-			return false
-		}
-		return true
+		return nil
 	},
 }
diff --git a/services/certs.go b/services/certs.go
index e094dffbc2144c98234e5fd478b62c37a5c19c66..f340b40bf95c2f055399aa55e20be2d4c223aba6 100644
--- a/services/certs.go
+++ b/services/certs.go
@@ -30,12 +30,6 @@ type MasterCerts struct {
 	Kubelet *pekahi.Certificate
 	// Service certificate for the controller manager
 	ControllersTLS *pekahi.Certificate
-	// API client certificate for the controller manager
-	ControllersAPI *pekahi.Certificate
-	// API client certificate for the scheduler
-	SchedulerAPI *pekahi.Certificate
-	// Root access to the API server
-	RootClient *pekahi.Certificate
 }
 
 // Node certs
@@ -139,24 +133,6 @@ var pkiMaster = &Unit{
 		if err != nil {
 			return err
 		}
-		// Controller manager API client certificate
-		controllersAPICert, err := bundle.GetCertOrCSR("controllers-api",
-			pekahi.NewClientTemplate("system:kube-controller-manager", ""),
-		)
-		if err != nil {
-			return err
-		}
-		// Scheduler API client certificate
-		schedulerAPICert, err := bundle.GetCertOrCSR("scheduler-api",
-			pekahi.NewClientTemplate("system:kube-scheduler", ""),
-		)
-		if err != nil {
-			return err
-		}
-		// Root client certificate
-		rootClientCert, err := bundle.GetCertOrCSR("root",
-			pekahi.NewClientTemplate("root", "system:masters"),
-		)
 
 		m := &MasterCerts{
 			TLS:            tlsCert,
@@ -164,16 +140,10 @@ var pkiMaster = &Unit{
 			EtcdTokens:     etcdTokenKey,
 			Kubelet:        kubeletCert,
 			ControllersTLS: controllersTLSCert,
-			ControllersAPI: controllersAPICert,
-			SchedulerAPI:   schedulerAPICert,
-			RootClient:     rootClientCert,
 		}
 		c.pki.TLS.Sign(m.TLS, pekahi.NewServerTemplate(m.TLS.CSR.DNSNames, m.TLS.CSR.IPAddresses))
 		c.pki.Kubelet.Sign(m.Kubelet, pekahi.NewClientTemplate(m.Kubelet.CSR.Subject.CommonName, "system:masters"))
 		c.pki.TLS.Sign(m.ControllersTLS, pekahi.NewServerTemplate(m.ControllersTLS.CSR.DNSNames, m.ControllersTLS.CSR.IPAddresses))
-		c.pki.API.Sign(m.ControllersAPI, pekahi.NewClientTemplate(m.ControllersAPI.CSR.Subject.CommonName, ""))
-		c.pki.API.Sign(m.SchedulerAPI, pekahi.NewClientTemplate(m.SchedulerAPI.CSR.Subject.CommonName, ""))
-		c.pki.API.Sign(m.RootClient, pekahi.NewClientTemplate(m.RootClient.CSR.Subject.CommonName, "system:masters"))
 		c.masterCerts = m
 		u.Manager.Logger.Info("master pki initialized")
 		return nil
diff --git a/services/cm.go b/services/cm.go
index 58094582e158b4d9b125cbf391d44910a6cd7c54..326c422e2ceef81ba4004c80efefe13d43725acf 100644
--- a/services/cm.go
+++ b/services/cm.go
@@ -41,7 +41,7 @@ var kubeControllerManager = &Unit{
 		// Used as a replacement for InformersStarted in vanilla code
 		allReady := make(chan struct{})
 
-		clients, err := newClients(c, c.networking.NodeAddress.IP, c.masterCerts.ControllersAPI)
+		clients, err := newLoopbackClients(c)
 		if err != nil {
 			return err
 		}
diff --git a/services/k8s.go b/services/k8s.go
index 09455d571cc9d72f8965f44a91065ccf378474ba..06158fc9d98d6d281b73d8ecaa30f81839059d71 100644
--- a/services/k8s.go
+++ b/services/k8s.go
@@ -3,7 +3,6 @@ package services
 import (
 	"context"
 	"fmt"
-	"net"
 
 	"github.com/spf13/pflag"
 	"go.acides.org/pekahi"
@@ -44,15 +43,28 @@ type Clients struct {
 	DynInformer    dynamicinformer.DynamicSharedInformerFactory
 }
 
-func newClients(c *Cluster, masterIP net.IP, cert *pekahi.Certificate) (*Clients, error) {
-	kc := &rest.Config{
-		Host: fmt.Sprintf("https://[%s]:%d", masterIP.String(), apiserverPort),
+// Make a k8s client config for connecting to the master
+func newKC(c *Cluster) *rest.Config {
+	return &rest.Config{
+		Host: fmt.Sprintf("https://[%s]:%d", c.masterNode.VpnIP.String(), apiserverPort),
 		TLSClientConfig: rest.TLSClientConfig{
-			CAFile:   c.pki.TLS.CertPath(),
-			CertFile: cert.CertPath(),
-			KeyFile:  cert.KeyPath(),
+			CAFile: c.pki.TLS.CertPath(),
 		},
 	}
+}
+
+// Make clients authenticating with a ceritficate (typically for kubelets)
+func newCertClients(c *Cluster, cert *pekahi.Certificate) (*Clients, error) {
+	kc := newKC(c)
+	kc.TLSClientConfig.CertFile = cert.CertPath()
+	kc.TLSClientConfig.KeyFile = cert.KeyPath()
+	return newClientsForKC(kc)
+}
+
+// Make clients from the master context itself
+func newLoopbackClients(c *Cluster) (*Clients, error) {
+	kc := newKC(c)
+	kc.BearerToken = c.loopbackToken
 	return newClientsForKC(kc)
 }
 
diff --git a/services/kubelet.go b/services/kubelet.go
index a1b43b06651ccf40839262cf2734f296d1902ad8..a323ca700d667b7c040cc7eff0e52017589b747c 100644
--- a/services/kubelet.go
+++ b/services/kubelet.go
@@ -44,7 +44,7 @@ var kubeKubelet = &Unit{
 		// (very difficult to check otherwise)
 		time.Sleep(10 * time.Second)
 		kubeletRoot := path.Join(c.settings.DataDir, "kubelet")
-		clients, err := newClients(c, c.masterNode.VpnIP, c.certs.API)
+		clients, err := newCertClients(c, c.certs.API)
 		if err != nil {
 			return fmt.Errorf("could not create clients: %w", err)
 		}
diff --git a/services/manager.go b/services/manager.go
index 6954a5e9b2ce700323ad29792946628883af948d..e3fc0c51b1abc6f4eca9181b5260dfe71890ead1 100644
--- a/services/manager.go
+++ b/services/manager.go
@@ -9,6 +9,7 @@ import (
 
 	"github.com/containerd/containerd/services/server"
 	"github.com/go-logr/logr"
+	"github.com/google/uuid"
 	"github.com/sirupsen/logrus"
 	"go.acides.org/daeman"
 	"go.acides.org/hepto/utils"
@@ -63,10 +64,11 @@ type Cluster struct {
 	state      *HeptoState
 	masterNode *HeptoMeta
 
-	certs       *NodeCerts
-	masterCerts *MasterCerts
-	pki         *ClusterCA
-	containerd  *server.Server
+	certs         *NodeCerts
+	masterCerts   *MasterCerts
+	pki           *ClusterCA
+	containerd    *server.Server
+	loopbackToken string
 
 	vpn *wg.Wireguard
 }
@@ -86,10 +88,11 @@ func NewManager(settings *ClusterSettings, node *NodeSettings, logger logr.Logge
 		API:     bundle.GetCertificate("api"),
 	}
 	cluster := &Cluster{
-		settings:   settings,
-		node:       node,
-		networking: networking,
-		pki:        pki,
+		settings:      settings,
+		node:          node,
+		networking:    networking,
+		pki:           pki,
+		loopbackToken: uuid.NewString(),
 		localNode: &HeptoMeta{
 			Name:     node.Name,
 			PublicIP: node.IP,
@@ -105,11 +108,11 @@ func NewManager(settings *ClusterSettings, node *NodeSettings, logger logr.Logge
 	units := []*Unit{}
 	switch node.Role {
 	case "master":
-		units = append(units, memberlist, vpn, kubeApiserver, kubeControllerManager, kubeScheduler)
+		units = append(units, memberlist, vpn, kubeApiserver, loopback, kubeControllerManager, kubeScheduler)
 	case "node":
 		units = append(units, memberlist, vpn, kubeKubelet)
 	case "full":
-		units = append(units, memberlist, vpn, kubeApiserver, kubeControllerManager, kubeScheduler, kubeKubelet)
+		units = append(units, memberlist, vpn, kubeApiserver, loopback, kubeControllerManager, kubeScheduler, kubeKubelet)
 	}
 	return daeman.NewManager(cluster, units, logger), nil
 }
diff --git a/services/scheduler.go b/services/scheduler.go
index b5b34a38d3b33d7cd8b58e711868594b86d55cd3..3f4aeb31ed2219cd60dcccc1b522499cddcdbe8f 100644
--- a/services/scheduler.go
+++ b/services/scheduler.go
@@ -10,7 +10,7 @@ var kubeScheduler = &Unit{
 	Name:         "kube-scheduler",
 	Dependencies: []*Unit{kubeApiserver, pkiCA, pkiMaster, kubeLogger},
 	Run: func(u *Unit, c *Cluster, ctx context.Context) error {
-		clients, err := newClients(c, c.networking.NodeAddress.IP, c.masterCerts.SchedulerAPI)
+		clients, err := newLoopbackClients(c)
 		if err != nil {
 			return err
 		}