TLS everywhere
Este conteúdo não está disponível em sua língua ainda.
Production-secure means TLS on every network surface. Each component of the framework has its own TLS configuration; this page is the catalog of where to enable it and what cert material each needs.
Cluster transport
Section titled “Cluster transport”import { TcpTransport, Cluster, ClusterOptions } from 'actor-ts/cluster';
const transport = new TcpTransport(self, log, { cert: fs.readFileSync('./tls/cluster.crt'), key: fs.readFileSync('./tls/cluster.key'), ca: fs.readFileSync('./tls/ca.crt'), rejectUnauthorized: true,});
const clusterOptions = ClusterOptions.create() .withHost(host) .withPort(port) .withSeeds(seeds) .withTransport(transport);await Cluster.join(system, clusterOptions);Mutually-authenticated TLS between cluster nodes. See cluster security for the full discussion.
Every field here takes the certificate material, never a path to it — which is why the example reads the files itself. Nothing in the transport touches the filesystem, and none of Bun, Node or Deno accepts a filename in these fields.
cert and key are required together on a listener. A tls
object with only one of them used to make the listener conclude
“no TLS” and bind in plaintext, while still dialling out over
TLS — so the cluster formed and nothing looked wrong. That
combination is now refused at bind time, which also means a node
mis-configured this way stops silently downgrading and starts
failing loudly.
Don’t skip this — even on internal networks, defense in depth applies.
HTTP server
Section titled “HTTP server”import { HttpExtensionId } from 'actor-ts/http';
const http = system.extension(HttpExtensionId);
await http.newServerAt('0.0.0.0', 8443) .useBackend(new FastifyBackend({ https: { cert: fs.readFileSync('./tls/http.crt'), key: fs.readFileSync('./tls/http.key'), }, })) .bind(routes);For HTTPS at the application layer. Often terminated at the load balancer instead — in K8s, the Service/Ingress handles TLS, the app speaks plain HTTP internally. Pick by your infrastructure shape.
Management endpoint
Section titled “Management endpoint”import { FastifyBackend } from 'actor-ts/http';import { managementRoutes } from 'actor-ts/management';
const routes = managementRoutes(system, cluster);
// TLS is a property of the HTTP backend — Fastify's `https` option.const tlsBackend = new FastifyBackend({ https: { cert: fs.readFileSync('./tls/mgmt.crt'), key: fs.readFileSync('./tls/mgmt.key'), },});await system.http(8558, { backend: tlsBackend }).bind(routes);The management server is internal-only by default — but TLS still helps:
- Protects against lateral movement inside a compromised network.
- Required by some compliance regimes regardless of network topology.
Broker actors
Section titled “Broker actors”Each broker actor takes TLS in two separate steps, and confusing them is the usual reason a connection that says TLS turns out not to verify what you meant:
- Turning TLS on is the URL scheme —
amqps://,mqtts://,rediss://,wss://— orwithSsl(true)on Kafka. The driver then verifies the broker against the system trust store, which is all a publicly-trusted certificate needs. - Supplying certificate material is
withTls({ … }): a private CA to trust, or a client certificate for mTLS. It configures the handshake rather than starting one, so keep the TLS scheme as well.
As on the cluster transport, every field carries the material
itself, never a path to it — which is why each example reads the
file. cert and key only mean anything together; supplying one
without the other is refused when the actor starts, rather than
throwing inside the handshake and being retried forever as a
connection failure.
None of this is readable from HOCON, deliberately: a private key does
not belong in a config file. withTls is code-only on every broker,
and Kafka’s ssl config leaf stays boolean-only.
Kafka has no URL scheme to carry TLS, so ssl does both jobs —
true for the system trust store, or the material itself:
const kafkaOptions = KafkaOptions.create() .withBrokers(['kafka-1:9093']) .withSsl({ ca: fs.readFileSync('./tls/ca.crt', 'utf8'), cert: fs.readFileSync('./tls/client.crt', 'utf8'), key: fs.readFileSync('./tls/client.key', 'utf8'), }) .withSasl({ mechanism: 'scram-sha-512', username: process.env.KAFKA_USER!, password: process.env.KAFKA_PASS!, });new KafkaActor(kafkaOptions);const mqttOptions = MqttOptions.create() .withBrokerUrl('mqtts://mqtt.example.com:8883') .withCredentials(process.env.MQTT_USER, process.env.MQTT_PASS) .withTls({ ca: fs.readFileSync('./tls/ca.crt', 'utf8'), });new MqttActor(mqttOptions);mqtts:// starts the handshake; withTls decides what it trusts.
Drop withTls and verification falls back to the mqtt package’s
defaults, which is the system trust store.
const amqpOptions = AmqpOptions.create() .withUrl('amqps://rabbitmq.example.com:5671') .withTls({ ca: fs.readFileSync('./tls/ca.crt', 'utf8'), });new AmqpActor(amqpOptions);amqps:// plus withTls, which reaches amqplib as its
socketOptions argument. AMQP URL query parameters carry
heartbeat, frameMax, channelMax and locale — never
certificate material, so there is no way to express this in the URL.
const natsOptions = NatsOptions.create() .withServers(['nats://nats.example.com:4222']) .withTls({ ca: fs.readFileSync('./tls/ca.crt', 'utf8'), cert: fs.readFileSync('./tls/client.crt', 'utf8'), key: fs.readFileSync('./tls/client.key', 'utf8'), });new NatsActor(natsOptions);mTLS via withTls — common for production NATS. NATS has no tls
URL scheme, so here withTls is also what asks the driver to
negotiate TLS at all. JetStreamActor takes the identical option.
Redis Streams
Section titled “Redis Streams”const redisStreamsOptions = RedisStreamsOptions.create() .withUrl('rediss://redis.example.com:6380') .withTls({ ca: fs.readFileSync('./tls/ca.crt', 'utf8'), });new RedisStreamsActor(redisStreamsOptions);rediss:// URL scheme (note the double-s). Both the producer and
the consumer client are built from the same material.
TCP listener
Section titled “TCP listener”The one broker actor on the inbound side. It takes the same
material as the cluster transport, and supplying ca turns on
client-certificate verification unless requestClientCert: false
says otherwise:
const tcpServerOptions = TcpServerOptions.create() .withBindPort(9500) .withTarget(handler) .withTls({ cert: fs.readFileSync('./tls/server.crt', 'utf8'), key: fs.readFileSync('./tls/server.key', 'utf8'), ca: fs.readFileSync('./tls/ca.crt', 'utf8'), });new TcpServerActor(tcpServerOptions);const grpcClientOptions = GrpcClientOptions.create() .withEndpoint('orders.example.com:50051') .withCredentials({ kind: 'tls', rootCerts: fs.readFileSync('./tls/ca.crt'), });new GrpcClientActor(grpcClientOptions);For mutual TLS, add cert + key.
WebSocket
Section titled “WebSocket”const webSocketClientOptions = WebsocketClientOptions.create().withUrl('wss://realtime.example.com/feed');new WebsocketClientActor(webSocketClientOptions);wss:// URL scheme. Cert verification follows the runtime’s
TLS defaults.
Default trust store only
Section titled “Default trust store only”Two client actors still take no certificate material, so they can reach a broker only when its certificate chains to a publicly trusted root:
WebsocketClientActor(above).TcpSocketActor— the outbound TCP client. Its listener siblingTcpServerActordoes take material; the dialling half does not.
Behind a private CA, that means terminating TLS at a proxy holding a
public certificate, or adding the CA to the host’s trust store. It
does not mean NODE_TLS_REJECT_UNAUTHORIZED=0, which disables
verification for every TLS connection the process makes — the cluster
transport and every HTTP client included.
Persistence backends
Section titled “Persistence backends”SQLite
Section titled “SQLite”N/A — local-file access. TLS not applicable.Cassandra
Section titled “Cassandra”const cassandraJournalOptions = CassandraJournalOptions.create() .withContactPoints(['cass-1.example.com:9042']) .withClient(clientWithMtls);new CassandraJournal(cassandraJournalOptions); // mTLS cert material (cert / key / ca) is configured on the // cassandra-driver client passed via withClient()Cassandra clusters typically run with mTLS in production.
Object storage
Section titled “Object storage”const s3ObjectStorageOptions = S3ObjectStorageOptions.create().withRegion('eu-west-1');const objectStorageDurableStateStoreOptions = ObjectStorageDurableStateStoreOptions.create().withBackend(new S3ObjectStorageBackend(s3ObjectStorageOptions));new ObjectStorageDurableStateStore(objectStorageDurableStateStoreOptions); // S3 uses HTTPS by defaultCloud object storage (S3, GCS, Azure Blob) always uses TLS. No configuration needed beyond pointing at the endpoint.
Cert provisioning
Section titled “Cert provisioning”Three patterns in K8s production:
cert-manager
Section titled “cert-manager”apiVersion: cert-manager.io/v1kind: Certificatemetadata: name: actor-ts-clusterspec: secretName: actor-ts-cluster-tls issuerRef: name: actor-ts-ca kind: ClusterIssuer commonName: actor-ts dnsNames: [actor-ts-cluster.svc] duration: 8760h renewBefore: 720hcert-manager auto-renews certs before expiry. Most production K8s setups use it.
Vault Agent
Section titled “Vault Agent”Vault Agent runs as a sidecar; pulls certs into the pod’s filesystem; renews automatically. Useful when you already have HashiCorp Vault.
Manual + scheduled rotation
Section titled “Manual + scheduled rotation”For non-K8s environments, scheduled cron jobs that pull fresh certs from an internal CA + restart the affected services. Works but requires careful operational discipline.
What about CA chain?
Section titled “What about CA chain?”ca: fs.readFileSync('./tls/ca.crt'),Most internal setups: one CA, signs all client + server certs. The cert verification needs the CA cert at both ends.
Cloud-managed certificates (Let’s Encrypt, AWS ACM): use
public CA bundles — already present in most runtimes. No
explicit ca needed.
Where to next
Section titled “Where to next”- Cluster security — the cluster-transport detail.
- Master key rotation — for data at rest.
- Kubernetes deployment — the K8s recipe + cert-manager integration.
- Operations overview — the production-security checklist.
