diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go
index d0159ecdfbcffff1027c609f39e4fbd98f091d03..4370b0a0a2a1e549384a048bd06075a073054df6 100644
--- a/pkg/cluster/cluster.go
+++ b/pkg/cluster/cluster.go
@@ -5,7 +5,6 @@ package cluster
 import (
 	"net"
 
-	"forge.tedomum.net/acides/hepto/hepto/pkg/pki"
 	"forge.tedomum.net/acides/hepto/hepto/pkg/sml"
 	"forge.tedomum.net/acides/hepto/hepto/pkg/wg"
 	"github.com/sirupsen/logrus"
@@ -13,7 +12,7 @@ import (
 
 type Cluster struct {
 	settings   *ClusterSettings
-	ml         *sml.Memberlist[HeptoMeta, *HeptoMeta]
+	ml         *sml.Memberlist[HeptoMeta, HeptoState, *HeptoMeta, *HeptoState]
 	vpn        *wg.Wireguard
 	networking *ClusterNetworking
 	node       *NodeSettings
@@ -28,7 +27,7 @@ func New(settings *ClusterSettings, node *NodeSettings) (*Cluster, error) {
 		networking: NewClusterNetworking(settings.Name, node.Name),
 	}
 	// Prepare memberlist
-	c.ml = sml.New[HeptoMeta](node.Name, node.IP, node.Port, node.Anchors, settings.Key)
+	c.ml = sml.New[HeptoMeta, HeptoState](node.Name, node.IP, node.Port, node.Anchors, settings.Key)
 	// Prepare wireguard
 	vpn, err := wg.New("wg0", 7124, c.networking.NodeAddress.IPNet())
 	if err != nil {
@@ -42,18 +41,21 @@ func New(settings *ClusterSettings, node *NodeSettings) (*Cluster, error) {
 			return nil, err
 		}
 		c.pki = pki
+	} else {
+		c.pki = &ClusterPKI{}
 	}
 	certs, err := NewNodeCerts("certs", node.Name)
 	if err != nil {
 		return nil, err
 	}
 	c.certs = certs
-	// Initialize cluster meta
-	meta := c.ml.Meta
-	meta.VpnKey = vpn.PubKey.String()
-	meta.Role = node.Role
-	meta.Certs = make(map[string]NodePKIBytes)
-	meta.CSR.APIClient = certs.APIClient.CSR
+	// Initialize node meta
+	c.ml.Meta.VpnKey = vpn.PubKey.String()
+	c.ml.Meta.Role = node.Role
+	// Initialize cluster state
+	c.ml.State.PKI = c.pki
+	c.ml.State.Certificates = make(map[string]*NodeCerts)
+	c.ml.State.Certificates[node.Name] = certs
 	return c, nil
 }
 
@@ -69,7 +71,9 @@ func (c *Cluster) Run() error {
 	for {
 		select {
 		case <-events:
-			c.handlePKI()
+			if c.node.Role == "master" {
+				c.handlePKI()
+			}
 			c.updateVPN()
 		case <-instrUpdates:
 			c.networking.MTU = instr.MinMTU()
@@ -79,45 +83,8 @@ func (c *Cluster) Run() error {
 }
 
 func (c *Cluster) handlePKI() {
-	logrus.Debug("handling possible PKI messages")
-	for _, node := range c.ml.Nodes() {
-		meta := node.NodeMeta
-		if meta.Role == "master" {
-			logrus.Debug("found master node, checking certs")
-			logrus.Debug(string(node.Meta))
-			if certs, ok := meta.Certs[c.node.Name]; ok {
-				if len(certs.APIClient) > 0 {
-					logrus.Debug("certificate is signed")
-					err := c.certs.APIClient.SetCert(certs.APIClient)
-					if err != nil {
-						logrus.Warn("cannot load certificate")
-					}
-				}
-			}
-		}
-	}
-	if c.pki != nil {
-		logrus.Debug("this is the master, generating required certificates")
-		for _, node := range c.ml.Nodes() {
-			meta := node.NodeMeta
-			if len(meta.CSR.APIClient) > 0 {
-				template := pki.NewClientTemplate(node.Name, "")
-				cert, err := c.pki.APIServer.Sign(meta.CSR.APIClient, template)
-				if err != nil {
-					logrus.Warn("cannot sign certificate")
-					continue
-				}
-				logrus.Debug("signed for ", node.Name, cert)
-				this := c.ml.Meta
-				var certs NodePKIBytes
-				if certs, ok := this.Certs[node.Name]; !ok {
-					this.Certs[node.Name] = certs
-				}
-				certs.APIClient = cert
-				this.Certs[node.Name] = certs
-				c.ml.Update()
-			}
-		}
+	for name, certs := range c.ml.State.Certificates {
+		c.pki.SignNodeCerts(name, certs)
 	}
 }
 
@@ -131,12 +98,12 @@ func (c *Cluster) updateVPN() {
 		peerAddr := c.networking.NodeNet.DeriveAddress(node.Name).IP
 		peer, err := c.vpn.MakePeer(node.Addr, meta.VpnKey, peerAddr, []net.IPNet{})
 		if err != nil {
-			logrus.Debug("Cannot setup VPN with node ", node.Name)
+			logrus.Debug("cannot setup VPN with node ", node.Name)
 			logrus.Debug(err)
 			continue
 		}
 		peers = append(peers, peer)
 	}
-	logrus.Debugf("Updating the VPN mesh with %d peers, MTU %d", len(peers), c.networking.MTU)
+	logrus.Debugf("updating VPN mesh, %d peers, MTU %d", len(peers), c.networking.MTU)
 	c.vpn.Update(peers, c.networking.MTU)
 }
diff --git a/pkg/cluster/meta.go b/pkg/cluster/meta.go
index 2cfc4f60cc66633ca9700731eb362032ecd8ac67..b3dcb3b889b228196112d2745492ee23fce92153 100644
--- a/pkg/cluster/meta.go
+++ b/pkg/cluster/meta.go
@@ -2,34 +2,82 @@ package cluster
 
 import (
 	"encoding/json"
+	"fmt"
+	"strings"
 )
 
-// Represents a node CSRs and returned certs
-type NodePKIBytes struct {
-	// APIclient certificate
-	APIClient []byte `json:"apiclient"`
-}
-
 // Represents a node metadata
 type HeptoMeta struct {
 	// Public key for the wireguard mesh VPN
 	VpnKey string `json:"vpnKey"`
 	// Node role inside the cluster
 	Role string `json:"role"`
-	// Certificate CSRs
-	CSR NodePKIBytes `json:"csr"`
-	// Only on master, signed certificates
-	Certs map[string]NodePKIBytes `json:"certs"`
+}
+
+// Represents the cluster state
+type HeptoState struct {
+	// Cluster CAs public certificates
+	PKI *ClusterPKI `json:"ca"`
+	// Certificate per node, this should only
+	// be updated by the node itself
+	Certificates map[string]*NodeCerts `json:"nodes"`
 }
 
 func (m *HeptoMeta) Encode() ([]byte, error) {
-	return json.Marshal(&m)
+	return json.Marshal(m)
 }
 
 func (m *HeptoMeta) Decode(b []byte) error {
-	return json.Unmarshal(b, &m)
+	return json.Unmarshal(b, m)
 }
 
 func (m *HeptoMeta) String() string {
 	return m.Role
 }
+
+func (s *HeptoState) Encode() ([]byte, error) {
+	return json.Marshal(s)
+}
+
+func (s *HeptoState) Decode(b []byte) error {
+	return json.Unmarshal(b, s)
+}
+
+func (s *HeptoState) String() string {
+	var res string
+	if s.PKI != nil {
+		var ca []string
+		if s.PKI.API != nil {
+			ca = append(ca, "api")
+		}
+		if s.PKI.Kubelet != nil {
+			ca = append(ca, "kubelet")
+		}
+		if s.PKI.Services != nil {
+			ca = append(ca, "services")
+		}
+		res += fmt.Sprintf("ca{%s}", strings.Join(ca, ", "))
+	}
+	return res
+}
+
+func (s *HeptoState) Merge(b []byte) error {
+	remote := new(HeptoState)
+	err := json.Unmarshal(b, remote)
+	if err != nil {
+		return err
+	}
+	if remote.PKI == nil {
+		return nil
+	}
+	s.PKI.Merge(remote.PKI)
+	for name, remoteCerts := range remote.Certificates {
+		_, ok := s.Certificates[name]
+		if ok {
+			s.Certificates[name].Merge(remoteCerts)
+		} else {
+			s.Certificates[name] = remoteCerts
+		}
+	}
+	return nil
+}
diff --git a/pkg/cluster/pki.go b/pkg/cluster/pki.go
index b4988c8deef6404f2012fd68b0ae1ed6d3e3118b..37eafba55e1f7b416e0f6cde8009c988f2f21a40 100644
--- a/pkg/cluster/pki.go
+++ b/pkg/cluster/pki.go
@@ -1,16 +1,62 @@
 package cluster
 
 import (
+	"crypto/x509"
 	"os"
 	"path/filepath"
 
 	"forge.tedomum.net/acides/hepto/hepto/pkg/pki"
+	"github.com/sirupsen/logrus"
 )
 
+// Cluster PKI is made of three different PKIs
 type ClusterPKI struct {
-	Services  *pki.CA
-	Kubelet   *pki.CA
-	APIServer *pki.CA
+	// Signs services exposed over the cluster
+	Services *pki.PKI `json:"services"`
+	// Signs kubelet client certificates (master)
+	Kubelet *pki.PKI `json:"kubelet"`
+	// Signs apiserver client certificates (nodes and controller)
+	API *pki.PKI `json:"api"`
+}
+
+// Node certs
+type NodeCerts struct {
+	// Certificate for exposing the kubelet service
+	Service *pki.Certificate `json:"service"`
+	// Node certificate for accessing the apiserver
+	API *pki.Certificate `json:"api"`
+}
+
+// Merge PKI
+func (n *ClusterPKI) Merge(other *ClusterPKI) {
+	if other.API != nil {
+		n.API = other.API
+	}
+	if other.Services != nil {
+		n.Services = other.Services
+	}
+	if other.Kubelet != nil {
+		n.Kubelet = other.Kubelet
+	}
+}
+
+// Merge a single node certificate
+func mergeNodeCert(local *pki.Certificate, remote *pki.Certificate) {
+	// Import CSR to master for signing
+	if remote.CSR != nil {
+		local.CSR = remote.CSR
+	}
+	// Import and save cert back to node
+	if remote.Cert != nil {
+		local.Cert = remote.Cert
+		local.Save()
+	}
+}
+
+// Merge node certificates
+func (n *NodeCerts) Merge(other *NodeCerts) {
+	mergeNodeCert(n.Service, other.Service)
+	mergeNodeCert(n.API, other.API)
 }
 
 func NewClusterPKI(path string) (*ClusterPKI, error) {
@@ -18,35 +64,61 @@ func NewClusterPKI(path string) (*ClusterPKI, error) {
 	if err != nil {
 		return nil, err
 	}
-	servicesCA, err := pki.GetCA(filepath.Join(path, "services"))
+	servicesCA, err := pki.GetPKI(filepath.Join(path, "services"))
 	if err != nil {
 		return nil, err
 	}
-	kubeletCA, err := pki.GetCA(filepath.Join(path, "kubelet"))
+	kubeletCA, err := pki.GetPKI(filepath.Join(path, "kubelet"))
 	if err != nil {
 		return nil, err
 	}
-	apiserverCA, err := pki.GetCA(filepath.Join(path, "apiserver"))
+	apiserverCA, err := pki.GetPKI(filepath.Join(path, "api"))
 	if err != nil {
 		return nil, err
 	}
 	return &ClusterPKI{servicesCA, kubeletCA, apiserverCA}, nil
 }
 
-type NodeCerts struct {
-	APIClient *pki.CertKey
-}
-
 func NewNodeCerts(path string, nodeName string) (*NodeCerts, error) {
 	err := os.MkdirAll(path, 0755)
 	if err != nil {
 		return nil, err
 	}
-	apiClientCert, err := pki.LoadWithCSR(filepath.Join(path, "apiclient"), pki.NewClientTemplate(nodeName, ""))
+	// Service certificate
+	serviceCert, err := pki.GetCertificate(filepath.Join(path, "service"))
+	if err != nil {
+		return nil, err
+	}
+	err = serviceCert.MakeCSR(pki.NewServerTemplate([]string{nodeName}))
+	if err != nil {
+		return nil, err
+	}
+	// API certificate
+	apiClientCert, err := pki.GetCertificate(filepath.Join(path, "api"))
+	if err != nil {
+		return nil, err
+	}
+	err = apiClientCert.MakeCSR(pki.NewClientTemplate(nodeName, ""))
 	if err != nil {
 		return nil, err
 	}
 	return &NodeCerts{
-		APIClient: apiClientCert,
+		Service: serviceCert,
+		API:     apiClientCert,
 	}, nil
 }
+
+func signCert(p *pki.PKI, c *pki.Certificate, template *x509.Certificate) {
+	if c.CSR != nil && c.Cert == nil {
+		logrus.Info("signing certificate ", c.CSR.Subject.String())
+		err := p.Sign(c, template)
+		if err != nil {
+			logrus.Warnf("cannot sign API certificate for %s: %s", c.CSR.Subject.String(), err)
+		}
+	}
+}
+
+func (p *ClusterPKI) SignNodeCerts(name string, n *NodeCerts) {
+	signCert(p.Services, n.Service, pki.NewServerTemplate([]string{name}))
+	signCert(p.API, n.API, pki.NewClientTemplate(name, ""))
+}