You have probably already done the easy part. The browser shows a lock, the API gateway has a certificate, and the compliance checklist says TLS is enabled.
Then a request crosses a load balancer, lands on an internal service, gets forwarded once more, and reaches a database. Ask which of those hops carried plaintext and the room goes quiet.
That is the actual problem. Encryption in transit is not a switch you flip at the edge. It is a set of decisions about where trust changes hands, where sessions terminate, and which hops have to be re-encrypted so that one unexamined internal leg does not become the weak link.
Databases sit at the far end of that path, which makes them the last place anyone checks and the first place a gap actually matters. A graph workload compounds it: a single traversal can fan out into repeated round trips, replicas stream writes continuously between nodes, and cluster members gossip topology to each other on a separate channel entirely. Encrypting the client connection leaves most of that untouched. This guide walks the whole path, then shows the concrete configuration for a FalkorDB deployment.
Table of Contents
What “encrypted in transit” actually protects
Security teams separate data into three states: at rest on storage, in use in memory, and in motion across a network. Only the last one has to survive routers, proxies, shared infrastructure, and network segments the data owner does not directly control.
That distinction matters because the controls are not interchangeable. Disk encryption protects a stolen drive; it does nothing for a request being read off the wire. TLS protects a session; it does nothing once the payload is written to an unencrypted volume. Teams weighing where to spend effort across the two will find the comparison of at-rest and in-transit trade-offs at Garanord a reasonable framing of the differences, though the protocol details below come from the specifications themselves.
The University of California Berkeley encryption in transit guideline states the baseline plainly: covered traffic should stay protected across its entire path, because unencrypted traffic can be read by anyone with network access along the way.
A sealed package is the useful comparison. Sealing it at the front door accomplishes nothing if someone opens it in the hallway.
Practical rule: if a request crosses a trust boundary, treat that hop as needing its own protection unless you have deliberately designed it to stay encrypted.
The hop before the handshake: DNS and SNI
Most TLS guides start at the handshake. That skips a step that leaks in plaintext on nearly every deployment.
Before a client can open a TLS session it has to resolve a hostname. Classic DNS is unauthenticated UDP in the clear, so an observer learns which host you are about to contact before a single encrypted byte moves. Then the TLS ClientHello carries Server Name Indication, also in the clear, announcing the hostname again so that a server hosting many names can present the right certificate.
The payload stays private. The metadata does not. For internal service traffic that usually means an on-network observer can map your topology — which services talk to which, and how often — without decrypting anything.
Three mitigations are worth knowing:
-
DNS over HTTPS or DNS over TLS encrypts the resolution step itself, moving the query inside a protected channel rather than leaving it on the wire.
-
Encrypted Client Hello encrypts SNI, closing the second leak. Adoption is still uneven and depends on both client and server support.
-
Split-horizon internal DNS keeps internal names off public resolvers entirely, which is often the more practical control inside a VPC.
Encrypted DNS also runs into regional and regulatory friction, and deployments that span restrictive networks need to plan for resolvers being blocked or filtered rather than assume DoH will work everywhere. Throughwire’s writeup on encrypted DNS in restrictive network environments is a useful look at what happens when the resolution step itself is the thing being interfered with.
How TLS 1.3 establishes a session
TLS does two jobs in sequence: prove the endpoint is who it claims to be, then establish keys for a fast encrypted channel.
The version that matters is 1.3, defined in RFC 8446, and its handshake is shorter than the one most explainers describe:
-
ClientHello. The client sends supported cipher suites and a key share — a guess at the key exchange group the server will accept. This combination is what makes the handshake 1-RTT.
-
ServerHello. The server picks parameters, returns its own key share, and both sides derive the shared secret. Everything after this point is encrypted, including the server’s certificate.
-
Certificate and Finished. The server proves possession of its private key and confirms the transcript. The client verifies the chain and confirms in turn.
-
Application data. The channel is live.
The division of labour is the important part. Asymmetric cryptography handles identity and key agreement once, at setup. Symmetric cryptography carries the data stream, because paying public-key costs per packet would be untenable. This is also why connection churn hurts more than throughput: the expensive part is the handshake, not the bytes.
TLS 1.0 and 1.1 are formally deprecated by RFC 8996. Financial and regulated environments generally require 1.2 as an absolute floor, as in IBM Cloud’s encryption in transit requirements, and NIST SP 800-52 Rev. 2 treats TLS as something to configure and maintain deliberately rather than enable and forget.
| Version | Status | Handshake RTT | Recommendation |
|---|---|---|---|
| TLS 1.0 / 1.1 | Deprecated (RFC 8996) | 2 | Disable |
| TLS 1.2 | Acceptable floor | 2 (1 on resumption) | Allow only with ECDHE key exchange and AEAD suites |
| TLS 1.3 | Preferred | 1 (0 on resumption) | Require wherever you control both ends |
Two caveats on that table. TLS 1.2 is only acceptable when weak suites are removed — forward secrecy via ECDHE and authenticated encryption via AES-GCM or ChaCha20-Poly1305, nothing else. And TLS 1.3’s 0-RTT resumption mode trades a round trip for replay exposure, so it belongs on idempotent reads and nowhere near a write path.
The gap after termination
Here is the failure this whole article exists for.
A load balancer terminates the client’s TLS session so it can route, inspect, or apply policy. It then forwards the request to an internal service. If that forward is plaintext, the edge was protected and the middle was not. The external scan comes back clean. The dashboard is green. The data crossed shared infrastructure in the open.
The UK NCSC’s guidance on protecting sensitive data in transit points at decrypting as close to the final destination as possible, which reframes the whole thing: it is not an on/off setting, it is a question about where your trust boundaries actually sit.
Three questions resolve most cases:
-
Where does trust change hands? If a proxy, mesh sidecar, or gateway creates a new administrative boundary, terminate and re-encrypt there on purpose rather than by accident.
-
Who can observe the internal leg? Shared infrastructure, other tenants, or operations staff outside the data owner all count as observers. A private subnet is not a confidentiality control.
-
What breaks when this path fails? Re-encryption adds moving parts. It has to fit the failover design rather than fight it.
Databases make this concrete. Client connections are the hop everyone secures, but a clustered deployment carries three other conversations that are easy to forget: replication streams shipping every write to replicas, cluster bus traffic exchanging topology and health between nodes, and backup or snapshot transfers heading to object storage. Each is a separate channel with its own configuration, and each one carries the same data as the client connection you already encrypted.
Graph workloads add a fourth. Traversals frequently mean an application service issuing many small queries in sequence rather than one large fetch, so a plaintext leg between an internal service and the database is not a single exposed payload — it is a continuous stream of them, with the query text itself revealing the shape of the data model.
The breach pattern here is rarely someone defeating edge TLS. It is the quiet internal segment nobody modelled as sensitive, where assumptions quietly replaced controls. For teams working through this on shared infrastructure, FalkorDB’s writeup on multi-tenant cloud security for graph databases covers how those boundaries map to real deployment topology.
Choosing the right control at each boundary
Different boundaries call for different tools. The question is never which protocol wins; it is which one belongs where.
Mutual TLS adds client authentication to the server certificate check. Both ends present certificates, so a workload that does not belong in the cluster cannot connect and look legitimate. Plain TLS proves the server; mTLS proves both ends, which is why service meshes and internal APIs lean on it — identity matters there as much as confidentiality. The certificate lifecycle burden roughly doubles, since every client now needs an identity too. Archman’s architecture notes on TLS and mTLS walk through how the trust anchoring and rotation pieces fit together in a mesh.
IPsec operates at the network layer, so it protects everything crossing a tunnel regardless of application protocol. That makes it the right answer for site-to-site links, VPC-to-VPC connectivity, and cases where the requirement is “this entire path is protected” rather than “these sessions are protected.”
VPNs remain the control point for human and partner access — a person or an external network needing scoped entry into a private environment. The decision there is about access control across a partially trusted boundary, not workload identity.
Practical rule: TLS for application sessions, mTLS when services must verify each other, IPsec or VPN when the whole path or access channel needs covering.

These layer rather than compete. A request can arrive over a VPN, traverse an IPsec tunnel between VPCs, and still use mTLS between two services at the end of it.
Certificates: the part that actually breaks
Transport security fails on certificate operations far more often than on cryptography. Nobody breaks AES-GCM. People do let intermediates expire on a Saturday.
Build around trust chains, not files. A certificate authority — public for internet-facing endpoints, private PKI for internal fleets — issues the identity clients validate. Private PKI is usually the better fit internally because you control issuance scope, naming, and rotation policy. Clients must validate the full chain, not just the leaf, and servers must send their intermediates. An incomplete chain is the single most common cause of “works in my browser, fails from the service,” because browsers fetch missing intermediates and most service runtimes do not.
Automate the lifecycle. The workable model is five steps: issue from a trusted CA or internal PKI, store private keys with strict access control, validate the full chain on every client, rotate before expiry automatically, and retire old trust anchors in a controlled window. Let’s Encrypt’s operational documentation is the reference point for ACME-driven renewal, and the same automation applies to internal PKI through ACME, SPIFFE, or a cloud provider’s private CA.

Manual renewal works right up until the first missed refresh becomes an outage.
Operational rule: if a certificate expiry can page an on-call engineer, the rotation process is not automated yet.
HSTS belongs on browser-facing endpoints, where it stops clients from downgrading to plaintext HTTP. It is a browser control and has nothing to do with mTLS or database connections — the equivalent for a service fleet is simply removing plaintext listeners so there is no fallback to negotiate. If you are setting up certificates on a public web endpoint rather than a database listener, ARPHost’s step-by-step SSL certificate configuration walkthrough covers the web server side of that setup.
Transport identity and application identity also sit next to each other without replacing each other. FalkorDB’s personal access tokens and API authentication shows the application-layer half of that pairing.
Preparing for post-quantum
Any guide written now that stops at TLS 1.3 is already behind.
The threat model is harvest now, decrypt later: an adversary records encrypted traffic today and decrypts it once a cryptographically relevant quantum computer exists. That reframes the timeline. If your data needs to stay confidential for ten years, the migration deadline is not when quantum computers arrive — it is now.
NIST finalized ML-KEM as FIPS 203, and TLS deployments have converged on hybrid key exchange: X25519MLKEM768 combines classical X25519 with ML-KEM-768 so the session is no weaker than today’s even if one component turns out to be flawed. It is on by default in recent Chrome and Firefox, and supported across OpenSSL 3.5, major CDNs, and cloud load balancers.
Confidentiality is migrating first because key exchange can change unilaterally between two endpoints. Authentication lags: post-quantum signatures are substantially larger, and swapping them out requires the whole CA ecosystem to move.
Four things worth doing now:
-
Inventory by lifetime, not by system. Which traffic carries data that must stay confidential past the mid-2030s? That set migrates first.
-
Audit what your runtime actually links against. Browser support tells you nothing about your database drivers, and the dependency differs by language. Python’s
sslmodule uses whatever OpenSSL the interpreter was compiled against, so a container’s base image decides your capabilities — checkssl.OPENSSL_VERSIONrather than assuming. Node.js bundles its own OpenSSL, so support tracks the Node version, not the host library. Go does not use OpenSSL at all;crypto/tlsis a self-contained implementation, and hybrid support is a function of the Go release you build with. The JVM depends on the JDK’s JSSE provider or a replacement like Conscrypt. A cluster where the server supports hybrid key exchange and every client silently falls back to X25519 looks fine until someone inspects the negotiated group. -
Test for large ClientHello handling. Hybrid key shares push the ClientHello past a single packet, and some middleboxes and older servers mishandle the fragmentation. This breaks connections in ways that look nothing like a crypto problem.
-
Avoid hardcoding groups. Pinned key-exchange group lists in configs and client code are the thing that will make the next migration painful. Crypto-agility is the actual deliverable.
The cheap verification is to log the negotiated key exchange group per connection, the same way you would log the protocol version. If it is not in your telemetry, you will find out about a fallback during an audit rather than during testing.
This area moves quickly, so treat specific defaults here as a snapshot and check current library and browser status before building a plan around them.
Performance and observability
TLS costs show up in three places: handshake setup, bulk symmetric encryption during the session, and certificate operations over time.
The middle one is the one people overestimate, and the reason is hardware. AES-NI has been present on x86 server CPUs since Westmere in 2010, and ARMv8 Cryptography Extensions cover the equivalent on Graviton and other ARM instances. These execute AES rounds as single instructions rather than software round functions, which is roughly an order of magnitude faster and puts AES-GCM throughput well past what a database connection will saturate. On the rare platform without acceleration, ChaCha20-Poly1305 is the software-friendly alternative and is why it sits in the recommended suite list earlier.
So the real costs are elsewhere: handshake churn from too many short-lived connections — fixed by connection pooling and session resumption — and the operational drag of certificate management at scale, which is a headcount cost rather than a CPU one.
Observability changes shape too. You lose passive payload inspection, and mTLS makes naive middlebox interception impractical by design. What you keep is arguably more useful for operations anyway:
-
Handshake success and failure rates, split by failure reason
-
Negotiated protocol version, cipher suite, and key exchange group distribution
-
Certificate expiry countdown per endpoint, alerted well before the wire
-
Trust store and chain validation errors
-
Connection churn and resumption hit rate
-
Explicit checks that each designated re-encryption point is actually encrypting
If a service silently falls back to plaintext, telemetry should surface it without anyone reading application data. FalkorDB’s cloud cluster support on GCP describes the kind of multi-zone shape where these checks earn their keep.
Useful check: if your only proof of encryption is that the page loads, you do not have observability, you have optimism.
Configuring TLS for FalkorDB
FalkorDB speaks the RESP protocol, so transport configuration follows Redis conventions. That means the settings below will look familiar if you have configured Redis TLS, and it also means there are separate directives for client traffic, replication traffic, and cluster bus traffic — which is exactly where the termination gap from earlier shows up in practice.
Server configuration
# Listen on TLS only. Setting port 0 removes the plaintext listener entirely,
# which is the fleet equivalent of HSTS: no fallback to negotiate.
port 0
tls-port 6379
tls-cert-file /etc/falkordb/tls/server.crt
tls-key-file /etc/falkordb/tls/server.key
tls-ca-cert-file /etc/falkordb/tls/ca.crt
# yes = require client certificates (mTLS). no = accept clients without one.
tls-auth-clients yes
tls-protocols "TLSv1.2 TLSv1.3"
tls-ciphersuites TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
tls-ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
tls-prefer-server-ciphers yes
# The internal hops. Enabling tls-port alone does NOT encrypt these.
tls-replication yes
tls-cluster yes
Those last two lines are the whole article in miniature. A deployment with tls-port set and tls-replication unset has an encrypted client edge and plaintext replication traffic between nodes — the exact pattern that passes an external scan and fails an internal one.
Connecting
Command line:
redis-cli --tls
--cert /etc/falkordb/tls/client.crt
--key /etc/falkordb/tls/client.key
--cacert /etc/falkordb/tls/ca.crt
-h graph.internal.example.com -p 6379
GRAPH.QUERY social "MATCH (n:Person) RETURN count(n)"
Python:
from falkordb import FalkorDB
db = FalkorDB(
host="graph.internal.example.com",
port=6379,
ssl=True,
ssl_ca_certs="/etc/falkordb/tls/ca.crt",
ssl_certfile="/etc/falkordb/tls/client.crt",
ssl_keyfile="/etc/falkordb/tls/client.key",
ssl_cert_reqs="required",
)
graph = db.select_graph("social")
result = graph.query("MATCH (n:Person) RETURN count(n)")
Node.js:
import { readFileSync } from 'node:fs';
import { FalkorDB } from 'falkordb';
const db = await FalkorDB.connect({
socket: {
host: 'graph.internal.example.com',
port: 6379,
tls: true,
ca: [readFileSync('/etc/falkordb/tls/ca.crt')],
cert: readFileSync('/etc/falkordb/tls/client.crt'),
key: readFileSync('/etc/falkordb/tls/client.key'),
},
});
const graph = db.selectGraph('social');
JVM and Go clients follow the same shape: supply a CA bundle for verification, plus a client certificate and key when tls-auth-clients is set to yes.
Verify from outside the client library before debugging the client library:
openssl s_client -connect graph.internal.example.com:6379
-CAfile /etc/falkordb/tls/ca.crt
-cert /etc/falkordb/tls/client.crt
-key /etc/falkordb/tls/client.key
-tls1_3 < /dev/null
Failures you will actually hit
| Symptom | Likely cause | First thing to check |
|---|---|---|
SSL: WRONG_VERSION_NUMBER |
Plaintext client dialling a TLS port, or the reverse | Is the client actually passing --tls / ssl=True? |
unable to get local issuer certificate |
Incomplete chain, or CA missing from the client trust store | Is the server sending intermediates? Is ssl_ca_certs pointed at the right bundle? |
Hostname mismatch |
Connecting by IP or an internal alias absent from the certificate | Does the SAN list the exact name the client dials? |
Works from redis-cli, fails from the app |
Different trust stores — system bundle vs. a runtime’s bundled roots | Which CA bundle does that runtime load by default? |
| Worked for a year, then stopped | Expired leaf or intermediate | Read notAfter on the leaf and every intermediate |
| Client traffic encrypted, replica traffic in the clear | tls-replication / tls-cluster never enabled |
Those two directives, not tls-port |
For managed deployments, FalkorDB Cloud handles certificate provisioning and rotation for the client edge, and VPC peering removes the public internet from the path — though peering is a network reachability control, not an encryption one, and the internal legs still need their own answer.
A practical audit checklist
Start with boundaries, not tools. Decide where TLS terminates, where traffic is re-encrypted, and where mTLS is required because service identity matters. Then confirm every certificate has an owner, an automated rotation path, and a validated chain.
If you are auditing a system this week, check six things:
-
Legacy protocols are off. TLS 1.0 and 1.1 disabled, TLS 1.2 restricted to ECDHE and AEAD suites.
-
Internal hops after termination are encrypted, or the decision to trust them is written down and owned by someone.
-
Replication and cluster traffic are covered, not just the client edge.
-
Certificates rotate automatically with alerting well ahead of expiry.
-
Telemetry proves the transport is working without inspecting payloads.
-
Nothing hardcodes a key-exchange group, so post-quantum migration is a config change rather than a rewrite.
Transport encryption is one layer of a larger control set — identity, network segmentation, key management, and audit all sit alongside it, and Cloudvara’s overview of implementing cloud security controls is a reasonable survey of how those pieces are usually organized. The through-line stays the same at every layer: default-on encryption, explicit trust boundaries, and no hidden plaintext segments.