Repository: Tochemey/goakt
Pull request #597 (2025-01-13 17:29:22)

feat: add TLS support
- [x] Implementation completed
- [x] Fix flakey tests
- [ ] QA

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/Tochemey/goakt/pull/597?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene) Report
Attention: Patch coverage is `84.68468%` with `17 lines` in your changes missing coverage. Please review.
> Project coverage is 86.98%. Comparing base [(`b7db344`)](https://app.codecov.io/gh/Tochemey/goakt/commit/b7db3443bf284372d2cd5121cd2e00f3b50cf478?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene) to head [(`ddc940b`)](https://app.codecov.io/gh/Tochemey/goakt/commit/ddc940b572bfe6b7559aa62420e015a598199b88?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene).

| [Files with missing lines](https://app.codecov.io/gh/Tochemey/goakt/pull/597?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene) | Patch % | Lines |
|---|---|---|
| [client/node.go](https://app.codecov.io/gh/Tochemey/goakt/pull/597?src=pr&el=tree&filepath=client%2Fnode.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene#diff-Y2xpZW50L25vZGUuZ28=) | 0.00% | [9 Missing and 2 partials :warning: ](https://app.codecov.io/gh/Tochemey/goakt/pull/597?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene) |
| [actors/actor\_system.go](https://app.codecov.io/gh/Tochemey/goakt/pull/597?src=pr&el=tree&filepath=actors%2Factor_system.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene#diff-YWN0b3JzL2FjdG9yX3N5c3RlbS5nbw==) | 91.54% | [4 Missing and 2 partials :warning: ](https://app.codecov.io/gh/Tochemey/goakt/pull/597?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #597      +/-   ##
==========================================
- Coverage   87.22%   86.98%   -0.25%     
==========================================
  Files          58       58              
  Lines        5536     5610      +74     
==========================================
+ Hits         4829     4880      +51     
- Misses        568      586      +18     
- Partials      139      144       +5     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/Tochemey/goakt/pull/597?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene).

github-advanced-security[bot]: This pull request sets up GitHub code scanning for this repository. Once the scans have completed and the checks have passed, the analysis results for this pull request branch will appear on [this overview](/Tochemey/goakt/security/code-scanning?query=pr%3A597+is%3Aopen). Once you merge this pull request, the 'Security' tab will show more code scanning analysis results (for example, for the default branch). Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results. For more information about GitHub code scanning, check out [the documentation](https://docs.github.com/code-security/code-scanning/introduction-to-code-scanning/about-code-scanning). 

Patch:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 944b90a..84325f5 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -22,7 +22,7 @@ jobs:
           fetch-depth: 0
       - uses: actions/setup-go@v5
         with:
-          go-version: '>=1.22.0'
+          go-version: '1.22.0'
           check-latest: true
           cache-dependency-path: "**/*.sum"
       - run: go version
@@ -47,7 +47,7 @@ jobs:
       - uses: actions/checkout@v4
       - uses: actions/setup-go@v5
         with:
-          go-version: '>=1.22.0'
+          go-version: '1.22.0'
           check-latest: true
           cache-dependency-path: "**/*.sum"
       - name: golangci-lint
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
new file mode 100644
index 0000000..0ac6691
--- /dev/null
+++ b/.github/workflows/codeql-analysis.yml
@@ -0,0 +1,70 @@
+# For most projects, this workflow file will not need changing; you simply need
+# to commit it to your repository.
+#
+# You may wish to alter this file to override the set of languages analyzed,
+# or to provide custom queries or build logic.
+#
+# ******** NOTE ********
+# We have attempted to detect the languages in your repository. Please check
+# the `language` matrix defined below to confirm you have the correct set of
+# supported CodeQL languages.
+#
+name: "CodeQL"
+
+on:
+  push:
+    branches: [ main ]
+  pull_request:
+    # The branches below must be a subset of the branches above
+    branches: [ main ]
+  schedule:
+    - cron: '28 20 * * 0'
+
+jobs:
+  analyze:
+    name: Analyze
+    runs-on: ubuntu-latest
+    permissions:
+      actions: read
+      contents: read
+      security-events: write
+
+    strategy:
+      fail-fast: false
+      matrix:
+        language: [ 'go' ]
+        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
+        # Learn more about CodeQL language support at https://git.io/codeql-language-support
+
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v4
+
+    # Initializes the CodeQL tools for scanning.
+    - name: Initialize CodeQL
+      uses: github/codeql-action/init@v2
+      with:
+        languages: ${{ matrix.language }}
+        # If you wish to specify custom queries, you can do so here or in a config file.
+        # By default, queries listed here will override any specified in a config file.
+        # Prefix the list here with "+" to use these queries and those in the config file.
+        # queries: ./path/to/local/query, your-org/your-repo/queries@main
+
+    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
+    # If this step fails, then you should remove it and run the build manually (see below)
+    - name: Autobuild
+      uses: github/codeql-action/autobuild@v2
+
+    # ℹ️ Command-line programs to run using the OS shell.
+    # 📚 https://git.io/JvXDl
+
+    # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
+    #    and modify them (or add more) to build your code if your project
+    #    uses a compiled language
+
+    #- run: |
+    #   make bootstrap
+    #   make release
+
+    - name: Perform CodeQL Analysis
+      uses: github/codeql-action/analyze@v2
diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml
index 338b47c..118d354 100644
--- a/.github/workflows/pr.yml
+++ b/.github/workflows/pr.yml
@@ -26,7 +26,7 @@ jobs:
           fetch-depth: 0
       - uses: actions/setup-go@v5
         with:
-          go-version: '>=1.22.0'
+          go-version: '1.22.0'
           check-latest: true
           cache-dependency-path: "**/*.sum"
       - run: go version
@@ -51,7 +51,7 @@ jobs:
       - uses: actions/checkout@v4
       - uses: actions/setup-go@v5
         with:
-          go-version: '>=1.22.0'
+          go-version: '1.22.0'
           check-latest: true
           cache-dependency-path: "**/*.sum"
       - name: golangci-lint
diff --git a/actors/actor_system.go b/actors/actor_system.go
index 5bfae9a..6e5227a 100644
--- a/actors/actor_system.go
+++ b/actors/actor_system.go
@@ -26,6 +26,7 @@ package actors
 
 import (
 	"context"
+	"crypto/tls"
 	"errors"
 	"fmt"
 	"net"
@@ -292,6 +293,9 @@ type actorSystem struct {
 
 	actorsCounter      *atomic.Uint64
 	deadlettersCounter *atomic.Uint64
+
+	tlsClientConfig *tls.Config
+	tlsServerConfig *tls.Config
 }
 
 var (
@@ -365,10 +369,13 @@ func NewActorSystem(name string, opts ...Option) (ActorSystem, error) {
 		}
 	}
 
-	system.scheduler = newScheduler(system.logger,
-		system.shutdownTimeout,
-		withSchedulerCluster(system.cluster),
-		withSchedulerRemoting(NewRemoting()))
+	// perform some quick validations on the TLS configurations
+	if (system.tlsServerConfig == nil) != (system.tlsClientConfig == nil) {
+		return nil, ErrInvalidTLSConfiguration
+	}
+
+	// append the right protocols to the TLS settings
+	system.ensureTLSProtos()
 
 	return system, nil
 }
@@ -452,7 +459,7 @@ func (x *actorSystem) Start(ctx context.Context) error {
 		return err
 	}
 
-	x.scheduler.Start(ctx)
+	x.startMessagesScheduler(ctx)
 	x.startedAt.Store(time.Now().Unix())
 	x.logger.Infof("%s actor system successfully started..:)", x.name)
 	return nil
@@ -1356,6 +1363,7 @@ func (x *actorSystem) enableClustering(ctx context.Context) error {
 		cluster.WithWriteQuorum(x.clusterConfig.WriteQuorum()),
 		cluster.WithReadQuorum(x.clusterConfig.ReadQuorum()),
 		cluster.WithReplicaCount(x.clusterConfig.ReplicaCount()),
+		cluster.WithTLS(x.tlsServerConfig, x.tlsClientConfig),
 	)
 	if err != nil {
 		x.logger.Errorf("failed to initialize cluster engine: %v", err)
@@ -1403,6 +1411,7 @@ func (x *actorSystem) enableRemoting(ctx context.Context) error {
 	if !x.remotingEnabled.Load() {
 		return nil
 	}
+
 	x.logger.Info("enabling remoting...")
 	remotingServicePath, remotingServiceHandler := internalpbconnect.NewRemotingServiceHandler(x)
 	clusterServicePath, clusterServiceHandler := internalpbconnect.NewClusterServiceHandler(x)
@@ -1429,11 +1438,49 @@ func (x *actorSystem) enableRemoting(ctx context.Context) error {
 		}
 	}()
 
-	x.remoting = NewRemoting()
+	// configure remoting
+	x.setRemoting()
 	x.logger.Info("remoting enabled...:)")
 	return nil
 }
 
+// setRemoting sets the remoting service
+func (x *actorSystem) setRemoting() {
+	if x.tlsClientConfig != nil {
+		x.remoting = NewRemoting(WithRemotingTLS(x.tlsClientConfig))
+		return
+	}
+	x.remoting = NewRemoting()
+}
+
+// startMessagesScheduler starts the messages scheduler
+func (x *actorSystem) startMessagesScheduler(ctx context.Context) {
+	// set the scheduler
+	x.scheduler = newScheduler(x.logger,
+		x.shutdownTimeout,
+		withSchedulerCluster(x.cluster),
+		withSchedulerRemoting(x.remoting))
+	// start the scheduler
+	x.scheduler.Start(ctx)
+}
+
+func (x *actorSystem) ensureTLSProtos() {
+	if x.tlsServerConfig != nil && x.tlsClientConfig != nil {
+		// ensure that the required protocols are set for the TLS
+		toAdd := []string{"h2", "http/1.1"}
+
+		// server application protocols setting
+		protos := goset.NewSet[string](x.tlsServerConfig.NextProtos...)
+		protos.Append(toAdd...)
+		x.tlsServerConfig.NextProtos = protos.ToSlice()
+
+		// client application protocols setting
+		protos = goset.NewSet[string](x.tlsClientConfig.NextProtos...)
+		protos.Append(toAdd...)
+		x.tlsClientConfig.NextProtos = protos.ToSlice()
+	}
+}
+
 // reset the actor system
 func (x *actorSystem) reset() {
 	x.actors.Reset()
@@ -1700,6 +1747,7 @@ func (x *actorSystem) configPID(ctx context.Context, name string, actor Actor, o
 		withActorSystem(x),
 		withEventsStream(x.eventsStream),
 		withInitTimeout(x.actorInitTimeout),
+		withRemoting(x.remoting),
 	}
 
 	spawnConfig := newSpawnConfig(opts...)
@@ -1772,22 +1820,31 @@ func (x *actorSystem) shutdownHTTPServer(ctx context.Context) error {
 func (x *actorSystem) configureServer(ctx context.Context, mux *nethttp.ServeMux) error {
 	hostPort := net.JoinHostPort(x.host, strconv.Itoa(int(x.port)))
 	httpServer := getServer(ctx, hostPort)
-	// create a tcp listener
-	lnr, err := net.Listen("tcp", hostPort)
+	listener, err := tcp.NewKeepAliveListener(httpServer.Addr)
 	if err != nil {
 		return err
 	}
 
-	// set the http server
+	// Configure HTTP/2 with performance tuning
+	http2Server := &http2.Server{
+		MaxConcurrentStreams: 1000,               // Allow up to 1000 concurrent streams
+		MaxReadFrameSize:     10 << 20,           // 10 MB max frame size
+		IdleTimeout:          1200 * time.Second, // Timeout for idle connections
+	}
+
+	// set the http TLS server
+	if x.tlsServerConfig != nil {
+		x.server = httpServer
+		x.server.TLSConfig = x.tlsServerConfig
+		x.server.Handler = mux
+		x.listener = tls.NewListener(listener, x.tlsServerConfig)
+		return http2.ConfigureServer(x.server, http2Server)
+	}
+
+	// http/2 server with h2c (HTTP/2 Cleartext).
 	x.server = httpServer
-	// For gRPC clients, it's convenient to support HTTP/2 without TLS.
-	x.server.Handler = h2c.NewHandler(
-		mux, &http2.Server{
-			IdleTimeout: 1200 * time.Second,
-		},
-	)
-	// set the non-secure http server
-	x.listener = lnr
+	x.server.Handler = h2c.NewHandler(mux, http2Server)
+	x.listener = listener
 	return nil
 }
 
@@ -1879,25 +1936,27 @@ func (x *actorSystem) spawnJanitor(ctx context.Context) error {
 
 // spawnRebalancer creates the cluster rebalancer
 func (x *actorSystem) spawnRebalancer(ctx context.Context) error {
-	var err error
-	actorName := x.reservedName(rebalancerType)
-	x.rebalancer, err = x.configPID(ctx,
-		actorName,
-		newRebalancer(x.reflection),
-		WithSupervisorStrategies(
-			NewSupervisorStrategy(PanicError{}, NewRestartDirective()),
-			NewSupervisorStrategy(&runtime.PanicNilError{}, NewRestartDirective()),
-			NewSupervisorStrategy(rebalancingError{}, NewRestartDirective()),
-			NewSupervisorStrategy(InternalError{}, NewResumeDirective()),
-			NewSupervisorStrategy(SpawnError{}, NewResumeDirective()),
-		),
-	)
-	if err != nil {
-		return fmt.Errorf("actor=%s failed to start cluster rebalancer: %w", actorName, err)
-	}
+	if x.clusterEnabled.Load() {
+		var err error
+		actorName := x.reservedName(rebalancerType)
+		x.rebalancer, err = x.configPID(ctx,
+			actorName,
+			newRebalancer(x.reflection, x.remoting),
+			WithSupervisorStrategies(
+				NewSupervisorStrategy(PanicError{}, NewRestartDirective()),
+				NewSupervisorStrategy(&runtime.PanicNilError{}, NewRestartDirective()),
+				NewSupervisorStrategy(rebalancingError{}, NewRestartDirective()),
+				NewSupervisorStrategy(InternalError{}, NewResumeDirective()),
+				NewSupervisorStrategy(SpawnError{}, NewResumeDirective()),
+			),
+		)
+		if err != nil {
+			return fmt.Errorf("actor=%s failed to start cluster rebalancer: %w", actorName, err)
+		}
 
-	// the rebalancer is a child actor of the system guardian
-	_ = x.actors.AddNode(x.systemGuardian, x.rebalancer)
+		// the rebalancer is a child actor of the system guardian
+		_ = x.actors.AddNode(x.systemGuardian, x.rebalancer)
+	}
 	return nil
 }
 
diff --git a/actors/errors.go b/actors/errors.go
index e1c2fc6..23b9cb3 100644
--- a/actors/errors.go
+++ b/actors/errors.go
@@ -93,6 +93,8 @@ var (
 	ErrPriorityMessageRequired = errors.New("priority message type is required")
 	// ErrActorAlreadyExists is returned when trying to create the same actor more than once
 	ErrActorAlreadyExists = func(actorName string) error { return fmt.Errorf("actor=(%s) already exists", actorName) }
+	// ErrInvalidTLSConfiguration is returned whent the TLS configuration is not properly set
+	ErrInvalidTLSConfiguration = errors.New("TLS configuration is invalid")
 )
 
 // eof returns true if the given error is an EOF error
diff --git a/actors/option.go b/actors/option.go
index 4f80bd6..2520c3f 100644
--- a/actors/option.go
+++ b/actors/option.go
@@ -26,6 +26,7 @@ package actors
 
 import (
 	"context"
+	"crypto/tls"
 	"time"
 
 	"github.com/tochemey/goakt/v2/hash"
@@ -175,3 +176,17 @@ func WithCoordinatedShutdown(hooks ...ShutdownHook) Option {
 		system.shutdownHooks = append(system.shutdownHooks, hooks...)
 	})
 }
+
+// WithTLS configures TLS settings for both the Server and Client.
+// Ensure that both the Server and Client are configured with the same
+// root Certificate Authority (CA) to enable successful handshake and
+// mutual authentication.
+//
+// In cluster mode, all nodes must share the same root CA to establish
+// secure communication and complete handshakes successfully.
+func WithTLS(serverConfig, clientConfig *tls.Config) Option {
+	return OptionFunc(func(system *actorSystem) {
+		system.tlsServerConfig = serverConfig
+		system.tlsClientConfig = clientConfig
+	})
+}
diff --git a/actors/rebalancer.go b/actors/rebalancer.go
index aae31ad..7442f21 100644
--- a/actors/rebalancer.go
+++ b/actors/rebalancer.go
@@ -51,9 +51,10 @@ type rebalancer struct {
 var _ Actor = (*rebalancer)(nil)
 
 // newRebalancer creates an instance of rebalancer
-func newRebalancer(reflection *reflection) *rebalancer {
+func newRebalancer(reflection *reflection, remoting *Remoting) *rebalancer {
 	return &rebalancer{
 		reflection: reflection,
+		remoting:   remoting,
 	}
 }
 
@@ -67,7 +68,6 @@ func (r *rebalancer) Receive(ctx *ReceiveContext) {
 	switch ctx.Message().(type) {
 	case *goaktpb.PostStart:
 		r.pid = ctx.Self()
-		r.remoting = NewRemoting()
 		r.logger = ctx.Logger()
 		r.logger.Infof("%s started successfully", r.pid.Name())
 		ctx.Become(r.Rebalance)
diff --git a/actors/remoting.go b/actors/remoting.go
index 05cec62..b5c4924 100644
--- a/actors/remoting.go
+++ b/actors/remoting.go
@@ -26,6 +26,7 @@ package actors
 
 import (
 	"context"
+	"crypto/tls"
 	"errors"
 	nethttp "net/http"
 	"strings"
@@ -42,10 +43,28 @@ import (
 	"github.com/tochemey/goakt/v2/internal/internalpb/internalpbconnect"
 )
 
+// RemotingOption sets the remoting option
+type RemotingOption func(*Remoting)
+
+// WithRemotingTLS configures the remoting system to use a secure connection
+// for communication with the specified remote node. This requires a TLS
+// client configuration to enable secure interactions with the remote actor system.
+//
+// Ensure that the remote actor system is configured with TLS enabled and
+// capable of completing a successful handshake. It is recommended that both
+// systems share the same root Certificate Authority (CA) for mutual trust and
+// secure communication.
+func WithRemotingTLS(tlsConfig *tls.Config) RemotingOption {
+	return func(r *Remoting) {
+		r.tlsConfig = tlsConfig
+	}
+}
+
 // Remoting defines the Remoting APIs
 // This requires Remoting is enabled on the connected actor system
 type Remoting struct {
-	client *nethttp.Client
+	client    *nethttp.Client
+	tlsConfig *tls.Config
 }
 
 // NewRemoting creates an instance Remoting with an insecure connection. To use a secure connection
@@ -54,10 +73,19 @@ type Remoting struct {
 // Make sure to call Close to free up resources otherwise you may be leaking socket connections
 //
 // One can also override the remoting option when calling any of the method for custom one.
-func NewRemoting() *Remoting {
+func NewRemoting(opts ...RemotingOption) *Remoting {
 	r := &Remoting{
 		client: http.NewClient(),
 	}
+
+	for _, opt := range opts {
+		opt(r)
+	}
+
+	if r.tlsConfig != nil {
+		r.client = http.NewTLSClient(r.tlsConfig)
+	}
+
 	return r
 }
 
@@ -356,5 +384,14 @@ func (r *Remoting) Close() {
 // serviceClient returns a Remoting service client instance
 func (r *Remoting) serviceClient(host string, port int) internalpbconnect.RemotingServiceClient {
 	endpoint := http.URL(host, port)
-	return internalpbconnect.NewRemotingServiceClient(r.client, endpoint)
+	if r.tlsConfig != nil {
+		endpoint = http.URLs(host, port)
+	}
+
+	return internalpbconnect.NewRemotingServiceClient(
+		r.client,
+		endpoint,
+		connect.WithGRPC(),
+		connect.WithSendGzip(),
+	)
 }
diff --git a/client/client.go b/client/client.go
index 0eb2861..cd7b106 100644
--- a/client/client.go
+++ b/client/client.go
@@ -111,6 +111,8 @@ func (x *Client) Kinds(ctx context.Context) ([]string, error) {
 	service := internalpbconnect.NewClusterServiceClient(
 		node.HTTPClient(),
 		node.HTTPEndPoint(),
+		connect.WithGRPC(),
+		connect.WithSendGzip(),
 	)
 
 	response, err := service.GetKinds(
@@ -295,6 +297,8 @@ func getNodeMetric(ctx context.Context, node *Node) (int, bool, error) {
 	service := internalpbconnect.NewClusterServiceClient(
 		node.HTTPClient(),
 		node.HTTPEndPoint(),
+		connect.WithGRPC(),
+		connect.WithSendGzip(),
 	)
 
 	response, err := service.GetNodeMetric(ctx, connect.NewRequest(&internalpb.GetNodeMetricRequest{NodeAddress: node.Address()}))
diff --git a/client/node.go b/client/node.go
index 5eac262..fcfdb27 100644
--- a/client/node.go
+++ b/client/node.go
@@ -25,6 +25,7 @@
 package client
 
 import (
+	"crypto/tls"
 	"net"
 	nethttp "net/http"
 	"strconv"
@@ -44,14 +45,29 @@ func WithWeight(weight float64) NodeOption {
 	}
 }
 
+// WithTLS configures the node to use a secure connection
+// for communication with the specified remote node. This requires a TLS
+// client configuration to enable secure interactions with the remote actor system.
+//
+// Ensure that the actor cluster is configured with TLS enabled and
+// capable of completing a successful handshake. It is recommended that both
+// systems share the same root Certificate Authority (CA) for mutual trust and
+// secure communication.
+func WithTLS(config *tls.Config) NodeOption {
+	return func(n *Node) {
+		n.tlsConfig = config
+	}
+}
+
 // Node represents the node in the cluster
 type Node struct {
 	address string
 	weight  float64
 	mutex   *sync.Mutex
 
-	client   *nethttp.Client
-	remoting *actors.Remoting
+	client    *nethttp.Client
+	remoting  *actors.Remoting
+	tlsConfig *tls.Config
 }
 
 // NewNode creates an instance of Node
@@ -67,6 +83,11 @@ func NewNode(address string, opts ...NodeOption) *Node {
 		opt(node)
 	}
 
+	if node.tlsConfig != nil {
+		node.client = http.NewTLSClient(node.tlsConfig)
+		node.remoting = actors.NewRemoting(actors.WithRemotingTLS(node.tlsConfig))
+	}
+
 	return node
 }
 
@@ -123,6 +144,9 @@ func (n *Node) HTTPEndPoint() string {
 	host, p, _ := net.SplitHostPort(n.address)
 	port, _ := strconv.Atoi(p)
 	n.mutex.Unlock()
+	if n.tlsConfig != nil {
+		return http.URLs(host, port)
+	}
 	return http.URL(host, port)
 }
 
diff --git a/discovery/errors.go b/discovery/errors.go
index f002f3a..65c404c 100644
--- a/discovery/errors.go
+++ b/discovery/errors.go
@@ -24,7 +24,7 @@
 
 package discovery
 
-import "github.com/pkg/errors"
+import "errors"
 
 var (
 	// ErrAlreadyInitialized is used when attempting to re-initialize the discovery provider
diff --git a/discovery/kubernetes/discovery.go b/discovery/kubernetes/discovery.go
index 902630c..d8681e8 100644
--- a/discovery/kubernetes/discovery.go
+++ b/discovery/kubernetes/discovery.go
@@ -26,12 +26,12 @@ package kubernetes
 
 import (
 	"context"
+	"fmt"
 	"net"
 	"strconv"
 	"sync"
 
 	goset "github.com/deckarep/golang-set/v2"
-	"github.com/pkg/errors"
 	"go.uber.org/atomic"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -98,12 +98,12 @@ func (d *Discovery) Register() error {
 
 	config, err := rest.InClusterConfig()
 	if err != nil {
-		return errors.Wrap(err, "failed to get the in-cluster config of the kubernetes provider")
+		return fmt.Errorf("failed to get the in-cluster config of the kubernetes provider: %w", err)
 	}
 
 	client, err := kubernetes.NewForConfig(config)
 	if err != nil {
-		return errors.Wrap(err, "failed to create the kubernetes client api")
+		return fmt.Errorf("failed to create the kubernetes client api: %w", err)
 	}
 
 	d.client = client
diff --git a/discovery/mdns/discovery.go b/discovery/mdns/discovery.go
index 4bf8440..47b69c1 100644
--- a/discovery/mdns/discovery.go
+++ b/discovery/mdns/discovery.go
@@ -26,6 +26,7 @@ package mdns
 
 import (
 	"context"
+	"fmt"
 	"net"
 	"strconv"
 	"sync"
@@ -33,7 +34,6 @@ import (
 
 	goset "github.com/deckarep/golang-set/v2"
 	"github.com/grandcat/zeroconf"
-	"github.com/pkg/errors"
 	"go.uber.org/atomic"
 
 	"github.com/tochemey/goakt/v2/discovery"
@@ -97,7 +97,7 @@ func (d *Discovery) Register() error {
 
 	res, err := zeroconf.NewResolver(nil)
 	if err != nil {
-		return errors.Wrap(err, "failed to instantiate the mDNS discovery provider")
+		return fmt.Errorf("failed to instantiate the mDNS discovery provider: %w", err)
 	}
 
 	d.resolver = res
diff --git a/go.mod b/go.mod
index fe66768..98e41cc 100644
--- a/go.mod
+++ b/go.mod
@@ -12,11 +12,10 @@ require (
 	github.com/hashicorp/go-sockaddr v1.0.7
 	github.com/nats-io/nats-server/v2 v2.10.24
 	github.com/nats-io/nats.go v1.38.0
-	github.com/pkg/errors v0.9.1
 	github.com/redis/go-redis/v9 v9.7.0
 	github.com/reugn/go-quartz v0.13.0
 	github.com/stretchr/testify v1.10.0
-	github.com/tochemey/olric v0.1.1
+	github.com/tochemey/olric v0.1.2-0.20250113121802-36ab86212cba
 	github.com/travisjeffery/go-dynaport v1.0.0
 	github.com/zeebo/xxh3 v1.0.2
 	go.uber.org/atomic v1.11.0
@@ -24,7 +23,7 @@ require (
 	go.uber.org/zap v1.27.0
 	golang.org/x/net v0.34.0
 	golang.org/x/sync v0.10.0
-	google.golang.org/protobuf v1.36.2
+	google.golang.org/protobuf v1.36.3
 	k8s.io/api v0.31.4
 	k8s.io/apimachinery v0.31.4
 	k8s.io/client-go v0.31.4
@@ -74,6 +73,7 @@ require (
 	github.com/nats-io/jwt/v2 v2.7.3 // indirect
 	github.com/nats-io/nkeys v0.4.9 // indirect
 	github.com/nats-io/nuid v1.0.1 // indirect
+	github.com/pkg/errors v0.9.1 // indirect
 	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
 	github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect
 	github.com/stretchr/objx v0.5.2 // indirect
diff --git a/go.sum b/go.sum
index 6008c96..b6011b2 100644
--- a/go.sum
+++ b/go.sum
@@ -244,8 +244,8 @@ github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT
 github.com/tidwall/redcon v1.6.2 h1:5qfvrrybgtO85jnhSravmkZyC0D+7WstbfCs3MmPhow=
 github.com/tidwall/redcon v1.6.2/go.mod h1:p5Wbsgeyi2VSTBWOcA5vRXrOb9arFTcU2+ZzFjqV75Y=
 github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
-github.com/tochemey/olric v0.1.1 h1:f/eXaB01x+E5f60PoKEwhia70hgbutz9zmmXaTOEiDc=
-github.com/tochemey/olric v0.1.1/go.mod h1:x0Q6soq7bk0rWSDFPzE4fA8a5m+MnWxHRHZzg/ywg0M=
+github.com/tochemey/olric v0.1.2-0.20250113121802-36ab86212cba h1:E6zoEwD+z17XyXgTQOIczS2S1t9Reb9oXDHz/6+VeTo=
+github.com/tochemey/olric v0.1.2-0.20250113121802-36ab86212cba/go.mod h1:x0Q6soq7bk0rWSDFPzE4fA8a5m+MnWxHRHZzg/ywg0M=
 github.com/travisjeffery/go-dynaport v1.0.0 h1:m/qqf5AHgB96CMMSworIPyo1i7NZueRsnwdzdCJ8Ajw=
 github.com/travisjeffery/go-dynaport v1.0.0/go.mod h1:0LHuDS4QAx+mAc4ri3WkQdavgVoBIZ7cE9ob17KIAJk=
 github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
@@ -351,8 +351,8 @@ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miE
 google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
-google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU=
-google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
+google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU=
+google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/internal/cluster/discovery.go b/internal/cluster/discovery.go
index cc3ec87..9ad0607 100644
--- a/internal/cluster/discovery.go
+++ b/internal/cluster/discovery.go
@@ -25,10 +25,10 @@
 package cluster
 
 import (
+	"errors"
 	golog "log"
 	"strings"
 
-	"github.com/pkg/errors"
 	"github.com/tochemey/olric/pkg/service_discovery"
 
 	"github.com/tochemey/goakt/v2/discovery"
diff --git a/internal/cluster/engine.go b/internal/cluster/engine.go
index db17ba8..5e7e42a 100644
--- a/internal/cluster/engine.go
+++ b/internal/cluster/engine.go
@@ -26,6 +26,7 @@ package cluster
 
 import (
 	"context"
+	"crypto/tls"
 	"encoding/json"
 	"errors"
 	"fmt"
@@ -168,6 +169,9 @@ type Engine struct {
 
 	nodeJoinedEventsFilter goset.Set[string]
 	nodeLeftEventsFilter   goset.Set[string]
+
+	tlsClientConfig *tls.Config
+	tlsServerConfig *tls.Config
 }
 
 // enforce compilation error
@@ -202,6 +206,11 @@ func NewEngine(name string, disco discovery.Provider, host *discovery.Node, opts
 		opt.Apply(engine)
 	}
 
+	// perform some quick validations
+	if (engine.tlsServerConfig == nil) != (engine.tlsClientConfig == nil) {
+		return nil, ErrInvalidTLSConfiguration
+	}
+
 	// set the node startNode
 	engine.node = host
 
@@ -214,7 +223,12 @@ func (x *Engine) Start(ctx context.Context) error {
 
 	logger.Infof("Starting GoAkt cluster Engine service on node=(%s)....🤔", x.node.PeersAddress())
 
-	conf := x.buildConfig()
+	conf, err := x.buildConfig()
+	if err != nil {
+		logger.Errorf("failed to build the cluster Engine configuration.💥: %v", err)
+		return err
+	}
+
 	conf.Hasher = &hasherWrapper{x.hasher}
 
 	m, err := config.NewMemberlistConfig("lan")
@@ -735,7 +749,7 @@ func (x *Engine) consume() {
 }
 
 // buildConfig builds the Node configuration
-func (x *Engine) buildConfig() *config.Config {
+func (x *Engine) buildConfig() (*config.Config, error) {
 	// define the log level
 	logLevel := "INFO"
 	switch x.logger.LogLevel() {
@@ -774,12 +788,29 @@ func (x *Engine) buildConfig() *config.Config {
 		TriggerBalancerInterval:    config.DefaultTriggerBalancerInterval,
 	}
 
+	// Set TLS configuration accordingly
+	if x.tlsServerConfig != nil && x.tlsClientConfig != nil {
+		// set the server TLS config
+		conf.TlsConfig = x.tlsServerConfig
+
+		// create a client configuration that will be used by the
+		// embedded client calls
+		client := &config.Client{TLSConfig: x.tlsClientConfig}
+		// sanitize client configuration
+		if err := client.Sanitize(); err != nil {
+			return nil, fmt.Errorf("failed to sanitize client config: %v", err)
+		}
+
+		// set the client configuration
+		conf.Client = client
+	}
+
 	// set verbosity when debug is enabled
 	if x.logger.LogLevel() == log.DebugLevel {
 		conf.LogVerbosity = config.DefaultLogVerbosity
 	}
 
-	return conf
+	return conf, nil
 }
 
 // initializeState sets the node state in the cluster after boot
diff --git a/internal/cluster/errors.go b/internal/cluster/errors.go
index 7cf6345..b91eab0 100644
--- a/internal/cluster/errors.go
+++ b/internal/cluster/errors.go
@@ -24,11 +24,13 @@
 
 package cluster
 
-import "github.com/pkg/errors"
+import "errors"
 
 var (
 	// ErrActorNotFound is return when an actor is not found
 	ErrActorNotFound = errors.New("actor not found")
 	// ErrPeerSyncNotFound is returned when a peerSync record is not found
 	ErrPeerSyncNotFound = errors.New("peerSync record not found")
+	// ErrInvalidTLSConfiguration is returned whent the TLS configuration is not properly set
+	ErrInvalidTLSConfiguration = errors.New("TLS configuration is invalid")
 )
diff --git a/internal/cluster/option.go b/internal/cluster/option.go
index 985c0c5..6cdbdf4 100644
--- a/internal/cluster/option.go
+++ b/internal/cluster/option.go
@@ -25,6 +25,7 @@
 package cluster
 
 import (
+	"crypto/tls"
 	"time"
 
 	"github.com/tochemey/goakt/v2/hash"
@@ -118,3 +119,13 @@ func WithReadQuorum(count uint32) Option {
 		eng.readQuorum = count
 	})
 }
+
+// WithTLS sets the various TLS for both Server and Client
+// configuration. Bear in mind that both Client and Server must have the same
+// root CA for successful handshake and authentication
+func WithTLS(serverConfig, clientConfig *tls.Config) Option {
+	return OptionFunc(func(eng *Engine) {
+		eng.tlsClientConfig = clientConfig
+		eng.tlsServerConfig = serverConfig
+	})
+}
diff --git a/internal/http/http.go b/internal/http/http.go
index 8ce9b1f..28c6f72 100644
--- a/internal/http/http.go
+++ b/internal/http/http.go
@@ -34,7 +34,6 @@ import (
 	"time"
 
 	"golang.org/x/net/http2"
-	"golang.org/x/net/http2/h2c"
 )
 
 // NewClient creates a http client use h2c
@@ -45,7 +44,9 @@ func NewClient() *http.Client {
 			return http.ErrUseLastResponse
 		},
 		Transport: &http2.Transport{
-			AllowHTTP: true,
+			TLSClientConfig:    nil,
+			AllowHTTP:          true,
+			DisableCompression: false,
 			DialTLSContext: func(_ context.Context, network, addr string, _ *tls.Config) (net.Conn, error) {
 				// If you're also using this client for non-h2c traffic, you may want to
 				// delegate to tls.Dial if the network isn't TCP or the addr isn't in an
@@ -58,34 +59,21 @@ func NewClient() *http.Client {
 	}
 }
 
-// NewServer returns an instance of an http server
-func NewServer(ctx context.Context, host string, port int, mux *http.ServeMux) *http.Server {
-	// TODO revisit the timeouts
-	// reference: https://adam-p.ca/blog/2022/01/golang-http-server-timeouts/
-	return &http.Server{
-		Addr: net.JoinHostPort(host, strconv.Itoa(port)),
-		// The maximum duration for reading the entire request, including the body.
-		// It’s implemented in net/http by calling SetReadDeadline immediately after Accept
-		// ReadTimeout := handler_timeout + ReadHeaderTimeout + wiggle_room
-		ReadTimeout: 3 * time.Second,
-		// ReadHeaderTimeout is the amount of time allowed to read request headers
-		ReadHeaderTimeout: time.Second,
-		// WriteTimeout is the maximum duration before timing out writes of the response.
-		// It is reset whenever a new request’s header is read.
-		// This effectively covers the lifetime of the ServeHTTP handler stack
-		WriteTimeout: time.Second,
-		// IdleTimeout is the maximum amount of time to wait for the next request when keep-alive are enabled.
-		// If IdleTimeout is zero, the value of ReadTimeout is used. Not relevant to request timeouts
-		IdleTimeout: 1200 * time.Second,
-		// For gRPC clients, it's convenient to support HTTP/2 without TLS. You can
-		// avoid x/net/http2 by using http.ListenAndServeTLS.
-		Handler: h2c.NewHandler(
-			mux, &http2.Server{
-				IdleTimeout: 1200 * time.Second,
+// NewTLSClient creates a secured http client
+func NewTLSClient(tlsConfig *tls.Config) *http.Client {
+	return &http.Client{
+		// Most RPC servers don't use HTTP redirects
+		CheckRedirect: func(_ *http.Request, _ []*http.Request) error {
+			return http.ErrUseLastResponse
+		},
+		Transport: &http2.Transport{
+			TLSClientConfig:    tlsConfig,
+			DisableCompression: false,
+			DialTLSContext: func(_ context.Context, network, addr string, config *tls.Config) (net.Conn, error) {
+				return tls.Dial(network, addr, config)
 			},
-		),
-		BaseContext: func(_ net.Listener) context.Context {
-			return ctx
+			PingTimeout:     30 * time.Second,
+			ReadIdleTimeout: 30 * time.Second,
 		},
 	}
 }
@@ -94,3 +82,8 @@ func NewServer(ctx context.Context, host string, port int, mux *http.ServeMux) *
 func URL(host string, port int) string {
 	return fmt.Sprintf("http://%s", net.JoinHostPort(host, strconv.Itoa(port)))
 }
+
+// URLs create a secured http connection address
+func URLs(host string, port int) string {
+	return fmt.Sprintf("https://%s", net.JoinHostPort(host, strconv.Itoa(port)))
+}
diff --git a/internal/tcp/tcp.go b/internal/tcp/tcp.go
index 8c66bf9..c780739 100644
--- a/internal/tcp/tcp.go
+++ b/internal/tcp/tcp.go
@@ -27,6 +27,7 @@ package tcp
 import (
 	"fmt"
 	"net"
+	"time"
 
 	"github.com/hashicorp/go-sockaddr"
 )
@@ -77,3 +78,31 @@ func GetBindIP(address string) (string, error) {
 	}
 	return bindIP, nil
 }
+
+// KeepAliveListener sets TCP keep-alive timeouts on accepted
+// connections. It's used by Serve so that dead TCP connections eventually
+// go away.
+type KeepAliveListener struct {
+	*net.TCPListener
+}
+
+func (ln KeepAliveListener) Accept() (c net.Conn, err error) {
+	if c, err = ln.AcceptTCP(); err != nil {
+		return
+	} else if err = c.(*net.TCPConn).SetKeepAlive(true); err != nil {
+		return
+	}
+	// Ignore error from setting the KeepAlivePeriod as some systems, such as
+	// OpenBSD, do not support setting TCP_USER_TIMEOUT on IPPROTO_TCP
+	_ = c.(*net.TCPConn).SetKeepAlivePeriod(3 * time.Minute)
+	return
+}
+
+// NewKeepAliveListener creates an instance of KeepAliveListener
+func NewKeepAliveListener(address string) (*KeepAliveListener, error) {
+	listener, err := net.Listen("tcp", address)
+	if err != nil {
+		return nil, err
+	}
+	return &KeepAliveListener{listener.(*net.TCPListener)}, nil
+}


Test patch:
diff --git a/actors/actor_system_test.go b/actors/actor_system_test.go
index 914128f..828436d 100644
--- a/actors/actor_system_test.go
+++ b/actors/actor_system_test.go
@@ -26,13 +26,13 @@ package actors
 
 import (
 	"context"
+	"errors"
 	"net"
 	"strconv"
 	"testing"
 	"time"
 
 	"github.com/google/uuid"
-	"github.com/pkg/errors"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 	"github.com/travisjeffery/go-dynaport"
diff --git a/actors/mock_test.go b/actors/mock_test.go
index 0505e93..7a3d724 100644
--- a/actors/mock_test.go
+++ b/actors/mock_test.go
@@ -27,6 +27,7 @@ package actors
 import (
 	"context"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"strconv"
 	"sync"
@@ -34,7 +35,6 @@ import (
 	"time"
 
 	natsserver "github.com/nats-io/nats-server/v2/server"
-	"github.com/pkg/errors"
 	"github.com/stretchr/testify/require"
 	"github.com/travisjeffery/go-dynaport"
 	"go.uber.org/atomic"
@@ -43,6 +43,7 @@ import (
 	"github.com/tochemey/goakt/v2/discovery/nats"
 	"github.com/tochemey/goakt/v2/goaktpb"
 	"github.com/tochemey/goakt/v2/internal/lib"
+	"github.com/tochemey/goakt/v2/internal/testutil"
 	"github.com/tochemey/goakt/v2/log"
 	"github.com/tochemey/goakt/v2/test/data/testpb"
 	testspb "github.com/tochemey/goakt/v2/test/data/testpb"
@@ -429,13 +430,27 @@ func startNatsServer(t *testing.T) *natsserver.Server {
 	return serv
 }
 
-func startClusterSystem(t *testing.T, serverAddr string) (ActorSystem, discovery.Provider) {
+type testClusterConfig struct {
+	tlsEnabled bool
+	rootCert   *testutil.CertRoot
+}
+
+type testClusterOption func(*testClusterConfig)
+
+func withTSL(rootCert *testutil.CertRoot) testClusterOption {
+	return func(tc *testClusterConfig) {
+		tc.tlsEnabled = true
+		tc.rootCert = rootCert
+	}
+}
+
+func startClusterSystem(t *testing.T, serverAddr string, opts ...testClusterOption) (ActorSystem, discovery.Provider) {
 	ctx := context.TODO()
 	logger := log.DiscardLogger
 
 	// generate the ports for the single startNode
 	nodePorts := dynaport.Get(3)
-	gossipPort := nodePorts[0]
+	discoveryPort := nodePorts[0]
 	clusterPort := nodePorts[1]
 	remotingPort := nodePorts[2]
 
@@ -452,30 +467,43 @@ func startClusterSystem(t *testing.T, serverAddr string) (ActorSystem, discovery
 		NatsServer:      fmt.Sprintf("nats://%s", serverAddr),
 		NatsSubject:     natsSubject,
 		Host:            host,
-		DiscoveryPort:   gossipPort,
+		DiscoveryPort:   discoveryPort,
 	}
 
 	// create the instance of provider
 	provider := nats.NewDiscovery(&config, nats.WithLogger(log.DiscardLogger))
 
-	// create the actor system
-	system, err := NewActorSystem(
-		actorSystemName,
+	// create the actor system options
+	options := []Option{
 		WithPassivationDisabled(),
 		WithLogger(logger),
 		WithRemoting(host, int32(remotingPort)),
 		WithJanitorInterval(time.Minute),
-		WithPeerStateLoopInterval(500*time.Millisecond),
+		WithPeerStateLoopInterval(500 * time.Millisecond),
 		WithCluster(
 			NewClusterConfig().
 				WithKinds(new(mockActor)).
-				WithPartitionCount(10).
+				WithPartitionCount(7).
 				WithReplicaCount(1).
 				WithPeersPort(clusterPort).
 				WithMinimumPeersQuorum(1).
-				WithDiscoveryPort(gossipPort).
+				WithDiscoveryPort(discoveryPort).
 				WithDiscovery(provider)),
-	)
+	}
+
+	cfg := &testClusterConfig{}
+	for _, opt := range opts {
+		opt(cfg)
+	}
+
+	if cfg.tlsEnabled {
+		serverConfig := testutil.GetServerTLSConfig(t, cfg.rootCert)
+		clientConfig := testutil.GetClientTLSConfig(t, cfg.rootCert)
+		options = append(options, WithTLS(serverConfig, clientConfig))
+	}
+
+	// create the actor system
+	system, err := NewActorSystem(actorSystemName, options...)
 
 	require.NotNil(t, system)
 	require.NoError(t, err)
diff --git a/actors/option_test.go b/actors/option_test.go
index 721c78d..01aac20 100644
--- a/actors/option_test.go
+++ b/actors/option_test.go
@@ -26,6 +26,7 @@ package actors
 
 import (
 	"context"
+	"crypto/tls"
 	"testing"
 	"time"
 
@@ -41,7 +42,8 @@ func TestOption(t *testing.T) {
 	atomicTrue.Store(true)
 	clusterConfig := NewClusterConfig()
 	hasher := hash.DefaultHasher()
-
+	// nolint
+	tlsConfig := &tls.Config{}
 	testCases := []struct {
 		name     string
 		option   Option
@@ -107,6 +109,11 @@ func TestOption(t *testing.T) {
 			option:   WithJanitorInterval(2 * time.Second),
 			expected: actorSystem{janitorInterval: 2. * time.Second},
 		},
+		{
+			name:     "WithTLS",
+			option:   WithTLS(tlsConfig, tlsConfig),
+			expected: actorSystem{tlsServerConfig: tlsConfig, tlsClientConfig: tlsConfig},
+		},
 	}
 
 	for _, tc := range testCases {
diff --git a/actors/rebalancer_test.go b/actors/rebalancer_test.go
index 53f61fe..ef0b320 100644
--- a/actors/rebalancer_test.go
+++ b/actors/rebalancer_test.go
@@ -34,6 +34,7 @@ import (
 	"github.com/stretchr/testify/require"
 
 	"github.com/tochemey/goakt/v2/internal/lib"
+	"github.com/tochemey/goakt/v2/internal/testutil"
 	"github.com/tochemey/goakt/v2/test/data/testpb"
 )
 
@@ -103,6 +104,81 @@ func TestRebalancing(t *testing.T) {
 		err = sender.SendAsync(ctx, actorName, new(testpb.TestSend))
 		require.NoError(t, err)
 
+		t.Cleanup(func() {
+			assert.NoError(t, node1.Stop(ctx))
+			assert.NoError(t, node3.Stop(ctx))
+			assert.NoError(t, sd1.Close())
+			assert.NoError(t, sd3.Close())
+			srv.Shutdown()
+		})
+	})
+	t.Run("With successful actors redeployment when TLS enabled", func(t *testing.T) {
+		// create a context
+		ctx := context.TODO()
+		// start the NATS server
+		srv := startNatsServer(t)
+
+		rootCert := testutil.NewCertRoot(t)
+
+		// create and start system cluster
+		node1, sd1 := startClusterSystem(t, srv.Addr().String(), withTSL(rootCert))
+		require.NotNil(t, node1)
+		require.NotNil(t, sd1)
+
+		// create and start system cluster
+		node2, sd2 := startClusterSystem(t, srv.Addr().String(), withTSL(rootCert))
+		require.NotNil(t, node2)
+		require.NotNil(t, sd2)
+
+		// create and start system cluster
+		node3, sd3 := startClusterSystem(t, srv.Addr().String(), withTSL(rootCert))
+		require.NotNil(t, node3)
+		require.NotNil(t, sd3)
+
+		// let us create 4 actors on each node
+		for j := 1; j <= 4; j++ {
+			actorName := fmt.Sprintf("Node1-Actor-%d", j)
+			pid, err := node1.Spawn(ctx, actorName, newMockActor())
+			require.NoError(t, err)
+			require.NotNil(t, pid)
+		}
+
+		lib.Pause(time.Second)
+
+		for j := 1; j <= 4; j++ {
+			actorName := fmt.Sprintf("Node2-Actor-%d", j)
+			pid, err := node2.Spawn(ctx, actorName, newMockActor())
+			require.NoError(t, err)
+			require.NotNil(t, pid)
+		}
+
+		lib.Pause(time.Second)
+
+		for j := 1; j <= 4; j++ {
+			actorName := fmt.Sprintf("Node3-Actor-%d", j)
+			pid, err := node3.Spawn(ctx, actorName, newMockActor())
+			require.NoError(t, err)
+			require.NotNil(t, pid)
+		}
+
+		lib.Pause(time.Second)
+
+		// take down node2
+		require.NoError(t, node2.Stop(ctx))
+		require.NoError(t, sd2.Close())
+
+		// Wait for cluster rebalancing
+		lib.Pause(time.Minute)
+
+		sender, err := node1.LocalActor("Node1-Actor-1")
+		require.NoError(t, err)
+		require.NotNil(t, sender)
+
+		// let us access some of the node2 actors from node 1 and  node 3
+		actorName := "Node2-Actor-1"
+		err = sender.SendAsync(ctx, actorName, new(testpb.TestSend))
+		require.NoError(t, err)
+
 		t.Cleanup(func() {
 			assert.NoError(t, node1.Stop(ctx))
 			assert.NoError(t, node3.Stop(ctx))
diff --git a/actors/remoting_test.go b/actors/remoting_test.go
index 35a3488..f7d49ce 100644
--- a/actors/remoting_test.go
+++ b/actors/remoting_test.go
@@ -38,6 +38,7 @@ import (
 	"github.com/tochemey/goakt/v2/address"
 	"github.com/tochemey/goakt/v2/goaktpb"
 	"github.com/tochemey/goakt/v2/internal/lib"
+	"github.com/tochemey/goakt/v2/internal/testutil"
 	"github.com/tochemey/goakt/v2/log"
 	"github.com/tochemey/goakt/v2/test/data/testpb"
 )
@@ -548,978 +549,1289 @@ func TestRemoteTell(t *testing.T) {
 		err = sys.Stop(ctx)
 		assert.NoError(t, err)
 	})
-}
+	t.Run("With TLS enabled", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		rootCert := testutil.NewCertRoot(t)
+		serverConfig := testutil.GetServerTLSConfig(t, rootCert)
+		clientConfig := testutil.GetClientTLSConfig(t, rootCert)
 
-func TestRemoteAsk(t *testing.T) {
-	t.Run(
-		"With happy path", func(t *testing.T) {
-			// create the context
-			ctx := context.TODO()
-			// define the logger to use
-			logger := log.DiscardLogger
-			// generate the remoting port
-			nodePorts := dynaport.Get(1)
-			remotingPort := nodePorts[0]
-			host := "0.0.0.0"
-
-			// create the actor system
-			sys, err := NewActorSystem(
-				"test",
-				WithLogger(logger),
-				WithPassivationDisabled(),
-				WithRemoting(host, int32(remotingPort)),
-			)
-			// assert there are no error
-			require.NoError(t, err)
+		// define the logger to use
+		logger := log.DiscardLogger
+		// generate the remoting port
+		nodePorts := dynaport.Get(1)
+		remotingPort := nodePorts[0]
+		host := "127.0.0.1"
 
-			// start the actor system
-			err = sys.Start(ctx)
-			assert.NoError(t, err)
+		// create the actor system
+		sys, err := NewActorSystem(
+			"test",
+			WithLogger(logger),
+			WithPassivationDisabled(),
+			WithRemoting(host, int32(remotingPort)),
+			WithTLS(serverConfig, clientConfig),
+		)
+		// assert there are no error
+		require.NoError(t, err)
 
-			lib.Pause(time.Second)
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
 
-			// create a test actor
-			actorName := "test"
-			actor := newMockActor()
-			actorRef, err := sys.Spawn(ctx, actorName, actor)
-			require.NoError(t, err)
-			assert.NotNil(t, actorRef)
+		lib.Pause(time.Second)
 
-			remoting := NewRemoting()
-			from := address.NoSender()
-			// get the address of the actor
-			addr, err := remoting.RemoteLookup(ctx, sys.Host(), int(sys.Port()), actorName)
-			require.NoError(t, err)
+		// create a test actor
+		actorName := "test"
+		actor := newMockActor()
+		actorRef, err := sys.Spawn(ctx, actorName, actor)
+		require.NoError(t, err)
+		assert.NotNil(t, actorRef)
 
-			// create a message to send to the test actor
-			message := new(testpb.TestReply)
-			// send the message to the actor
-			reply, err := remoting.RemoteAsk(ctx, from, addr, message, time.Minute)
+		remoting := NewRemoting(WithRemotingTLS(clientConfig))
+		// get the address of the actor
+		addr, err := remoting.RemoteLookup(ctx, sys.Host(), int(sys.Port()), actorName)
+		require.NoError(t, err)
+		// create a message to send to the test actor
+		message := new(testpb.TestSend)
+		from := address.NoSender()
+		// send the message to the actor
+		for i := 0; i < 10; i++ {
+			err = remoting.RemoteTell(ctx, from, addr, message)
 			// perform some assertions
 			require.NoError(t, err)
-			require.NotNil(t, reply)
-			require.True(t, reply.MessageIs(new(testpb.Reply)))
+		}
 
-			actual := new(testpb.Reply)
-			err = reply.UnmarshalTo(actual)
-			require.NoError(t, err)
+		// stop the actor after some time
+		lib.Pause(time.Second)
 
-			expected := &testpb.Reply{Content: "received message"}
-			assert.True(t, proto.Equal(expected, actual))
-
-			// stop the actor after some time
-			lib.Pause(time.Second)
-
-			remoting.Close()
-			err = sys.Stop(ctx)
-			assert.NoError(t, err)
-		},
-	)
-	t.Run(
-		"With invalid message", func(t *testing.T) {
-			// create the context
-			ctx := context.TODO()
-			// define the logger to use
-			logger := log.DiscardLogger
-			// generate the remoting port
-			nodePorts := dynaport.Get(1)
-			remotingPort := nodePorts[0]
-			host := "0.0.0.0"
-
-			// create the actor system
-			sys, err := NewActorSystem(
-				"test",
-				WithLogger(logger),
-				WithPassivationDisabled(),
-				WithRemoting(host, int32(remotingPort)),
-			)
-			// assert there are no error
-			require.NoError(t, err)
+		remoting.Close()
+		err = sys.Stop(ctx)
+		assert.NoError(t, err)
+	})
+}
 
-			// start the actor system
-			err = sys.Start(ctx)
-			assert.NoError(t, err)
+func TestRemoteAsk(t *testing.T) {
+	t.Run("With happy path", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		// define the logger to use
+		logger := log.DiscardLogger
+		// generate the remoting port
+		nodePorts := dynaport.Get(1)
+		remotingPort := nodePorts[0]
+		host := "0.0.0.0"
 
-			lib.Pause(time.Second)
+		// create the actor system
+		sys, err := NewActorSystem(
+			"test",
+			WithLogger(logger),
+			WithPassivationDisabled(),
+			WithRemoting(host, int32(remotingPort)),
+		)
+		// assert there are no error
+		require.NoError(t, err)
 
-			// create a test actor
-			actorName := "test"
-			actor := newMockActor()
-			actorRef, err := sys.Spawn(ctx, actorName, actor)
-			require.NoError(t, err)
-			assert.NotNil(t, actorRef)
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
 
-			remoting := NewRemoting()
-			// get the address of the actor
-			addr, err := remoting.RemoteLookup(ctx, sys.Host(), int(sys.Port()), actorName)
-			require.NoError(t, err)
+		lib.Pause(time.Second)
 
-			from := address.NoSender()
-			// send the message to the actor
-			reply, err := remoting.RemoteAsk(ctx, from, addr, nil, time.Minute)
-			// perform some assertions
-			require.Error(t, err)
-			require.Nil(t, reply)
-
-			// stop the actor after some time
-			lib.Pause(time.Second)
-
-			err = sys.Stop(ctx)
-			assert.NoError(t, err)
-		},
-	)
-	t.Run(
-		"With remote service failure", func(t *testing.T) {
-			// create the context
-			ctx := context.TODO()
-			// define the logger to use
-			logger := log.DiscardLogger
-			// generate the remoting port
-			nodePorts := dynaport.Get(1)
-			remotingPort := nodePorts[0]
-			host := "0.0.0.0"
-
-			// create the actor system
-			sys, err := NewActorSystem(
-				"test",
-				WithLogger(logger),
-				WithPassivationDisabled(),
-				WithRemoting(host, int32(remotingPort)),
-			)
-			// assert there are no error
-			require.NoError(t, err)
+		// create a test actor
+		actorName := "test"
+		actor := newMockActor()
+		actorRef, err := sys.Spawn(ctx, actorName, actor)
+		require.NoError(t, err)
+		assert.NotNil(t, actorRef)
 
-			// start the actor system
-			err = sys.Start(ctx)
-			assert.NoError(t, err)
+		remoting := NewRemoting()
+		from := address.NoSender()
+		// get the address of the actor
+		addr, err := remoting.RemoteLookup(ctx, sys.Host(), int(sys.Port()), actorName)
+		require.NoError(t, err)
 
-			lib.Pause(time.Second)
+		// create a message to send to the test actor
+		message := new(testpb.TestReply)
+		// send the message to the actor
+		reply, err := remoting.RemoteAsk(ctx, from, addr, message, time.Minute)
+		// perform some assertions
+		require.NoError(t, err)
+		require.NotNil(t, reply)
+		require.True(t, reply.MessageIs(new(testpb.Reply)))
 
-			// create a test actor
-			actorName := "test"
-			actor := newMockActor()
-			actorRef, err := sys.Spawn(ctx, actorName, actor)
-			require.NoError(t, err)
-			assert.NotNil(t, actorRef)
-
-			// get the address of the actor
-			addr := &goaktpb.Address{
-				Host: host,
-				Port: 2222,
-				Name: "",
-				Id:   "",
-			}
-
-			remoting := NewRemoting()
-			from := address.NoSender()
-			// create a message to send to the test actor
-			message := new(testpb.TestReply)
-			// send the message to the actor
-			reply, err := remoting.RemoteAsk(ctx, from, address.From(addr), message, time.Minute)
-			// perform some assertions
-			require.Error(t, err)
-			require.Nil(t, reply)
-
-			// stop the actor after some time
-			lib.Pause(time.Second)
-			remoting.Close()
-			t.Cleanup(
-				func() {
-					assert.NoError(t, sys.Stop(ctx))
-				},
-			)
-		},
-	)
-	t.Run(
-		"With remoting disabled", func(t *testing.T) {
-			// create the context
-			ctx := context.TODO()
-			// define the logger to use
-			logger := log.DiscardLogger
-			// generate the remoting port
-			nodePorts := dynaport.Get(1)
-			remotingPort := nodePorts[0]
-			host := "0.0.0.0"
-
-			// create the actor system
-			sys, err := NewActorSystem(
-				"test",
-				WithLogger(logger),
-				WithPassivationDisabled(),
-				WithRemoting(host, int32(remotingPort)),
-			)
-			// assert there are no error
-			require.NoError(t, err)
+		actual := new(testpb.Reply)
+		err = reply.UnmarshalTo(actual)
+		require.NoError(t, err)
 
-			// start the actor system
-			err = sys.Start(ctx)
-			assert.NoError(t, err)
+		expected := &testpb.Reply{Content: "received message"}
+		assert.True(t, proto.Equal(expected, actual))
 
-			lib.Pause(time.Second)
+		// stop the actor after some time
+		lib.Pause(time.Second)
 
-			// create a test actor
-			actorName := "test"
-			actor := newMockActor()
-			actorRef, err := sys.Spawn(ctx, actorName, actor)
-			require.NoError(t, err)
-			assert.NotNil(t, actorRef)
+		remoting.Close()
+		err = sys.Stop(ctx)
+		assert.NoError(t, err)
+	})
+	t.Run("With invalid message", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		// define the logger to use
+		logger := log.DiscardLogger
+		// generate the remoting port
+		nodePorts := dynaport.Get(1)
+		remotingPort := nodePorts[0]
+		host := "0.0.0.0"
 
-			remoting := NewRemoting()
-			// get the address of the actor
-			addr, err := remoting.RemoteLookup(ctx, sys.Host(), int(sys.Port()), actorName)
-			require.NoError(t, err)
+		// create the actor system
+		sys, err := NewActorSystem(
+			"test",
+			WithLogger(logger),
+			WithPassivationDisabled(),
+			WithRemoting(host, int32(remotingPort)),
+		)
+		// assert there are no error
+		require.NoError(t, err)
 
-			// let us disable remoting
-			actorsSystem := sys.(*actorSystem)
-			actorsSystem.remotingEnabled.Store(false)
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
 
-			from := address.NoSender()
-			// create a message to send to the test actor
-			message := new(testpb.TestReply)
-			// send the message to the actor
-			reply, err := remoting.RemoteAsk(ctx, from, addr, message, time.Minute)
-			// perform some assertions
-			require.Error(t, err)
-			require.EqualError(t, err, "failed_precondition: remoting is not enabled")
-			require.Nil(t, reply)
-
-			// stop the actor after some time
-			lib.Pause(time.Second)
-
-			err = sys.Stop(ctx)
-			assert.NoError(t, err)
-		},
-	)
-	t.Run(
-		"With Batch request", func(t *testing.T) {
-			// create the context
-			ctx := context.TODO()
-			// define the logger to use
-			logger := log.DiscardLogger
-			// generate the remoting port
-			nodePorts := dynaport.Get(1)
-			remotingPort := nodePorts[0]
-			host := "0.0.0.0"
-
-			// create the actor system
-			sys, err := NewActorSystem(
-				"test",
-				WithLogger(logger),
-				WithPassivationDisabled(),
-				WithRemoting(host, int32(remotingPort)),
-			)
-			// assert there are no error
-			require.NoError(t, err)
+		lib.Pause(time.Second)
 
-			// start the actor system
-			err = sys.Start(ctx)
-			assert.NoError(t, err)
+		// create a test actor
+		actorName := "test"
+		actor := newMockActor()
+		actorRef, err := sys.Spawn(ctx, actorName, actor)
+		require.NoError(t, err)
+		assert.NotNil(t, actorRef)
 
-			lib.Pause(time.Second)
+		remoting := NewRemoting()
+		// get the address of the actor
+		addr, err := remoting.RemoteLookup(ctx, sys.Host(), int(sys.Port()), actorName)
+		require.NoError(t, err)
 
-			// create a test actor
-			actorName := "test"
-			actor := newMockActor()
-			actorRef, err := sys.Spawn(ctx, actorName, actor)
-			require.NoError(t, err)
-			assert.NotNil(t, actorRef)
+		from := address.NoSender()
+		// send the message to the actor
+		reply, err := remoting.RemoteAsk(ctx, from, addr, nil, time.Minute)
+		// perform some assertions
+		require.Error(t, err)
+		require.Nil(t, reply)
 
-			remoting := NewRemoting()
-			// get the address of the actor
-			addr, err := remoting.RemoteLookup(ctx, sys.Host(), int(sys.Port()), actorName)
-			require.NoError(t, err)
+		// stop the actor after some time
+		lib.Pause(time.Second)
+
+		err = sys.Stop(ctx)
+		assert.NoError(t, err)
+	})
+	t.Run("With remote service failure", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		// define the logger to use
+		logger := log.DiscardLogger
+		// generate the remoting port
+		nodePorts := dynaport.Get(1)
+		remotingPort := nodePorts[0]
+		host := "0.0.0.0"
+
+		// create the actor system
+		sys, err := NewActorSystem(
+			"test",
+			WithLogger(logger),
+			WithPassivationDisabled(),
+			WithRemoting(host, int32(remotingPort)),
+		)
+		// assert there are no error
+		require.NoError(t, err)
+
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
+
+		lib.Pause(time.Second)
+
+		// create a test actor
+		actorName := "test"
+		actor := newMockActor()
+		actorRef, err := sys.Spawn(ctx, actorName, actor)
+		require.NoError(t, err)
+		assert.NotNil(t, actorRef)
+
+		// get the address of the actor
+		addr := &goaktpb.Address{
+			Host: host,
+			Port: 2222,
+			Name: "",
+			Id:   "",
+		}
+
+		remoting := NewRemoting()
+		from := address.NoSender()
+		// create a message to send to the test actor
+		message := new(testpb.TestReply)
+		// send the message to the actor
+		reply, err := remoting.RemoteAsk(ctx, from, address.From(addr), message, time.Minute)
+		// perform some assertions
+		require.Error(t, err)
+		require.Nil(t, reply)
+
+		// stop the actor after some time
+		lib.Pause(time.Second)
+		remoting.Close()
+		t.Cleanup(
+			func() {
+				assert.NoError(t, sys.Stop(ctx))
+			},
+		)
+	})
+	t.Run("With remoting disabled", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		// define the logger to use
+		logger := log.DiscardLogger
+		// generate the remoting port
+		nodePorts := dynaport.Get(1)
+		remotingPort := nodePorts[0]
+		host := "0.0.0.0"
+
+		// create the actor system
+		sys, err := NewActorSystem(
+			"test",
+			WithLogger(logger),
+			WithPassivationDisabled(),
+			WithRemoting(host, int32(remotingPort)),
+		)
+		// assert there are no error
+		require.NoError(t, err)
+
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
+
+		lib.Pause(time.Second)
+
+		// create a test actor
+		actorName := "test"
+		actor := newMockActor()
+		actorRef, err := sys.Spawn(ctx, actorName, actor)
+		require.NoError(t, err)
+		assert.NotNil(t, actorRef)
+
+		remoting := NewRemoting()
+		// get the address of the actor
+		addr, err := remoting.RemoteLookup(ctx, sys.Host(), int(sys.Port()), actorName)
+		require.NoError(t, err)
+
+		// let us disable remoting
+		actorsSystem := sys.(*actorSystem)
+		actorsSystem.remotingEnabled.Store(false)
+
+		from := address.NoSender()
+		// create a message to send to the test actor
+		message := new(testpb.TestReply)
+		// send the message to the actor
+		reply, err := remoting.RemoteAsk(ctx, from, addr, message, time.Minute)
+		// perform some assertions
+		require.Error(t, err)
+		require.EqualError(t, err, "failed_precondition: remoting is not enabled")
+		require.Nil(t, reply)
+
+		// stop the actor after some time
+		lib.Pause(time.Second)
+
+		err = sys.Stop(ctx)
+		assert.NoError(t, err)
+	})
+	t.Run("With Batch request", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		// define the logger to use
+		logger := log.DiscardLogger
+		// generate the remoting port
+		nodePorts := dynaport.Get(1)
+		remotingPort := nodePorts[0]
+		host := "0.0.0.0"
+
+		// create the actor system
+		sys, err := NewActorSystem(
+			"test",
+			WithLogger(logger),
+			WithPassivationDisabled(),
+			WithRemoting(host, int32(remotingPort)),
+		)
+		// assert there are no error
+		require.NoError(t, err)
+
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
+
+		lib.Pause(time.Second)
+
+		// create a test actor
+		actorName := "test"
+		actor := newMockActor()
+		actorRef, err := sys.Spawn(ctx, actorName, actor)
+		require.NoError(t, err)
+		assert.NotNil(t, actorRef)
+
+		remoting := NewRemoting()
+		// get the address of the actor
+		addr, err := remoting.RemoteLookup(ctx, sys.Host(), int(sys.Port()), actorName)
+		require.NoError(t, err)
+
+		from := address.NoSender()
+		// create a message to send to the test actor
+		message := new(testpb.TestReply)
+		// send the message to the actor
+		replies, err := remoting.RemoteBatchAsk(ctx, from, addr, []proto.Message{message}, time.Minute)
+		// perform some assertions
+		require.NoError(t, err)
+		require.Len(t, replies, 1)
+		require.NotNil(t, replies[0])
+		require.True(t, replies[0].MessageIs(new(testpb.Reply)))
+
+		actual := new(testpb.Reply)
+		err = replies[0].UnmarshalTo(actual)
+		require.NoError(t, err)
+
+		expected := &testpb.Reply{Content: "received message"}
+		assert.True(t, proto.Equal(expected, actual))
+
+		// stop the actor after some time
+		lib.Pause(time.Second)
+
+		remoting.Close()
+		err = sys.Stop(ctx)
+		assert.NoError(t, err)
+	})
+	t.Run("With Batch service failure", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		// define the logger to use
+		logger := log.DiscardLogger
+		// generate the remoting port
+		nodePorts := dynaport.Get(1)
+		remotingPort := nodePorts[0]
+		host := "0.0.0.0"
+
+		// create the actor system
+		sys, err := NewActorSystem(
+			"test",
+			WithLogger(logger),
+			WithPassivationDisabled(),
+			WithRemoting(host, int32(remotingPort)),
+		)
+		// assert there are no error
+		require.NoError(t, err)
+
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
+
+		lib.Pause(time.Second)
+
+		// create a test actor
+		actorName := "test"
+		actor := newMockActor()
+		actorRef, err := sys.Spawn(ctx, actorName, actor)
+		require.NoError(t, err)
+		assert.NotNil(t, actorRef)
+
+		// get the address of the actor
+		addr := &goaktpb.Address{
+			Host: host,
+			Port: 2222,
+			Name: "",
+			Id:   "",
+		}
+
+		remoting := NewRemoting()
+		from := address.NoSender()
+		// create a message to send to the test actor
+		message := new(testpb.TestReply)
+		// send the message to the actor
+		reply, err := remoting.RemoteBatchAsk(ctx, from, address.From(addr), []proto.Message{message}, time.Minute)
+		// perform some assertions
+		require.Error(t, err)
+		require.Nil(t, reply)
+
+		remoting.Close()
+		// stop the actor after some time
+		lib.Pause(time.Second)
+		t.Cleanup(
+			func() {
+				assert.NoError(t, sys.Stop(ctx))
+			},
+		)
+	})
+	t.Run("With Batch when remoting is disabled", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		// define the logger to use
+		logger := log.DiscardLogger
+		// generate the remoting port
+		nodePorts := dynaport.Get(1)
+		remotingPort := nodePorts[0]
+		host := "0.0.0.0"
+
+		// create the actor system
+		sys, err := NewActorSystem(
+			"test",
+			WithLogger(logger),
+			WithPassivationDisabled(),
+			WithRemoting(host, int32(remotingPort)),
+		)
+		// assert there are no error
+		require.NoError(t, err)
+
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
+
+		lib.Pause(time.Second)
+
+		// create a test actor
+		actorName := "test"
+		actor := newMockActor()
+		actorRef, err := sys.Spawn(ctx, actorName, actor)
+		require.NoError(t, err)
+		assert.NotNil(t, actorRef)
+
+		remoting := NewRemoting()
+
+		addr, err := remoting.RemoteLookup(ctx, sys.Host(), int(sys.Port()), actorName)
+		require.NoError(t, err)
+
+		// let us disable remoting
+		actorsSystem := sys.(*actorSystem)
+		actorsSystem.remotingEnabled.Store(false)
+
+		from := address.NoSender()
+		// create a message to send to the test actor
+		message := new(testpb.TestReply)
+		// send the message to the actor
+		reply, err := remoting.RemoteBatchAsk(ctx, from, addr, []proto.Message{message}, time.Minute)
+		// perform some assertions
+		require.Error(t, err)
+		require.EqualError(t, err, "failed_precondition: remoting is not enabled")
+		require.Nil(t, reply)
+
+		// stop the actor after some time
+		lib.Pause(time.Second)
+		remoting.Close()
+		t.Cleanup(
+			func() {
+				assert.NoError(t, sys.Stop(ctx))
+			},
+		)
+	})
+	t.Run("With actor not found", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		// define the logger to use
+		logger := log.DiscardLogger
+		// generate the remoting port
+		nodePorts := dynaport.Get(1)
+		remotingPort := nodePorts[0]
+		host := "0.0.0.0"
+
+		// create the actor system
+		sys, err := NewActorSystem(
+			"test",
+			WithLogger(logger),
+			WithPassivationDisabled(),
+			WithJanitorInterval(30*time.Millisecond),
+			WithRemoting(host, int32(remotingPort)),
+		)
+		// assert there are no error
+		require.NoError(t, err)
+
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
+
+		lib.Pause(time.Second)
+
+		// create a test actor
+		actorName := "test"
+		actor := newMockActor()
+		actorRef, err := sys.Spawn(ctx, actorName, actor)
+		require.NoError(t, err)
+		assert.NotNil(t, actorRef)
+
+		remoting := NewRemoting()
+		from := address.NoSender()
+		// get the address of the actor
+		addr, err := remoting.RemoteLookup(ctx, sys.Host(), int(sys.Port()), actorName)
+		require.NoError(t, err)
+
+		// stop the actor when wait for cleanup to take place
+		require.NoError(t, actorRef.Shutdown(ctx))
+		lib.Pause(time.Second)
+
+		// create a message to send to the test actor
+		message := new(testpb.TestReply)
+		// send the message to the actor
+		reply, err := remoting.RemoteAsk(ctx, from, addr, message, time.Minute)
+		// perform some assertions
+		require.Error(t, err)
+		require.Contains(t, err.Error(), "not found")
+		require.Nil(t, reply)
+
+		// stop the actor after some time
+		lib.Pause(time.Second)
+
+		remoting.Close()
+		err = sys.Stop(ctx)
+		assert.NoError(t, err)
+	})
+	t.Run("With Batch actor not found", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		// define the logger to use
+		logger := log.DiscardLogger
+		// generate the remoting port
+		nodePorts := dynaport.Get(1)
+		remotingPort := nodePorts[0]
+		host := "0.0.0.0"
+
+		// create the actor system
+		sys, err := NewActorSystem(
+			"test",
+			WithLogger(logger),
+			WithPassivationDisabled(),
+			WithJanitorInterval(30*time.Millisecond),
+			WithRemoting(host, int32(remotingPort)),
+		)
+		// assert there are no error
+		require.NoError(t, err)
+
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
+
+		lib.Pause(time.Second)
+
+		// create a test actor
+		actorName := "test"
+		actor := newMockActor()
+		actorRef, err := sys.Spawn(ctx, actorName, actor)
+		require.NoError(t, err)
+		assert.NotNil(t, actorRef)
+
+		remoting := NewRemoting()
+		// get the address of the actor
+		addr, err := remoting.RemoteLookup(ctx, sys.Host(), int(sys.Port()), actorName)
+		require.NoError(t, err)
+
+		// stop the actor when wait for cleanup to take place
+		require.NoError(t, actorRef.Shutdown(ctx))
+		lib.Pause(time.Second)
+
+		from := address.NoSender()
+		// create a message to send to the test actor
+		message := new(testpb.TestReply)
+		// send the message to the actor
+		reply, err := remoting.RemoteBatchAsk(ctx, from, addr, []proto.Message{message}, time.Minute)
+		// perform some assertions
+		require.Error(t, err)
+		require.Contains(t, err.Error(), "not found")
+		require.Nil(t, reply)
+
+		// stop the actor after some time
+		lib.Pause(time.Second)
+
+		err = sys.Stop(ctx)
+		assert.NoError(t, err)
+	})
+	t.Run("With TLS enabled", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		rootCert := testutil.NewCertRoot(t)
+		serverConfig := testutil.GetServerTLSConfig(t, rootCert)
+		clientConfig := testutil.GetClientTLSConfig(t, rootCert)
+		// define the logger to use
+		logger := log.DiscardLogger
+		// generate the remoting port
+		nodePorts := dynaport.Get(1)
+		remotingPort := nodePorts[0]
+		host := "127.0.0.1"
+
+		// create the actor system
+		sys, err := NewActorSystem(
+			"test",
+			WithTLS(serverConfig, clientConfig),
+			WithLogger(logger),
+			WithPassivationDisabled(),
+			WithRemoting(host, int32(remotingPort)),
+		)
+		// assert there are no error
+		require.NoError(t, err)
+
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
+
+		lib.Pause(time.Second)
+
+		// create a test actor
+		actorName := "test"
+		actor := newMockActor()
+		actorRef, err := sys.Spawn(ctx, actorName, actor)
+		require.NoError(t, err)
+		assert.NotNil(t, actorRef)
+
+		remoting := NewRemoting(WithRemotingTLS(clientConfig))
+		from := address.NoSender()
+		// get the address of the actor
+		addr, err := remoting.RemoteLookup(ctx, sys.Host(), int(sys.Port()), actorName)
+		require.NoError(t, err)
+
+		// create a message to send to the test actor
+		message := new(testpb.TestReply)
+		// send the message to the actor
+		reply, err := remoting.RemoteAsk(ctx, from, addr, message, time.Minute)
+		// perform some assertions
+		require.NoError(t, err)
+		require.NotNil(t, reply)
+		require.True(t, reply.MessageIs(new(testpb.Reply)))
+
+		actual := new(testpb.Reply)
+		err = reply.UnmarshalTo(actual)
+		require.NoError(t, err)
+
+		expected := &testpb.Reply{Content: "received message"}
+		assert.True(t, proto.Equal(expected, actual))
+
+		// stop the actor after some time
+		lib.Pause(time.Second)
+
+		remoting.Close()
+		err = sys.Stop(ctx)
+		assert.NoError(t, err)
+	})
+}
+
+func TestRemotingLookup(t *testing.T) {
+	t.Run("When remoting is not enabled", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		// define the logger to use
+		logger := log.DiscardLogger
+		// generate the remoting port
+		nodePorts := dynaport.Get(1)
+		remotingPort := nodePorts[0]
+		host := "0.0.0.0"
+
+		// create the actor system
+		sys, err := NewActorSystem(
+			"test",
+			WithLogger(logger),
+			WithPassivationDisabled(),
+			WithRemoting(host, int32(remotingPort)),
+		)
+		// assert there are no error
+		require.NoError(t, err)
+
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
+
+		lib.Pause(time.Second)
+
+		// let us disable remoting
+		actorsSystem := sys.(*actorSystem)
+		actorsSystem.remotingEnabled.Store(false)
+		remoting := NewRemoting()
+		// create a test actor
+		actorName := "test"
+		// get the address of the actor
+		addr, err := remoting.RemoteLookup(ctx, sys.Host(), int(sys.Port()), actorName)
+		require.Error(t, err)
+		require.EqualError(t, err, "failed_precondition: remoting is not enabled")
+		require.Nil(t, addr)
+
+		t.Cleanup(
+			func() {
+				assert.NoError(t, sys.Stop(ctx))
+			},
+		)
+	})
+	t.Run("When TLS enabled", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		rootCert := testutil.NewCertRoot(t)
+		serverConfig := testutil.GetServerTLSConfig(t, rootCert)
+		clientConfig := testutil.GetClientTLSConfig(t, rootCert)
+
+		// define the logger to use
+		logger := log.DiscardLogger
+		// generate the remoting port
+		nodePorts := dynaport.Get(1)
+		remotingPort := nodePorts[0]
+		host := "127.0.0.1"
+
+		// create the actor system
+		sys, err := NewActorSystem(
+			"test",
+			WithLogger(logger),
+			WithPassivationDisabled(),
+			WithRemoting(host, int32(remotingPort)),
+			WithTLS(serverConfig, clientConfig),
+		)
+		// assert there are no error
+		require.NoError(t, err)
+
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
+
+		lib.Pause(time.Second)
+
+		// let us disable remoting
+		actorsSystem := sys.(*actorSystem)
+		actorsSystem.remotingEnabled.Store(false)
+		remoting := NewRemoting(WithRemotingTLS(clientConfig))
+		// create a test actor
+		actorName := "test"
+		// get the address of the actor
+		addr, err := remoting.RemoteLookup(ctx, sys.Host(), int(sys.Port()), actorName)
+		require.Error(t, err)
+		require.EqualError(t, err, "failed_precondition: remoting is not enabled")
+		require.Nil(t, addr)
+
+		t.Cleanup(
+			func() {
+				assert.NoError(t, sys.Stop(ctx))
+			},
+		)
+	})
+}
+
+func TestRemotingReSpawn(t *testing.T) {
+	t.Run("When remoting is not enabled", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		// define the logger to use
+		logger := log.DiscardLogger
+		// generate the remoting port
+		nodePorts := dynaport.Get(1)
+		remotingPort := nodePorts[0]
+		host := "0.0.0.0"
+
+		// create the actor system
+		sys, err := NewActorSystem(
+			"test",
+			WithLogger(logger),
+			WithPassivationDisabled(),
+			WithRemoting(host, int32(remotingPort)),
+		)
+		// assert there are no error
+		require.NoError(t, err)
+
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
+
+		lib.Pause(time.Second)
+
+		// let us disable remoting
+		actorsSystem := sys.(*actorSystem)
+		actorsSystem.remotingEnabled.Store(false)
+		remoting := NewRemoting()
+		// create a test actor
+		actorName := "test"
+		// get the address of the actor
+		err = remoting.RemoteReSpawn(ctx, sys.Host(), int(sys.Port()), actorName)
+		require.Error(t, err)
+		require.EqualError(t, err, "failed_precondition: remoting is not enabled")
+
+		remoting.Close()
+		t.Cleanup(
+			func() {
+				assert.NoError(t, sys.Stop(ctx))
+			},
+		)
+	})
+	t.Run("When remoting is enabled", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		// define the logger to use
+		logger := log.DiscardLogger
+		// generate the remoting port
+		nodePorts := dynaport.Get(1)
+		remotingPort := nodePorts[0]
+		host := "0.0.0.0"
+
+		// create the actor system
+		sys, err := NewActorSystem(
+			"test",
+			WithLogger(logger),
+			WithPassivationDisabled(),
+			WithRemoting(host, int32(remotingPort)),
+		)
+		// assert there are no error
+		require.NoError(t, err)
+
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
+
+		lib.Pause(time.Second)
+
+		// create a test actor
+		actorName := "test"
+		actor := newMockActor()
+		actorRef, err := sys.Spawn(ctx, actorName, actor)
+		require.NoError(t, err)
+		assert.NotNil(t, actorRef)
+
+		// assert the actor restart count
+		pid := actorRef
+		assert.Zero(t, pid.restartCount.Load())
+		remoting := NewRemoting()
+		// get the address of the actor
+		err = remoting.RemoteReSpawn(ctx, sys.Host(), int(sys.Port()), actorName)
+		require.NoError(t, err)
+
+		assert.EqualValues(t, 1, pid.restartCount.Load())
+
+		// stop the actor after some time
+		lib.Pause(time.Second)
+
+		remoting.Close()
+		err = sys.Stop(ctx)
+		assert.NoError(t, err)
+	})
+	t.Run("When TLS enabled", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		rootCert := testutil.NewCertRoot(t)
+		serverConfig := testutil.GetServerTLSConfig(t, rootCert)
+		clientConfig := testutil.GetClientTLSConfig(t, rootCert)
+		// define the logger to use
+		logger := log.DiscardLogger
+		// generate the remoting port
+		nodePorts := dynaport.Get(1)
+		remotingPort := nodePorts[0]
+		host := "127.0.0.1"
+
+		// create the actor system
+		sys, err := NewActorSystem(
+			"test",
+			WithLogger(logger),
+			WithPassivationDisabled(),
+			WithRemoting(host, int32(remotingPort)),
+			WithTLS(serverConfig, clientConfig),
+		)
+		// assert there are no error
+		require.NoError(t, err)
+
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
 
-			from := address.NoSender()
-			// create a message to send to the test actor
-			message := new(testpb.TestReply)
-			// send the message to the actor
-			replies, err := remoting.RemoteBatchAsk(ctx, from, addr, []proto.Message{message}, time.Minute)
-			// perform some assertions
-			require.NoError(t, err)
-			require.Len(t, replies, 1)
-			require.NotNil(t, replies[0])
-			require.True(t, replies[0].MessageIs(new(testpb.Reply)))
+		lib.Pause(time.Second)
 
-			actual := new(testpb.Reply)
-			err = replies[0].UnmarshalTo(actual)
-			require.NoError(t, err)
+		// let us disable remoting
+		actorsSystem := sys.(*actorSystem)
+		actorsSystem.remotingEnabled.Store(false)
+		remoting := NewRemoting(WithRemotingTLS(clientConfig))
+		// create a test actor
+		actorName := "test"
+		// get the address of the actor
+		err = remoting.RemoteReSpawn(ctx, sys.Host(), int(sys.Port()), actorName)
+		require.Error(t, err)
+		require.EqualError(t, err, "failed_precondition: remoting is not enabled")
 
-			expected := &testpb.Reply{Content: "received message"}
-			assert.True(t, proto.Equal(expected, actual))
-
-			// stop the actor after some time
-			lib.Pause(time.Second)
-
-			remoting.Close()
-			err = sys.Stop(ctx)
-			assert.NoError(t, err)
-		},
-	)
-	t.Run(
-		"With Batch service failure", func(t *testing.T) {
-			// create the context
-			ctx := context.TODO()
-			// define the logger to use
-			logger := log.DiscardLogger
-			// generate the remoting port
-			nodePorts := dynaport.Get(1)
-			remotingPort := nodePorts[0]
-			host := "0.0.0.0"
-
-			// create the actor system
-			sys, err := NewActorSystem(
-				"test",
-				WithLogger(logger),
-				WithPassivationDisabled(),
-				WithRemoting(host, int32(remotingPort)),
-			)
-			// assert there are no error
-			require.NoError(t, err)
+		remoting.Close()
+		t.Cleanup(
+			func() {
+				assert.NoError(t, sys.Stop(ctx))
+			},
+		)
+	})
+}
 
-			// start the actor system
-			err = sys.Start(ctx)
-			assert.NoError(t, err)
+func TestRemotingStop(t *testing.T) {
+	t.Run("When remoting is not enabled", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		// define the logger to use
+		logger := log.DiscardLogger
+		// generate the remoting port
+		nodePorts := dynaport.Get(1)
+		remotingPort := nodePorts[0]
+		host := "0.0.0.0"
 
-			lib.Pause(time.Second)
+		// create the actor system
+		sys, err := NewActorSystem(
+			"test",
+			WithLogger(logger),
+			WithPassivationDisabled(),
+			WithRemoting(host, int32(remotingPort)),
+		)
+		// assert there are no error
+		require.NoError(t, err)
 
-			// create a test actor
-			actorName := "test"
-			actor := newMockActor()
-			actorRef, err := sys.Spawn(ctx, actorName, actor)
-			require.NoError(t, err)
-			assert.NotNil(t, actorRef)
-
-			// get the address of the actor
-			addr := &goaktpb.Address{
-				Host: host,
-				Port: 2222,
-				Name: "",
-				Id:   "",
-			}
-
-			remoting := NewRemoting()
-			from := address.NoSender()
-			// create a message to send to the test actor
-			message := new(testpb.TestReply)
-			// send the message to the actor
-			reply, err := remoting.RemoteBatchAsk(ctx, from, address.From(addr), []proto.Message{message}, time.Minute)
-			// perform some assertions
-			require.Error(t, err)
-			require.Nil(t, reply)
-
-			remoting.Close()
-			// stop the actor after some time
-			lib.Pause(time.Second)
-			t.Cleanup(
-				func() {
-					assert.NoError(t, sys.Stop(ctx))
-				},
-			)
-		},
-	)
-	t.Run(
-		"With Batch when remoting is disabled", func(t *testing.T) {
-			// create the context
-			ctx := context.TODO()
-			// define the logger to use
-			logger := log.DiscardLogger
-			// generate the remoting port
-			nodePorts := dynaport.Get(1)
-			remotingPort := nodePorts[0]
-			host := "0.0.0.0"
-
-			// create the actor system
-			sys, err := NewActorSystem(
-				"test",
-				WithLogger(logger),
-				WithPassivationDisabled(),
-				WithRemoting(host, int32(remotingPort)),
-			)
-			// assert there are no error
-			require.NoError(t, err)
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
 
-			// start the actor system
-			err = sys.Start(ctx)
-			assert.NoError(t, err)
+		lib.Pause(time.Second)
 
-			lib.Pause(time.Second)
+		// let us disable remoting
+		actorsSystem := sys.(*actorSystem)
+		actorsSystem.remotingEnabled.Store(false)
 
-			// create a test actor
-			actorName := "test"
-			actor := newMockActor()
-			actorRef, err := sys.Spawn(ctx, actorName, actor)
-			require.NoError(t, err)
-			assert.NotNil(t, actorRef)
+		remoting := NewRemoting()
+		// create a test actor
+		actorName := "test"
+		// get the address of the actor
+		err = remoting.RemoteStop(ctx, sys.Host(), int(sys.Port()), actorName)
+		require.Error(t, err)
+		require.EqualError(t, err, "failed_precondition: remoting is not enabled")
 
-			remoting := NewRemoting()
+		remoting.Close()
+		t.Cleanup(
+			func() {
+				assert.NoError(t, sys.Stop(ctx))
+			},
+		)
+	})
+	t.Run("When remoting is enabled", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		// define the logger to use
+		logger := log.DiscardLogger
+		// generate the remoting port
+		nodePorts := dynaport.Get(1)
+		remotingPort := nodePorts[0]
+		host := "0.0.0.0"
 
-			addr, err := remoting.RemoteLookup(ctx, sys.Host(), int(sys.Port()), actorName)
-			require.NoError(t, err)
+		// create the actor system
+		sys, err := NewActorSystem(
+			"test",
+			WithLogger(logger),
+			WithPassivationDisabled(),
+			WithRemoting(host, int32(remotingPort)),
+		)
+		// assert there are no error
+		require.NoError(t, err)
 
-			// let us disable remoting
-			actorsSystem := sys.(*actorSystem)
-			actorsSystem.remotingEnabled.Store(false)
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
 
-			from := address.NoSender()
-			// create a message to send to the test actor
-			message := new(testpb.TestReply)
-			// send the message to the actor
-			reply, err := remoting.RemoteBatchAsk(ctx, from, addr, []proto.Message{message}, time.Minute)
-			// perform some assertions
-			require.Error(t, err)
-			require.EqualError(t, err, "failed_precondition: remoting is not enabled")
-			require.Nil(t, reply)
-
-			// stop the actor after some time
-			lib.Pause(time.Second)
-			remoting.Close()
-			t.Cleanup(
-				func() {
-					assert.NoError(t, sys.Stop(ctx))
-				},
-			)
-		},
-	)
-	t.Run(
-		"With actor not found", func(t *testing.T) {
-			// create the context
-			ctx := context.TODO()
-			// define the logger to use
-			logger := log.DiscardLogger
-			// generate the remoting port
-			nodePorts := dynaport.Get(1)
-			remotingPort := nodePorts[0]
-			host := "0.0.0.0"
-
-			// create the actor system
-			sys, err := NewActorSystem(
-				"test",
-				WithLogger(logger),
-				WithPassivationDisabled(),
-				WithJanitorInterval(30*time.Millisecond),
-				WithRemoting(host, int32(remotingPort)),
-			)
-			// assert there are no error
-			require.NoError(t, err)
+		lib.Pause(time.Second)
 
-			// start the actor system
-			err = sys.Start(ctx)
-			assert.NoError(t, err)
+		// create a test actor
+		actorName := "test"
+		actor := newMockActor()
+		actorRef, err := sys.Spawn(ctx, actorName, actor)
+		require.NoError(t, err)
+		assert.NotNil(t, actorRef)
 
-			lib.Pause(time.Second)
+		// assert the actor restart count
+		pid := actorRef
+		assert.Zero(t, pid.restartCount.Load())
 
-			// create a test actor
-			actorName := "test"
-			actor := newMockActor()
-			actorRef, err := sys.Spawn(ctx, actorName, actor)
-			require.NoError(t, err)
-			assert.NotNil(t, actorRef)
+		remoting := NewRemoting()
 
-			remoting := NewRemoting()
-			from := address.NoSender()
-			// get the address of the actor
-			addr, err := remoting.RemoteLookup(ctx, sys.Host(), int(sys.Port()), actorName)
-			require.NoError(t, err)
+		// get the address of the actor
+		err = remoting.RemoteStop(ctx, sys.Host(), int(sys.Port()), actorName)
+		require.NoError(t, err)
 
-			// stop the actor when wait for cleanup to take place
-			require.NoError(t, actorRef.Shutdown(ctx))
-			lib.Pause(time.Second)
+		lib.Pause(time.Second)
 
-			// create a message to send to the test actor
-			message := new(testpb.TestReply)
-			// send the message to the actor
-			reply, err := remoting.RemoteAsk(ctx, from, addr, message, time.Minute)
-			// perform some assertions
-			require.Error(t, err)
-			require.Contains(t, err.Error(), "not found")
-			require.Nil(t, reply)
-
-			// stop the actor after some time
-			lib.Pause(time.Second)
-
-			remoting.Close()
-			err = sys.Stop(ctx)
-			assert.NoError(t, err)
-		},
-	)
-	t.Run(
-		"With Batch actor not found", func(t *testing.T) {
-			// create the context
-			ctx := context.TODO()
-			// define the logger to use
-			logger := log.DiscardLogger
-			// generate the remoting port
-			nodePorts := dynaport.Get(1)
-			remotingPort := nodePorts[0]
-			host := "0.0.0.0"
-
-			// create the actor system
-			sys, err := NewActorSystem(
-				"test",
-				WithLogger(logger),
-				WithPassivationDisabled(),
-				WithJanitorInterval(30*time.Millisecond),
-				WithRemoting(host, int32(remotingPort)),
-			)
-			// assert there are no error
-			require.NoError(t, err)
+		assert.Empty(t, sys.Actors())
 
-			// start the actor system
-			err = sys.Start(ctx)
-			assert.NoError(t, err)
+		// stop the actor after some time
+		lib.Pause(time.Second)
 
-			lib.Pause(time.Second)
+		remoting.Close()
+		err = sys.Stop(ctx)
+		assert.NoError(t, err)
+	})
+	t.Run("When TLS enabled", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
 
-			// create a test actor
-			actorName := "test"
-			actor := newMockActor()
-			actorRef, err := sys.Spawn(ctx, actorName, actor)
-			require.NoError(t, err)
-			assert.NotNil(t, actorRef)
+		rootCert := testutil.NewCertRoot(t)
+		serverConfig := testutil.GetServerTLSConfig(t, rootCert)
+		clientConfig := testutil.GetClientTLSConfig(t, rootCert)
 
-			remoting := NewRemoting()
-			// get the address of the actor
-			addr, err := remoting.RemoteLookup(ctx, sys.Host(), int(sys.Port()), actorName)
-			require.NoError(t, err)
+		// define the logger to use
+		logger := log.DiscardLogger
+		// generate the remoting port
+		nodePorts := dynaport.Get(1)
+		remotingPort := nodePorts[0]
+		host := "127.0.0.1"
 
-			// stop the actor when wait for cleanup to take place
-			require.NoError(t, actorRef.Shutdown(ctx))
-			lib.Pause(time.Second)
+		// create the actor system
+		sys, err := NewActorSystem(
+			"test",
+			WithLogger(logger),
+			WithPassivationDisabled(),
+			WithRemoting(host, int32(remotingPort)),
+			WithTLS(serverConfig, clientConfig),
+		)
+		// assert there are no error
+		require.NoError(t, err)
 
-			from := address.NoSender()
-			// create a message to send to the test actor
-			message := new(testpb.TestReply)
-			// send the message to the actor
-			reply, err := remoting.RemoteBatchAsk(ctx, from, addr, []proto.Message{message}, time.Minute)
-			// perform some assertions
-			require.Error(t, err)
-			require.Contains(t, err.Error(), "not found")
-			require.Nil(t, reply)
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
 
-			// stop the actor after some time
-			lib.Pause(time.Second)
+		lib.Pause(time.Second)
 
-			err = sys.Stop(ctx)
-			assert.NoError(t, err)
-		},
-	)
-}
+		// let us disable remoting
+		actorsSystem := sys.(*actorSystem)
+		actorsSystem.remotingEnabled.Store(false)
 
-func TestAPIRemoteLookup(t *testing.T) {
-	t.Run(
-		"When remoting is not enabled", func(t *testing.T) {
-			// create the context
-			ctx := context.TODO()
-			// define the logger to use
-			logger := log.DiscardLogger
-			// generate the remoting port
-			nodePorts := dynaport.Get(1)
-			remotingPort := nodePorts[0]
-			host := "0.0.0.0"
-
-			// create the actor system
-			sys, err := NewActorSystem(
-				"test",
-				WithLogger(logger),
-				WithPassivationDisabled(),
-				WithRemoting(host, int32(remotingPort)),
-			)
-			// assert there are no error
-			require.NoError(t, err)
+		remoting := NewRemoting(WithRemotingTLS(clientConfig))
+		// create a test actor
+		actorName := "test"
+		// get the address of the actor
+		err = remoting.RemoteStop(ctx, sys.Host(), int(sys.Port()), actorName)
+		require.Error(t, err)
+		require.EqualError(t, err, "failed_precondition: remoting is not enabled")
 
-			// start the actor system
-			err = sys.Start(ctx)
-			assert.NoError(t, err)
-
-			lib.Pause(time.Second)
-
-			// let us disable remoting
-			actorsSystem := sys.(*actorSystem)
-			actorsSystem.remotingEnabled.Store(false)
-			remoting := NewRemoting()
-			// create a test actor
-			actorName := "test"
-			// get the address of the actor
-			addr, err := remoting.RemoteLookup(ctx, sys.Host(), int(sys.Port()), actorName)
-			require.Error(t, err)
-			require.EqualError(t, err, "failed_precondition: remoting is not enabled")
-			require.Nil(t, addr)
-
-			t.Cleanup(
-				func() {
-					assert.NoError(t, sys.Stop(ctx))
-				},
-			)
-		},
-	)
+		remoting.Close()
+		t.Cleanup(
+			func() {
+				assert.NoError(t, sys.Stop(ctx))
+			},
+		)
+	})
 }
 
-func TestAPIRemoteReSpawn(t *testing.T) {
-	t.Run(
-		"When remoting is not enabled", func(t *testing.T) {
-			// create the context
-			ctx := context.TODO()
-			// define the logger to use
-			logger := log.DiscardLogger
-			// generate the remoting port
-			nodePorts := dynaport.Get(1)
-			remotingPort := nodePorts[0]
-			host := "0.0.0.0"
-
-			// create the actor system
-			sys, err := NewActorSystem(
-				"test",
-				WithLogger(logger),
-				WithPassivationDisabled(),
-				WithRemoting(host, int32(remotingPort)),
-			)
-			// assert there are no error
-			require.NoError(t, err)
+func TestRemotingSpawn(t *testing.T) {
+	t.Run("When remoting is enabled", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		// define the logger to use
+		logger := log.DiscardLogger
+		// generate the remoting port
+		ports := dynaport.Get(1)
+		remotingPort := ports[0]
+		host := "127.0.0.1"
 
-			// start the actor system
-			err = sys.Start(ctx)
-			assert.NoError(t, err)
-
-			lib.Pause(time.Second)
-
-			// let us disable remoting
-			actorsSystem := sys.(*actorSystem)
-			actorsSystem.remotingEnabled.Store(false)
-			remoting := NewRemoting()
-			// create a test actor
-			actorName := "test"
-			// get the address of the actor
-			err = remoting.RemoteReSpawn(ctx, sys.Host(), int(sys.Port()), actorName)
-			require.Error(t, err)
-			require.EqualError(t, err, "failed_precondition: remoting is not enabled")
-
-			remoting.Close()
-			t.Cleanup(
-				func() {
-					assert.NoError(t, sys.Stop(ctx))
-				},
-			)
-		},
-	)
-	t.Run(
-		"When remoting is enabled", func(t *testing.T) {
-			// create the context
-			ctx := context.TODO()
-			// define the logger to use
-			logger := log.DiscardLogger
-			// generate the remoting port
-			nodePorts := dynaport.Get(1)
-			remotingPort := nodePorts[0]
-			host := "0.0.0.0"
-
-			// create the actor system
-			sys, err := NewActorSystem(
-				"test",
-				WithLogger(logger),
-				WithPassivationDisabled(),
-				WithRemoting(host, int32(remotingPort)),
-			)
-			// assert there are no error
-			require.NoError(t, err)
+		// create the actor system
+		sys, err := NewActorSystem(
+			"test",
+			WithLogger(logger),
+			WithPassivationDisabled(),
+			WithRemoting(host, int32(remotingPort)),
+		)
+		// assert there are no error
+		require.NoError(t, err)
 
-			// start the actor system
-			err = sys.Start(ctx)
-			assert.NoError(t, err)
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
 
-			lib.Pause(time.Second)
+		// create an actor implementation and register it
+		actor := &exchanger{}
+		actorName := uuid.NewString()
 
-			// create a test actor
-			actorName := "test"
-			actor := newMockActor()
-			actorRef, err := sys.Spawn(ctx, actorName, actor)
-			require.NoError(t, err)
-			assert.NotNil(t, actorRef)
-
-			// assert the actor restart count
-			pid := actorRef
-			assert.Zero(t, pid.restartCount.Load())
-			remoting := NewRemoting()
-			// get the address of the actor
-			err = remoting.RemoteReSpawn(ctx, sys.Host(), int(sys.Port()), actorName)
-			require.NoError(t, err)
+		remoting := NewRemoting()
+		// fetching the address of the that actor should return nil address
+		addr, err := remoting.RemoteLookup(ctx, sys.Host(), int(sys.Port()), actorName)
+		require.NoError(t, err)
+		require.Nil(t, addr)
 
-			assert.EqualValues(t, 1, pid.restartCount.Load())
+		// register the actor
+		err = sys.Register(ctx, actor)
+		require.NoError(t, err)
 
-			// stop the actor after some time
-			lib.Pause(time.Second)
+		// spawn the remote actor
+		err = remoting.RemoteSpawn(ctx, host, remotingPort, actorName, "actors.exchanger")
+		require.NoError(t, err)
 
-			remoting.Close()
-			err = sys.Stop(ctx)
-			assert.NoError(t, err)
-		},
-	)
-}
+		// re-fetching the address of the actor should return not nil address after start
+		addr, err = remoting.RemoteLookup(ctx, host, remotingPort, actorName)
+		require.NoError(t, err)
+		require.NotNil(t, addr)
 
-func TestAPIRemoteStop(t *testing.T) {
-	t.Run(
-		"When remoting is not enabled", func(t *testing.T) {
-			// create the context
-			ctx := context.TODO()
-			// define the logger to use
-			logger := log.DiscardLogger
-			// generate the remoting port
-			nodePorts := dynaport.Get(1)
-			remotingPort := nodePorts[0]
-			host := "0.0.0.0"
-
-			// create the actor system
-			sys, err := NewActorSystem(
-				"test",
-				WithLogger(logger),
-				WithPassivationDisabled(),
-				WithRemoting(host, int32(remotingPort)),
-			)
-			// assert there are no error
-			require.NoError(t, err)
+		from := address.NoSender()
+		// send the message to exchanger actor one using remote messaging
+		reply, err := remoting.RemoteAsk(ctx, from, addr, new(testpb.TestReply), time.Minute)
 
-			// start the actor system
-			err = sys.Start(ctx)
-			assert.NoError(t, err)
-
-			lib.Pause(time.Second)
-
-			// let us disable remoting
-			actorsSystem := sys.(*actorSystem)
-			actorsSystem.remotingEnabled.Store(false)
-
-			remoting := NewRemoting()
-			// create a test actor
-			actorName := "test"
-			// get the address of the actor
-			err = remoting.RemoteStop(ctx, sys.Host(), int(sys.Port()), actorName)
-			require.Error(t, err)
-			require.EqualError(t, err, "failed_precondition: remoting is not enabled")
-
-			remoting.Close()
-			t.Cleanup(
-				func() {
-					assert.NoError(t, sys.Stop(ctx))
-				},
-			)
-		},
-	)
-	t.Run(
-		"When remoting is enabled", func(t *testing.T) {
-			// create the context
-			ctx := context.TODO()
-			// define the logger to use
-			logger := log.DiscardLogger
-			// generate the remoting port
-			nodePorts := dynaport.Get(1)
-			remotingPort := nodePorts[0]
-			host := "0.0.0.0"
-
-			// create the actor system
-			sys, err := NewActorSystem(
-				"test",
-				WithLogger(logger),
-				WithPassivationDisabled(),
-				WithRemoting(host, int32(remotingPort)),
-			)
-			// assert there are no error
-			require.NoError(t, err)
+		require.NoError(t, err)
+		require.NotNil(t, reply)
+		require.True(t, reply.MessageIs(new(testpb.Reply)))
 
-			// start the actor system
-			err = sys.Start(ctx)
-			assert.NoError(t, err)
+		actual := new(testpb.Reply)
+		err = reply.UnmarshalTo(actual)
+		require.NoError(t, err)
 
-			lib.Pause(time.Second)
+		expected := new(testpb.Reply)
+		assert.True(t, proto.Equal(expected, actual))
 
-			// create a test actor
-			actorName := "test"
-			actor := newMockActor()
-			actorRef, err := sys.Spawn(ctx, actorName, actor)
-			require.NoError(t, err)
-			assert.NotNil(t, actorRef)
+		remoting.Close()
+		t.Cleanup(
+			func() {
+				err = sys.Stop(ctx)
+				assert.NoError(t, err)
+			},
+		)
+	})
+	t.Run("When actor not registered", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		// define the logger to use
+		logger := log.DiscardLogger
+		// generate the remoting port
+		ports := dynaport.Get(1)
+		remotingPort := ports[0]
+		host := "127.0.0.1"
 
-			// assert the actor restart count
-			pid := actorRef
-			assert.Zero(t, pid.restartCount.Load())
+		// create the actor system
+		sys, err := NewActorSystem(
+			"test",
+			WithLogger(logger),
+			WithPassivationDisabled(),
+			WithRemoting(host, int32(remotingPort)),
+		)
+		// assert there are no error
+		require.NoError(t, err)
 
-			remoting := NewRemoting()
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
 
-			// get the address of the actor
-			err = remoting.RemoteStop(ctx, sys.Host(), int(sys.Port()), actorName)
-			require.NoError(t, err)
+		// create an actor implementation and register it
+		actorName := uuid.NewString()
 
-			lib.Pause(time.Second)
+		remoting := NewRemoting()
+		// fetching the address of the that actor should return nil address
+		addr, err := remoting.RemoteLookup(ctx, sys.Host(), int(sys.Port()), actorName)
+		require.NoError(t, err)
+		require.Nil(t, addr)
 
-			assert.Empty(t, sys.Actors())
+		// spawn the remote actor
+		err = remoting.RemoteSpawn(ctx, sys.Host(), int(sys.Port()), actorName, "actors.exchanger")
+		require.Error(t, err)
+		assert.EqualError(t, err, ErrTypeNotRegistered.Error())
 
-			// stop the actor after some time
-			lib.Pause(time.Second)
+		t.Cleanup(
+			func() {
+				err = sys.Stop(ctx)
+				assert.NoError(t, err)
+			},
+		)
+	})
+	t.Run("When remoting is not enabled", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		// define the logger to use
+		logger := log.DiscardLogger
+		// generate the remoting port
+		ports := dynaport.Get(1)
+		remotingPort := ports[0]
+		host := "127.0.0.1"
 
-			remoting.Close()
-			err = sys.Stop(ctx)
-			assert.NoError(t, err)
-		},
-	)
-}
+		// create the actor system
+		sys, err := NewActorSystem(
+			"test",
+			WithLogger(logger),
+			WithPassivationDisabled(),
+		)
+		// assert there are no error
+		require.NoError(t, err)
 
-func TestAPIRemoteSpawn(t *testing.T) {
-	t.Run(
-		"When remoting is enabled", func(t *testing.T) {
-			// create the context
-			ctx := context.TODO()
-			// define the logger to use
-			logger := log.DiscardLogger
-			// generate the remoting port
-			ports := dynaport.Get(1)
-			remotingPort := ports[0]
-			host := "127.0.0.1"
-
-			// create the actor system
-			sys, err := NewActorSystem(
-				"test",
-				WithLogger(logger),
-				WithPassivationDisabled(),
-				WithRemoting(host, int32(remotingPort)),
-			)
-			// assert there are no error
-			require.NoError(t, err)
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
 
-			// start the actor system
-			err = sys.Start(ctx)
-			assert.NoError(t, err)
+		// create an actor implementation and register it
+		actorName := uuid.NewString()
+		remoting := NewRemoting()
+		// spawn the remote actor
+		err = remoting.RemoteSpawn(ctx, host, remotingPort, actorName, "actors.exchanger")
+		require.Error(t, err)
 
-			// create an actor implementation and register it
-			actor := &exchanger{}
-			actorName := uuid.NewString()
+		t.Cleanup(
+			func() {
+				err = sys.Stop(ctx)
+				assert.NoError(t, err)
+			},
+		)
+	})
+	t.Run("When TLS enabbled", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		rootCert := testutil.NewCertRoot(t)
+		serverConfig := testutil.GetServerTLSConfig(t, rootCert)
+		clientConfig := testutil.GetClientTLSConfig(t, rootCert)
+		// define the logger to use
+		logger := log.DiscardLogger
+		// generate the remoting port
+		ports := dynaport.Get(1)
+		remotingPort := ports[0]
+		host := "127.0.0.1"
 
-			remoting := NewRemoting()
-			// fetching the address of the that actor should return nil address
-			addr, err := remoting.RemoteLookup(ctx, sys.Host(), int(sys.Port()), actorName)
-			require.NoError(t, err)
-			require.Nil(t, addr)
+		// create the actor system
+		sys, err := NewActorSystem(
+			"test",
+			WithLogger(logger),
+			WithPassivationDisabled(),
+			WithRemoting(host, int32(remotingPort)),
+			WithTLS(serverConfig, clientConfig),
+		)
+		// assert there are no error
+		require.NoError(t, err)
 
-			// register the actor
-			err = sys.Register(ctx, actor)
-			require.NoError(t, err)
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
 
-			// spawn the remote actor
-			err = remoting.RemoteSpawn(ctx, host, remotingPort, actorName, "actors.exchanger")
-			require.NoError(t, err)
+		// create an actor implementation and register it
+		actor := &exchanger{}
+		actorName := uuid.NewString()
 
-			// re-fetching the address of the actor should return not nil address after start
-			addr, err = remoting.RemoteLookup(ctx, host, remotingPort, actorName)
-			require.NoError(t, err)
-			require.NotNil(t, addr)
+		remoting := NewRemoting(WithRemotingTLS(clientConfig))
+		// fetching the address of the that actor should return nil address
+		addr, err := remoting.RemoteLookup(ctx, sys.Host(), int(sys.Port()), actorName)
+		require.NoError(t, err)
+		require.Nil(t, addr)
 
-			from := address.NoSender()
-			// send the message to exchanger actor one using remote messaging
-			reply, err := remoting.RemoteAsk(ctx, from, addr, new(testpb.TestReply), time.Minute)
+		// register the actor
+		err = sys.Register(ctx, actor)
+		require.NoError(t, err)
 
-			require.NoError(t, err)
-			require.NotNil(t, reply)
-			require.True(t, reply.MessageIs(new(testpb.Reply)))
+		// spawn the remote actor
+		err = remoting.RemoteSpawn(ctx, host, remotingPort, actorName, "actors.exchanger")
+		require.NoError(t, err)
 
-			actual := new(testpb.Reply)
-			err = reply.UnmarshalTo(actual)
-			require.NoError(t, err)
+		// re-fetching the address of the actor should return not nil address after start
+		addr, err = remoting.RemoteLookup(ctx, host, remotingPort, actorName)
+		require.NoError(t, err)
+		require.NotNil(t, addr)
 
-			expected := new(testpb.Reply)
-			assert.True(t, proto.Equal(expected, actual))
-
-			remoting.Close()
-			t.Cleanup(
-				func() {
-					err = sys.Stop(ctx)
-					assert.NoError(t, err)
-				},
-			)
-		},
-	)
-
-	t.Run(
-		"When actor not registered", func(t *testing.T) {
-			// create the context
-			ctx := context.TODO()
-			// define the logger to use
-			logger := log.DiscardLogger
-			// generate the remoting port
-			ports := dynaport.Get(1)
-			remotingPort := ports[0]
-			host := "127.0.0.1"
-
-			// create the actor system
-			sys, err := NewActorSystem(
-				"test",
-				WithLogger(logger),
-				WithPassivationDisabled(),
-				WithRemoting(host, int32(remotingPort)),
-			)
-			// assert there are no error
-			require.NoError(t, err)
+		from := address.NoSender()
+		// send the message to exchanger actor one using remote messaging
+		reply, err := remoting.RemoteAsk(ctx, from, addr, new(testpb.TestReply), time.Minute)
 
-			// start the actor system
-			err = sys.Start(ctx)
-			assert.NoError(t, err)
+		require.NoError(t, err)
+		require.NotNil(t, reply)
+		require.True(t, reply.MessageIs(new(testpb.Reply)))
 
-			// create an actor implementation and register it
-			actorName := uuid.NewString()
+		actual := new(testpb.Reply)
+		err = reply.UnmarshalTo(actual)
+		require.NoError(t, err)
 
-			remoting := NewRemoting()
-			// fetching the address of the that actor should return nil address
-			addr, err := remoting.RemoteLookup(ctx, sys.Host(), int(sys.Port()), actorName)
-			require.NoError(t, err)
-			require.Nil(t, addr)
-
-			// spawn the remote actor
-			err = remoting.RemoteSpawn(ctx, sys.Host(), int(sys.Port()), actorName, "actors.exchanger")
-			require.Error(t, err)
-			assert.EqualError(t, err, ErrTypeNotRegistered.Error())
-
-			t.Cleanup(
-				func() {
-					err = sys.Stop(ctx)
-					assert.NoError(t, err)
-				},
-			)
-		},
-	)
-
-	t.Run(
-		"When remoting is not enabled", func(t *testing.T) {
-			// create the context
-			ctx := context.TODO()
-			// define the logger to use
-			logger := log.DiscardLogger
-			// generate the remoting port
-			ports := dynaport.Get(1)
-			remotingPort := ports[0]
-			host := "127.0.0.1"
-
-			// create the actor system
-			sys, err := NewActorSystem(
-				"test",
-				WithLogger(logger),
-				WithPassivationDisabled(),
-			)
-			// assert there are no error
-			require.NoError(t, err)
+		expected := new(testpb.Reply)
+		assert.True(t, proto.Equal(expected, actual))
 
-			// start the actor system
-			err = sys.Start(ctx)
-			assert.NoError(t, err)
-
-			// create an actor implementation and register it
-			actorName := uuid.NewString()
-			remoting := NewRemoting()
-			// spawn the remote actor
-			err = remoting.RemoteSpawn(ctx, host, remotingPort, actorName, "actors.exchanger")
-			require.Error(t, err)
-
-			t.Cleanup(
-				func() {
-					err = sys.Stop(ctx)
-					assert.NoError(t, err)
-				},
-			)
-		},
-	)
+		remoting.Close()
+		t.Cleanup(
+			func() {
+				err = sys.Stop(ctx)
+				assert.NoError(t, err)
+			},
+		)
+	})
 }
diff --git a/client/node_test.go b/client/node_test.go
new file mode 100644
index 0000000..bf0379a
--- /dev/null
+++ b/client/node_test.go
@@ -0,0 +1,57 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2022-2025  Arsene Tochemey Gandote
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package client
+
+import (
+	"fmt"
+	"net"
+	"strconv"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	"github.com/travisjeffery/go-dynaport"
+
+	"github.com/tochemey/goakt/v2/internal/testutil"
+)
+
+func TestNode(t *testing.T) {
+	ports := dynaport.Get(1)
+	address := net.JoinHostPort("127.0.0.1", strconv.Itoa(ports[0]))
+	rootCert := testutil.NewCertRoot(t)
+	clientConfig := testutil.GetClientTLSConfig(t, rootCert)
+
+	node := NewNode(address, WithWeight(10), WithTLS(clientConfig))
+	require.NotNil(t, node)
+	require.Equal(t, address, node.Address())
+	require.Exactly(t, float64(10), node.Weight())
+	require.NoError(t, node.Validate())
+	require.NotNil(t, node.HTTPClient())
+	require.Equal(t, fmt.Sprintf("https://%s", address), node.HTTPEndPoint())
+	require.NotNil(t, node.Remoting())
+	host, port := node.HostAndPort()
+	require.Equal(t, "127.0.0.1", host)
+	require.Equal(t, ports[0], port)
+	node.Free()
+}
diff --git a/internal/cluster/discovery_test.go b/internal/cluster/discovery_test.go
index e4540db..838736f 100644
--- a/internal/cluster/discovery_test.go
+++ b/internal/cluster/discovery_test.go
@@ -25,10 +25,10 @@
 package cluster
 
 import (
+	"errors"
 	"fmt"
 	"testing"
 
-	"github.com/pkg/errors"
 	"github.com/stretchr/testify/assert"
 	"github.com/travisjeffery/go-dynaport"
 
diff --git a/internal/cluster/engine_test.go b/internal/cluster/engine_test.go
index 0f56c66..215d37f 100644
--- a/internal/cluster/engine_test.go
+++ b/internal/cluster/engine_test.go
@@ -44,6 +44,7 @@ import (
 	"github.com/tochemey/goakt/v2/goaktpb"
 	"github.com/tochemey/goakt/v2/internal/internalpb"
 	"github.com/tochemey/goakt/v2/internal/lib"
+	"github.com/tochemey/goakt/v2/internal/testutil"
 	"github.com/tochemey/goakt/v2/log"
 	testkit "github.com/tochemey/goakt/v2/mocks/discovery"
 )
@@ -315,157 +316,314 @@ func TestSingleNode(t *testing.T) {
 }
 
 func TestMultipleNodes(t *testing.T) {
-	ctx := context.TODO()
+	t.Run("Without TLS", func(t *testing.T) {
+		ctx := context.TODO()
+
+		// start the NATS server
+		srv := startNatsServer(t)
+
+		// create a cluster node1
+		node1, sd1 := startEngine(t, "node1", srv.Addr().String())
+		require.NotNil(t, node1)
+
+		// wait for the node to start properly
+		lib.Pause(2 * time.Second)
+
+		// create a cluster node2
+		node2, sd2 := startEngine(t, "node2", srv.Addr().String())
+		require.NotNil(t, node2)
+		node2Addr := node2.node.PeersAddress()
+
+		// wait for the node to start properly
+		lib.Pause(time.Second)
+
+		// create a cluster node3
+		node3, sd3 := startEngine(t, "node3", srv.Addr().String())
+		require.NotNil(t, node3)
+		require.NotNil(t, sd3)
 
-	// start the NATS server
-	srv := startNatsServer(t)
+		// wait for the node to start properly
+		lib.Pause(time.Second)
 
-	// create a cluster node1
-	node1, sd1 := startEngine(t, "node1", srv.Addr().String())
-	require.NotNil(t, node1)
+		// assert the node joined cluster event
+		var events []*Event
+
+		// define an events reader loop and read events for some time
+	L:
+		for {
+			select {
+			case event, ok := <-node1.Events():
+				if ok {
+					events = append(events, event)
+				}
+			case <-time.After(time.Second):
+				break L
+			}
+		}
 
-	// wait for the node to start properly
-	lib.Pause(2 * time.Second)
+		require.NotEmpty(t, events)
+		require.Len(t, events, 2)
+		event := events[0]
+		msg, err := event.Payload.UnmarshalNew()
+		require.NoError(t, err)
+		nodeJoined, ok := msg.(*goaktpb.NodeJoined)
+		require.True(t, ok)
+		require.NotNil(t, nodeJoined)
+		require.Equal(t, node2Addr, nodeJoined.GetAddress())
+		peers, err := node1.Peers(ctx)
+		require.NoError(t, err)
+		require.Len(t, peers, 2)
+		require.Equal(t, node2Addr, net.JoinHostPort(peers[0].Host, strconv.Itoa(peers[0].PeersPort)))
 
-	// create a cluster node2
-	node2, sd2 := startEngine(t, "node2", srv.Addr().String())
-	require.NotNil(t, node2)
-	node2Addr := node2.node.PeersAddress()
+		// wait for some time
+		lib.Pause(time.Second)
 
-	// wait for the node to start properly
-	lib.Pause(time.Second)
+		// create some actors
+		actorName := uuid.NewString()
+		actor := &internalpb.ActorRef{
+			ActorAddress: &goaktpb.Address{
+				Name: actorName,
+			},
+			ActorType: "actorKind",
+		}
 
-	// create a cluster node3
-	node3, sd3 := startEngine(t, "node3", srv.Addr().String())
-	require.NotNil(t, node3)
-	require.NotNil(t, sd3)
+		// put an actor
+		err = node2.PutActor(ctx, actor)
+		require.NoError(t, err)
 
-	// wait for the node to start properly
-	lib.Pause(time.Second)
+		// wait for some time
+		lib.Pause(time.Second)
+
+		// get the actor from node1 and node3
+		actual, err := node1.GetActor(ctx, actorName)
+		require.NoError(t, err)
+		require.NotNil(t, actual)
+		require.True(t, proto.Equal(actor, actual))
+
+		actual, err = node3.GetActor(ctx, actorName)
+		require.NoError(t, err)
+		require.NotNil(t, actual)
+		require.True(t, proto.Equal(actor, actual))
+
+		// put another actor
+		actorName2 := uuid.NewString()
+		actor2 := &internalpb.ActorRef{
+			ActorAddress: &goaktpb.Address{
+				Name: actorName2,
+			},
+			ActorType: "actorKind",
+		}
+		err = node1.PutActor(ctx, actor2)
+		require.NoError(t, err)
+
+		// wait for some time
+		lib.Pause(time.Second)
+
+		actors, err := node1.Actors(ctx, time.Second)
+		require.NoError(t, err)
+		require.Len(t, actors, 2)
+
+		actors, err = node3.Actors(ctx, time.Second)
+		require.NoError(t, err)
+		require.Len(t, actors, 2)
+
+		actors, err = node2.Actors(ctx, time.Second)
+		require.NoError(t, err)
+		require.Len(t, actors, 2)
 
-	// assert the node joined cluster event
-	var events []*Event
+		// stop the second node
+		require.NoError(t, node2.Stop(ctx))
+		// wait for the event to propagate properly
+		lib.Pause(time.Second)
 
-	// define an events reader loop and read events for some time
-L:
-	for {
-		select {
-		case event, ok := <-node1.Events():
-			if ok {
-				events = append(events, event)
+		// reset the slice
+		events = []*Event{}
+
+		// define an events reader loop and read events for some time
+	L2:
+		for {
+			select {
+			case event, ok := <-node1.Events():
+				if ok {
+					events = append(events, event)
+				}
+			case <-time.After(time.Second):
+				break L2
 			}
-		case <-time.After(time.Second):
-			break L
 		}
-	}
 
-	require.NotEmpty(t, events)
-	require.Len(t, events, 2)
-	event := events[0]
-	msg, err := event.Payload.UnmarshalNew()
-	require.NoError(t, err)
-	nodeJoined, ok := msg.(*goaktpb.NodeJoined)
-	require.True(t, ok)
-	require.NotNil(t, nodeJoined)
-	require.Equal(t, node2Addr, nodeJoined.GetAddress())
-	peers, err := node1.Peers(ctx)
-	require.NoError(t, err)
-	require.Len(t, peers, 2)
-	require.Equal(t, node2Addr, net.JoinHostPort(peers[0].Host, strconv.Itoa(peers[0].PeersPort)))
-
-	// wait for some time
-	lib.Pause(time.Second)
-
-	// create some actors
-	actorName := uuid.NewString()
-	actor := &internalpb.ActorRef{
-		ActorAddress: &goaktpb.Address{
-			Name: actorName,
-		},
-		ActorType: "actorKind",
-	}
+		require.NotEmpty(t, events)
+		require.Len(t, events, 1)
+		event = events[0]
+		msg, err = event.Payload.UnmarshalNew()
+		require.NoError(t, err)
+		nodeLeft, ok := msg.(*goaktpb.NodeLeft)
+		require.True(t, ok)
+		require.NotNil(t, nodeLeft)
+		require.Equal(t, node2Addr, nodeLeft.GetAddress())
+
+		require.NoError(t, node1.Stop(ctx))
+		require.NoError(t, node3.Stop(ctx))
+		require.NoError(t, sd1.Close())
+		require.NoError(t, sd2.Close())
+		require.NoError(t, sd3.Close())
+		srv.Shutdown()
+	})
+	t.Run("With TLS", func(t *testing.T) {
+		ctx := context.TODO()
 
-	// put an actor
-	err = node2.PutActor(ctx, actor)
-	require.NoError(t, err)
+		rootCert := testutil.NewCertRoot(t)
 
-	// wait for some time
-	lib.Pause(time.Second)
+		// start the NATS server
+		srv := startNatsServer(t)
 
-	// get the actor from node1 and node3
-	actual, err := node1.GetActor(ctx, actorName)
-	require.NoError(t, err)
-	require.NotNil(t, actual)
-	require.True(t, proto.Equal(actor, actual))
+		// create a cluster node1
+		node1, sd1 := startEngineWithTLS(t, "node1", srv.Addr().String(), rootCert)
+		require.NotNil(t, node1)
 
-	actual, err = node3.GetActor(ctx, actorName)
-	require.NoError(t, err)
-	require.NotNil(t, actual)
-	require.True(t, proto.Equal(actor, actual))
-
-	// put another actor
-	actorName2 := uuid.NewString()
-	actor2 := &internalpb.ActorRef{
-		ActorAddress: &goaktpb.Address{
-			Name: actorName2,
-		},
-		ActorType: "actorKind",
-	}
-	err = node1.PutActor(ctx, actor2)
-	require.NoError(t, err)
+		// wait for the node to start properly
+		lib.Pause(2 * time.Second)
 
-	// wait for some time
-	lib.Pause(time.Second)
+		// create a cluster node2
+		node2, sd2 := startEngineWithTLS(t, "node2", srv.Addr().String(), rootCert)
+		require.NotNil(t, node2)
+		node2Addr := node2.node.PeersAddress()
 
-	actors, err := node1.Actors(ctx, time.Second)
-	require.NoError(t, err)
-	require.Len(t, actors, 2)
+		// wait for the node to start properly
+		lib.Pause(time.Second)
 
-	actors, err = node3.Actors(ctx, time.Second)
-	require.NoError(t, err)
-	require.Len(t, actors, 2)
+		// create a cluster node3
+		node3, sd3 := startEngineWithTLS(t, "node3", srv.Addr().String(), rootCert)
+		require.NotNil(t, node3)
+		require.NotNil(t, sd3)
 
-	actors, err = node2.Actors(ctx, time.Second)
-	require.NoError(t, err)
-	require.Len(t, actors, 2)
-
-	// stop the second node
-	require.NoError(t, node2.Stop(ctx))
-	// wait for the event to propagate properly
-	lib.Pause(time.Second)
-
-	// reset the slice
-	events = []*Event{}
-
-	// define an events reader loop and read events for some time
-L2:
-	for {
-		select {
-		case event, ok := <-node1.Events():
-			if ok {
-				events = append(events, event)
+		// wait for the node to start properly
+		lib.Pause(time.Second)
+
+		// assert the node joined cluster event
+		var events []*Event
+
+		// define an events reader loop and read events for some time
+	L:
+		for {
+			select {
+			case event, ok := <-node1.Events():
+				if ok {
+					events = append(events, event)
+				}
+			case <-time.After(time.Second):
+				break L
 			}
-		case <-time.After(time.Second):
-			break L2
 		}
-	}
 
-	require.NotEmpty(t, events)
-	require.Len(t, events, 1)
-	event = events[0]
-	msg, err = event.Payload.UnmarshalNew()
-	require.NoError(t, err)
-	nodeLeft, ok := msg.(*goaktpb.NodeLeft)
-	require.True(t, ok)
-	require.NotNil(t, nodeLeft)
-	require.Equal(t, node2Addr, nodeLeft.GetAddress())
-
-	require.NoError(t, node1.Stop(ctx))
-	require.NoError(t, node3.Stop(ctx))
-	require.NoError(t, sd1.Close())
-	require.NoError(t, sd2.Close())
-	require.NoError(t, sd3.Close())
-	srv.Shutdown()
+		require.NotEmpty(t, events)
+		require.Len(t, events, 2)
+		event := events[0]
+		msg, err := event.Payload.UnmarshalNew()
+		require.NoError(t, err)
+		nodeJoined, ok := msg.(*goaktpb.NodeJoined)
+		require.True(t, ok)
+		require.NotNil(t, nodeJoined)
+		require.Equal(t, node2Addr, nodeJoined.GetAddress())
+		peers, err := node1.Peers(ctx)
+		require.NoError(t, err)
+		require.Len(t, peers, 2)
+		require.Equal(t, node2Addr, net.JoinHostPort(peers[0].Host, strconv.Itoa(peers[0].PeersPort)))
+
+		// wait for some time
+		lib.Pause(time.Second)
+
+		// create some actors
+		actorName := uuid.NewString()
+		actor := &internalpb.ActorRef{
+			ActorAddress: &goaktpb.Address{
+				Name: actorName,
+			},
+			ActorType: "actorKind",
+		}
+
+		// put an actor
+		err = node2.PutActor(ctx, actor)
+		require.NoError(t, err)
+
+		// wait for some time
+		lib.Pause(time.Second)
+
+		// get the actor from node1 and node3
+		actual, err := node1.GetActor(ctx, actorName)
+		require.NoError(t, err)
+		require.NotNil(t, actual)
+		require.True(t, proto.Equal(actor, actual))
+
+		actual, err = node3.GetActor(ctx, actorName)
+		require.NoError(t, err)
+		require.NotNil(t, actual)
+		require.True(t, proto.Equal(actor, actual))
+
+		// put another actor
+		actorName2 := uuid.NewString()
+		actor2 := &internalpb.ActorRef{
+			ActorAddress: &goaktpb.Address{
+				Name: actorName2,
+			},
+			ActorType: "actorKind",
+		}
+		err = node1.PutActor(ctx, actor2)
+		require.NoError(t, err)
+
+		// wait for some time
+		lib.Pause(time.Second)
+
+		actors, err := node1.Actors(ctx, time.Second)
+		require.NoError(t, err)
+		require.Len(t, actors, 2)
+
+		actors, err = node3.Actors(ctx, time.Second)
+		require.NoError(t, err)
+		require.Len(t, actors, 2)
+
+		actors, err = node2.Actors(ctx, time.Second)
+		require.NoError(t, err)
+		require.Len(t, actors, 2)
+
+		// stop the second node
+		require.NoError(t, node2.Stop(ctx))
+		// wait for the event to propagate properly
+		lib.Pause(time.Second)
+
+		// reset the slice
+		events = []*Event{}
+
+		// define an events reader loop and read events for some time
+	L2:
+		for {
+			select {
+			case event, ok := <-node1.Events():
+				if ok {
+					events = append(events, event)
+				}
+			case <-time.After(time.Second):
+				break L2
+			}
+		}
+
+		require.NotEmpty(t, events)
+		require.Len(t, events, 1)
+		event = events[0]
+		msg, err = event.Payload.UnmarshalNew()
+		require.NoError(t, err)
+		nodeLeft, ok := msg.(*goaktpb.NodeLeft)
+		require.True(t, ok)
+		require.NotNil(t, nodeLeft)
+		require.Equal(t, node2Addr, nodeLeft.GetAddress())
+
+		require.NoError(t, node1.Stop(ctx))
+		require.NoError(t, node3.Stop(ctx))
+		require.NoError(t, sd1.Close())
+		require.NoError(t, sd2.Close())
+		require.NoError(t, sd3.Close())
+		srv.Shutdown()
+	})
 }
 
 func startNatsServer(t *testing.T) *natsserver.Server {
@@ -540,3 +698,59 @@ func startEngine(t *testing.T, nodeName, serverAddr string) (*Engine, discovery.
 	// return the cluster startNode
 	return engine, provider
 }
+
+func startEngineWithTLS(t *testing.T, nodeName, serverAddr string, rootCert *testutil.CertRoot) (*Engine, discovery.Provider) {
+	// create a context
+	ctx := context.TODO()
+
+	// generate the ports for the single startNode
+	nodePorts := dynaport.Get(3)
+	gossipPort := nodePorts[0]
+	clusterPort := nodePorts[1]
+	remotingPort := nodePorts[2]
+
+	// create a Cluster startNode
+	host := "127.0.0.1"
+	// create the various config option
+	applicationName := "accounts"
+	actorSystemName := "testSystem"
+	natsSubject := "some-subject"
+
+	// create the config
+	config := nats.Config{
+		ApplicationName: applicationName,
+		ActorSystemName: actorSystemName,
+		NatsServer:      fmt.Sprintf("nats://%s", serverAddr),
+		NatsSubject:     natsSubject,
+		Host:            host,
+		DiscoveryPort:   gossipPort,
+	}
+
+	hostNode := discovery.Node{
+		Name:          host,
+		Host:          host,
+		DiscoveryPort: gossipPort,
+		PeersPort:     clusterPort,
+		RemotingPort:  remotingPort,
+	}
+
+	// create the instance of provider
+	provider := nats.NewDiscovery(&config)
+
+	// create the server TLS config
+	serverTLSConfig := testutil.GetServerTLSConfig(t, rootCert)
+	clientTLSConfig := testutil.GetClientTLSConfig(t, rootCert)
+
+	// create the startNode
+	engine, err := NewEngine(nodeName, provider, &hostNode,
+		WithTLS(serverTLSConfig, clientTLSConfig),
+		WithLogger(log.DiscardLogger))
+	require.NoError(t, err)
+	require.NotNil(t, engine)
+
+	// start the node
+	require.NoError(t, engine.Start(ctx))
+
+	// return the cluster startNode
+	return engine, provider
+}
diff --git a/internal/cluster/option_test.go b/internal/cluster/option_test.go
index 2b71184..6c08ffa 100644
--- a/internal/cluster/option_test.go
+++ b/internal/cluster/option_test.go
@@ -25,6 +25,7 @@
 package cluster
 
 import (
+	"crypto/tls"
 	"testing"
 	"time"
 
@@ -36,6 +37,8 @@ import (
 
 func TestOptions(t *testing.T) {
 	mockHasher := new(testkit.Hasher)
+	// nolint
+	tlsConfig := &tls.Config{}
 	testCases := []struct {
 		name     string
 		option   Option
@@ -91,6 +94,11 @@ func TestOptions(t *testing.T) {
 			option:   WithReadQuorum(3),
 			expected: Engine{readQuorum: 3},
 		},
+		{
+			name:     "WithTLS",
+			option:   WithTLS(tlsConfig, tlsConfig),
+			expected: Engine{tlsServerConfig: tlsConfig, tlsClientConfig: tlsConfig},
+		},
 	}
 
 	for _, tc := range testCases {
diff --git a/internal/http/http_test.go b/internal/http/http_test.go
index 739005d..8545238 100644
--- a/internal/http/http_test.go
+++ b/internal/http/http_test.go
@@ -25,13 +25,11 @@
 package http
 
 import (
-	"context"
 	"net/http"
 	"testing"
 	"time"
 
 	"github.com/stretchr/testify/assert"
-	"github.com/travisjeffery/go-dynaport"
 	"golang.org/x/net/http2"
 )
 
@@ -45,21 +43,18 @@ func TestNewClient(t *testing.T) {
 	assert.Equal(t, 30*time.Second, tr.ReadIdleTimeout)
 }
 
-func TestNewServer(t *testing.T) {
+func TestURL(t *testing.T) {
 	host := "127.0.0.1"
-	port := dynaport.Get(1)[0]
-	mux := http.NewServeMux()
-	ctx := context.TODO()
+	port := 123
 
-	server := NewServer(ctx, host, port, mux)
-	assert.NotNil(t, server)
-	assert.IsType(t, new(http.Server), server)
+	url := URL(host, port)
+	assert.Equal(t, "http://127.0.0.1:123", url)
 }
 
-func TestURL(t *testing.T) {
+func TestURLs(t *testing.T) {
 	host := "127.0.0.1"
 	port := 123
 
-	url := URL(host, port)
-	assert.Equal(t, "http://127.0.0.1:123", url)
+	url := URLs(host, port)
+	assert.Equal(t, "https://127.0.0.1:123", url)
 }
diff --git a/internal/testutil/tls.go b/internal/testutil/tls.go
new file mode 100644
index 0000000..71f487d
--- /dev/null
+++ b/internal/testutil/tls.go
@@ -0,0 +1,281 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2022-2025  Arsene Tochemey Gandote
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package testutil
+
+import (
+	"crypto"
+	"crypto/rand"
+	"crypto/rsa"
+	"crypto/sha1" // nolint
+	"crypto/tls"
+	"crypto/x509"
+	"crypto/x509/pkix"
+	"encoding/asn1"
+	"encoding/pem"
+	"io"
+	"math/big"
+	"net"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/require"
+)
+
+// CertRoot defines the CA root
+// This include the CA key and CA certificate
+type CertRoot struct {
+	key  crypto.Signer
+	cert *x509.Certificate
+}
+
+// Key return the root certificate key
+func (x CertRoot) Key() crypto.Signer {
+	return x.key
+}
+
+// Cert returns the root certificate
+func (x CertRoot) Cert() *x509.Certificate {
+	return x.cert
+}
+
+// NewCertRoot creates an instance of CertRoot
+// useful for unit tests
+func NewCertRoot(t *testing.T) *CertRoot {
+	key, _ := generateKey(t)
+	cert, _ := generateRootCert(t, key)
+	return &CertRoot{
+		key:  key,
+		cert: cert,
+	}
+}
+
+// GetClientTLSConfig creates a client TLS configuration for unit tests
+func GetClientTLSConfig(t *testing.T, root *CertRoot) *tls.Config {
+	keyPEM, csrPEM := generateKeyAndCSR(t)
+	signCSR := signCSR(t, csrPEM, root.Key(), root.Cert())
+	certificate, err := tls.X509KeyPair(signCSR, keyPEM)
+	require.NoError(t, err)
+
+	certPool := x509.NewCertPool()
+	certPool.AddCert(root.Cert())
+
+	return &tls.Config{
+		Certificates: []tls.Certificate{certificate},
+		RootCAs:      certPool,
+		MinVersion:   tls.VersionTLS13,
+		CipherSuites: []uint16{
+			tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+			tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+		},
+		CurvePreferences: []tls.CurveID{
+			tls.CurveP521,
+			tls.CurveP384,
+			tls.CurveP256,
+		},
+	}
+}
+
+// GetServerTLSConfig creates a server TLS configuration for unit tests
+func GetServerTLSConfig(t *testing.T, root *CertRoot) *tls.Config {
+	keyPEM, csrPEM := generateKeyAndCSR(t)
+	signCSR := signCSR(t, csrPEM, root.Key(), root.Cert())
+	certificate, err := tls.X509KeyPair(signCSR, keyPEM)
+	require.NoError(t, err)
+
+	certPool := x509.NewCertPool()
+	certPool.AddCert(root.Cert())
+
+	return &tls.Config{
+		Certificates: []tls.Certificate{certificate},
+		ClientAuth:   tls.RequireAndVerifyClientCert,
+		ClientCAs:    certPool,
+		MinVersion:   tls.VersionTLS13,
+		CipherSuites: []uint16{
+			tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+			tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+		},
+		CurvePreferences: []tls.CurveID{
+			tls.CurveP521,
+			tls.CurveP384,
+			tls.CurveP256,
+		},
+	}
+}
+
+// generateRootCert generates a root certificate for unit tests
+func generateRootCert(t *testing.T, key crypto.Signer) (*x509.Certificate, []byte) {
+	subjectKeyIdentifier := calculateSubjectKeyIdentifier(t, key.Public())
+
+	template := &x509.Certificate{
+		SerialNumber:          generateSerial(t),
+		NotBefore:             time.Now(),
+		NotAfter:              time.Now().AddDate(10, 0, 0),
+		SubjectKeyId:          subjectKeyIdentifier,
+		AuthorityKeyId:        subjectKeyIdentifier,
+		KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
+		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
+		BasicConstraintsValid: true,
+		IsCA:                  true,
+		MaxPathLenZero:        true,
+	}
+
+	der, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	rootCert, err := x509.ParseCertificate(der)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	rootCertPEM := pem.EncodeToMemory(&pem.Block{
+		Type:  "CERTIFICATE",
+		Bytes: der,
+	})
+
+	return rootCert, rootCertPEM
+}
+
+// signCSR signs a certificate signing request with the given CA certificate and private key
+func signCSR(t *testing.T, csr []byte, caKey crypto.Signer, caCert *x509.Certificate) []byte {
+	block, _ := pem.Decode(csr)
+	if block == nil {
+		t.Fatal("failed to decode csr")
+	}
+
+	certificateRequest, err := x509.ParseCertificateRequest(block.Bytes)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if err := certificateRequest.CheckSignature(); err != nil {
+		t.Fatal(err)
+	}
+
+	template := x509.Certificate{
+		Subject:               certificateRequest.Subject,
+		PublicKeyAlgorithm:    certificateRequest.PublicKeyAlgorithm,
+		PublicKey:             certificateRequest.PublicKey,
+		SignatureAlgorithm:    certificateRequest.SignatureAlgorithm,
+		Signature:             certificateRequest.Signature,
+		SerialNumber:          generateSerial(t),
+		Issuer:                caCert.Issuer,
+		NotBefore:             time.Now(),
+		NotAfter:              time.Now().AddDate(10, 0, 0),
+		KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
+		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
+		SubjectKeyId:          calculateSubjectKeyIdentifier(t, certificateRequest.PublicKey),
+		BasicConstraintsValid: true,
+		IPAddresses:           certificateRequest.IPAddresses,
+	}
+
+	derBytes, err := x509.CreateCertificate(rand.Reader, &template, caCert, certificateRequest.PublicKey, caKey)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
+}
+
+// generateKey generates a 2048-bit RSA private key
+func generateKey(t *testing.T) (crypto.Signer, []byte) {
+	key, err := rsa.GenerateKey(rand.Reader, 2048)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	keyPEM := pem.EncodeToMemory(&pem.Block{
+		Type:  "RSA PRIVATE KEY",
+		Bytes: x509.MarshalPKCS1PrivateKey(key),
+	})
+
+	return key, keyPEM
+}
+
+func generateKeyAndCSR(t *testing.T) ([]byte, []byte) {
+	rsaKey, err := rsa.GenerateKey(rand.Reader, 2048)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	key := pem.EncodeToMemory(&pem.Block{
+		Type:  "RSA PRIVATE KEY",
+		Bytes: x509.MarshalPKCS1PrivateKey(rsaKey),
+	})
+
+	template := &x509.CertificateRequest{
+		SignatureAlgorithm: x509.SHA256WithRSA,
+		IPAddresses:        []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback},
+	}
+
+	req, err := x509.CreateCertificateRequest(rand.Reader, template, rsaKey)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	csr := pem.EncodeToMemory(&pem.Block{
+		Type:  "CERTIFICATE REQUEST",
+		Bytes: req,
+	})
+
+	return key, csr
+}
+
+// generateSerial generates a serial number using the maximum number of octets (20) allowed by RFC 5280 4.1.2.2
+// (Adapted from https://github.com/cloudflare/cfssl/blob/828c23c22cbca1f7632b9ba85174aaa26e745340/signer/local/local.go#L407-L418)
+func generateSerial(t *testing.T) *big.Int {
+	serialNumber := make([]byte, 20)
+	_, err := io.ReadFull(rand.Reader, serialNumber)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	return new(big.Int).SetBytes(serialNumber)
+}
+
+// calculateSubjectKeyIdentifier implements a common method to generate a key identifier
+// from a public key, namely, by composing it from the 160-bit SHA-1 hash of the bit string
+// of the public key (cf. https://tools.ietf.org/html/rfc5280#section-4.2.1.2).
+// (Adapted from https://github.com/jsha/minica/blob/master/main.go).
+func calculateSubjectKeyIdentifier(t *testing.T, pubKey crypto.PublicKey) []byte {
+	spkiASN1, err := x509.MarshalPKIXPublicKey(pubKey)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var spki struct {
+		Algorithm        pkix.AlgorithmIdentifier
+		SubjectPublicKey asn1.BitString
+	}
+	_, err = asn1.Unmarshal(spkiASN1, &spki)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// nolint
+	skid := sha1.Sum(spki.SubjectPublicKey.Bytes)
+	return skid[:]
+}