Encryption in transit for data stores: the basics
Why "TLS is available" is not the same as "TLS is required"
Almost every AWS data store can speak TLS, and almost none of them require it by default. An RDS for PostgreSQL or MySQL instance presents a certificate but happily accepts a plaintext connection from any client that does not ask for SSL. A Redshift cluster speaks the PostgreSQL wire protocol and will negotiate TLS if the client wants it, or fall back to clear text if it does not. ElastiCache replication groups, DocumentDB clusters, DAX clusters, MSK brokers, EMR clusters and DMS endpoints all share the same shape: encryption is offered but optional. The risk is identical in every case, which is that a single old driver, a forgotten batch job or a misconfigured pipeline ships credentials and query data across the network in the clear.
AWS Security Hub turns each engine's version of this gap into its own control, which is why one estate can fail a dozen in-transit checks at once. RDS.38 looks at rds.force_ssl on PostgreSQL parameter groups; RDS.36, RDS.39, RDS.41, RDS.43 and RDS.44 cover the equivalent enforcement on MySQL, MariaDB, SQL Server, Aurora proxies and the RDS Proxy front door; Redshift.2 checks require_ssl on the cluster parameter group; ElastiCache.5 reads TransitEncryptionEnabled on a replication group; DocumentDB.6, DynamoDB.7 (DAX), EMR.4, MSK.1, MSK.3 and DMS.9 and DMS.12 cover the rest. They look like separate problems on the report, but they are one capability: refuse any connection to a data store that will not encrypt itself.
The mechanics differ by engine, and that detail decides how hard the fix is. RDS and Redshift enforce TLS through a parameter group flag and need a reboot. ElastiCache and MSK fix the in-cluster setting at create time, so the older engines need a rebuild and migration rather than a toggle. DMS enforces SSL on the endpoint definition. The job is to find every data store that permits plaintext, confirm its clients already speak TLS, then flip enforcement on through the right mechanism for each engine.
In this lesson you will learn how each major AWS data store expresses TLS enforcement, why "TLS available" and "TLS required" are different positions, and how to flip enforcement on without breaking a live client. The Controls this lesson covers section lists every Security Hub control in this capability, each linking to a deep page with the exact check and a copy-and-paste fix.
The setting that lives almost everywhere except where you look
The enforcement flag is rarely on the database itself. RDS hides it behind rds.force_ssl (PostgreSQL) or require_secure_transport (MySQL) in a parameter group, and the default parameter group is read-only, so you must create a custom one. Redshift puts require_ssl on a cluster parameter group, also read-only by default. ElastiCache and MSK fix encryption at creation time, so on older engines there is no toggle at all. The result is a control family where teams routinely assume their traffic is encrypted, because most modern drivers negotiate TLS automatically, while the store quietly accepts the one plaintext client that does not. The database never logs a warning about the unencrypted connection it just allowed.
Finding plaintext-permitting data stores across an estate
Priya is the platform lead at a payments company preparing for a PCI assessment. Security Hub shows in-transit encryption failures spread across RDS, Redshift and ElastiCache in two accounts that pre-date the team's current guardrails.
Rather than work the findings one engine at a time, she starts by listing which data stores still permit plaintext, so she can rank them by the data they hold and confirm their clients already speak TLS before changing anything.
Start with the highest-value stores. List every ElastiCache replication group and whether in-transit encryption is on, the exact property ElastiCache.5 evaluates.
Rank in-transit failures by what the store caches or holds. Auth and payment data first.
How each engine enforces TLSdeep dive
The enforcement mechanism falls into three patterns. The first is a parameter-group flag that needs a reboot: RDS for PostgreSQL uses rds.force_ssl, RDS for MySQL and MariaDB use require_secure_transport, and Redshift uses require_ssl on a cluster parameter group. These are static parameters, so the change takes effect only after a reboot, and the default parameter group is immutable, so you create a custom group first. Security Hub keys off the applied state, so a finding stays FAILED until the reboot lands.
The second is a create-time setting that cannot be toggled in place. ElastiCache exposes TransitEncryptionEnabled and, on Redis OSS 7+, a TransitEncryptionMode of preferred then required for a rolling cutover; older engines must be rebuilt. MSK splits encryption into ClientBroker (how producers and consumers connect) and InCluster (how brokers replicate to each other), and InCluster is immutable after creation, so MSK.1 failures need a replacement cluster and a migration. EMR sets in-transit encryption through a security configuration applied at cluster launch.
The third is an endpoint or proxy setting. DMS enforces SSL through the endpoint definition's SslMode (require, verify-ca or verify-full), covered by DMS.9 and DMS.12; the RDS Proxy enforces TLS on its own listener (RDS.41); DocumentDB requires TLS through the tls cluster parameter (DocumentDB.6); and DAX enforces in-transit encryption as a create-time cluster setting (DynamoDB.7). The common theme is that enabling TLS often surfaces a client that was silently connecting in plaintext, so confirm the clients before you enforce.
What is the impact of a data store that permits plaintext?
The direct risk is interception. With enforcement off, any client that connects without requesting TLS sends its traffic, including the authentication exchange and every row of query or cache data, across the network in clear text. An attacker positioned on the network path, a compromised host in the same VPC, a misconfigured peering link or a malicious insider with packet-capture access, can read credentials and exfiltrate data without ever authenticating to the store. Data stores are a particularly rich target because they concentrate the hottest, most sensitive data in one place.
The compliance impact is usually the one that forces action. These controls map to PCI DSS 4.0 requirement 4.2.1 and the NIST 800-53 data-in-transit family (SC-8, SC-13, SC-23). An open finding is concrete, named evidence that data-in-transit protection is not enforced on a sensitive system, exactly what surfaces in a SOC 2 review, a PCI assessment or an enterprise security questionnaire. Unlike a fuzzy posture note, "this database permits unencrypted connections" is hard to argue with, because the flag is either set or it is not.
The remediation impact varies by engine and is the reason these findings accumulate. Parameter-group fixes are free and quick but need a reboot window. ElastiCache and MSK on older engines need a rebuild and migration, which is real engineering effort, plus an application-side change so clients connect over TLS. The cost is the coordinated change, not the encryption itself, which carries negligible price and performance overhead.
There is a quieter second-order impact: every plaintext-permitting store normalises the next one. If the team's mental model is "the VPC is the boundary, so the database does not need TLS," the next store launches the same way and the finding count grows. Closing this well means changing the default so new stores are born enforcing TLS, which converts an open-ended audit line into a one-time set of migrations plus a guardrail.
How do you enforce TLS on data stores safely?
Work the capability as one loop rather than chasing individual findings. The order matters: audit the clients before you enforce, so you do not refuse a live connection mid-flight, and pick the right mechanism for each engine.
1. Inventory every store that permits plaintext, ranked by data sensitivity
List the in-transit setting for every RDS instance, Redshift cluster, ElastiCache replication group, DocumentDB cluster, DAX cluster, MSK cluster and DMS endpoint across all regions and accounts. Rank the failures by what they hold: anything in PCI scope or holding authentication data goes first. Treat this inventory as the source of truth, not the raw finding count, because one engine family can trigger several controls.
2. Confirm clients already speak TLS before enforcing
The only thing that breaks when you require TLS is a client that connects in plaintext. Query the engine (for example pg_stat_ssl on PostgreSQL) or check access logs and CloudTrail data events to find non-TLS sessions, trace each back to its application, and update the driver to require TLS pointed at the right CA bundle. Do not enforce until that list is empty.
3. Flip enforcement through the right mechanism, highest impact first
For RDS set rds.force_ssl or require_secure_transport in a custom parameter group and reboot; for Redshift set require_ssl and reboot; for DMS set the endpoint SslMode. For ElastiCache on Redis OSS 7+ use the preferred-then-required rolling migration, and for older ElastiCache and MSK build an encrypted replacement and migrate. Verify behaviour, not just the pending value: attempt a deliberate plaintext connection and confirm it is refused.
4. Make TLS-required the default for new stores
Bake the enforcement setting into the shared database and cache modules so new stores launch encrypted, and back it with AWS Config rules and Service Control Policies so the setting cannot be quietly dropped. That converts a recurring audit finding into a one-time set of migrations, after which the control can only appear for legacy stores on a known list.
# Find the highest-impact plaintext-permitting stores across engines.
aws rds describe-db-instances \
--query 'DBInstances[].DBInstanceIdentifier' --output text
aws elasticache describe-replication-groups \
--query 'ReplicationGroups[?TransitEncryptionEnabled==`false`].ReplicationGroupId' \
--output text
# RDS for PostgreSQL: require TLS via rds.force_ssl (static -> needs a reboot).
PG=$(aws rds describe-db-instances --db-instance-identifier prod-orders-pg \
--query 'DBInstances[].DBParameterGroups[].DBParameterGroupName' --output text)
aws rds modify-db-parameter-group --db-parameter-group-name "$PG" \
--parameters 'ParameterName=rds.force_ssl,ParameterValue=1,ApplyMethod=pending-reboot'
aws rds reboot-db-instance --db-instance-identifier prod-orders-pg
# Redshift: require_ssl on a custom cluster parameter group, then reboot.
aws redshift modify-cluster-parameter-group --parameter-group-name analytics-tls \
--parameters ParameterName=require_ssl,ParameterValue=true
aws redshift reboot-cluster --cluster-identifier analytics-prod Quick quiz
Question 1 of 5Security Hub shows in-transit encryption failures across RDS, Redshift and ElastiCache. What is the most efficient way to think about them?
You scored
0 / 5
Keep learning
Go deeper on how TLS enforcement works across the engines in this capability.
- Using SSL with a PostgreSQL DB instance How rds.force_ssl works and how to require SSL on RDS for PostgreSQL connections.
- Amazon Redshift: configuring security options for connections How Redshift negotiates TLS and what require_ssl does to client connections.
- Amazon ElastiCache in-transit encryption How to enable TLS, the preferred and required migration modes, and the client-side changes connections require.
You can now treat database and cache encryption in transit as one capability rather than a scatter of findings: inventory every store that permits plaintext, rank them by the data they hold, confirm clients already speak TLS, then enforce through the right mechanism for each engine (a parameter flip and reboot on RDS and Redshift, a rolling or rebuild migration on ElastiCache and MSK, an endpoint setting on DMS), and make TLS-required the default for new stores. The Controls this lesson covers section below links every control in this group to its deep page and fix.
Back to the library