Back to blog
AWSBest PracticesCloud SecurityDatabasesOperations & Compliance

Redshift SSL Not Required: Enforce Encrypted Connections

Learn why Amazon Redshift clusters must enforce SSL, how to set require_ssl=true via CLI and Terraform, and how to prevent plaintext connections for good.

TL;DR

This check flags Redshift parameter groups where the require_ssl parameter is not set to true, meaning clients can connect over plaintext. Set require_ssl=true on the cluster's parameter group and reboot to enforce encrypted connections.

Amazon Redshift clusters happily accept connections whether or not the client uses SSL. By default, encryption in transit is optional, not mandatory. That means a misconfigured client, a legacy ETL job, or an attacker on the network path can send queries and pull results across an unencrypted channel. The Redshift SSL Not Required check looks for parameter groups where the require_ssl parameter is missing or set to false, and flags any cluster using them.


What this check detects

Redshift cluster behavior is controlled by a parameter group, similar to RDS. One of those parameters, require_ssl, decides whether the cluster will reject any client connection that does not negotiate SSL/TLS.

When require_ssl is false (the default), the cluster accepts both encrypted and plaintext connections. The Lensix check (redshift_nossl in the redshift_checks module) inspects the parameter group attached to each cluster and raises a finding when SSL is not enforced.

Note: The require_ssl parameter only enforces encryption. It does not encrypt anything on its own. The cluster already supports TLS, but without this flag set, clients can opt out, and many do so silently.

It is worth being clear about the distinction between two different protections:

  • Encryption at rest protects data stored on disk and in snapshots. This is a separate setting (--encrypted with a KMS key).
  • Encryption in transit protects data moving between your clients and the cluster. That is what require_ssl governs.

You can have one without the other. This check is concerned only with encryption in transit.


Why it matters

Redshift is usually the place where your most sensitive aggregated data lives: customer records, financial reporting, behavioral analytics, sometimes joined together in ways the source systems never were. Every query result that crosses the wire in plaintext is a potential leak.

The attack scenarios

  • Network sniffing inside the VPC. A compromised EC2 instance or container in the same VPC can capture traffic to the cluster. If connections are plaintext, the attacker reads query results and, worse, the credentials used to authenticate.
  • Misrouted or peered traffic. VPC peering, Transit Gateway, and on-prem connections via Direct Connect or VPN widen the set of hosts that can observe traffic. Plaintext traffic is exposed across every hop.
  • Downgrade by default. Many BI tools, JDBC drivers, and homegrown scripts connect without SSL unless explicitly configured. Developers rarely notice because the query works either way. Enforcing SSL at the cluster removes that footgun entirely.

The compliance angle

Plaintext database connections fail the encryption-in-transit requirements in PCI DSS, HIPAA, SOC 2, and most internal data governance policies. Auditors look for this specific control, and "the driver supports SSL" is not the same as "the cluster requires it." Requiring SSL at the cluster is the defensible position.

Warning: When you flip require_ssl to true, any existing client that connects without SSL will start failing immediately after the cluster reboots. Inventory your connection strings first so you do not break ETL jobs or dashboards at 2am.


How to fix it

The fix has two moving parts: set require_ssl=true on the parameter group, and make sure clients trust the Redshift certificate and connect with SSL enabled.

Step 1: Identify the parameter group in use

aws redshift describe-clusters \
  --query "Clusters[*].{Cluster:ClusterIdentifier,ParamGroup:ClusterParameterGroups[0].ParameterGroupName}" \
  --output table

If the cluster uses a default parameter group (named something like default.redshift-1.0), you cannot modify it. You will need to create a custom parameter group first.

Step 2: Create a custom parameter group (if needed)

aws redshift create-cluster-parameter-group \
  --parameter-group-name prod-redshift-ssl \
  --parameter-group-family redshift-1.0 \
  --description "Enforces SSL connections"

Step 3: Set require_ssl to true

aws redshift modify-cluster-parameter-group \
  --parameter-group-name prod-redshift-ssl \
  --parameters "ParameterName=require_ssl,ParameterValue=true,ApplyType=static"

Step 4: Attach the parameter group and reboot

If you created a new parameter group, attach it to the cluster:

aws redshift modify-cluster \
  --cluster-identifier prod-analytics \
  --cluster-parameter-group-name prod-redshift-ssl

Danger: require_ssl is a static parameter, so it only takes effect after a cluster reboot. Rebooting interrupts active queries and briefly takes the cluster offline. Schedule this in a maintenance window for production.

aws redshift reboot-cluster --cluster-identifier prod-analytics

Step 5: Verify it took effect

aws redshift describe-cluster-parameters \
  --parameter-group-name prod-redshift-ssl \
  --query "Parameters[?ParameterName=='require_ssl']"

You should see ParameterValue as true and the apply status as in-sync.

Step 6: Update your clients

Once SSL is required, clients must negotiate TLS and trust the Redshift CA bundle. For most JDBC connections, append SSL settings to the connection string:

# JDBC example
jdbc:redshift://prod-analytics.abc123.us-east-1.redshift.amazonaws.com:5439/analytics?ssl=true&sslmode=verify-full

For psql and libpq-based clients, set the mode and point at the AWS-provided certificate bundle:

PGSSLMODE=verify-full \
PGSSLROOTCERT=/path/to/redshift-ca-bundle.crt \
psql "host=prod-analytics.abc123.us-east-1.redshift.amazonaws.com port=5439 dbname=analytics user=admin"

Tip: Use verify-full rather than require where your tooling supports it. require encrypts the connection but does not validate the server certificate, which leaves you open to man-in-the-middle attacks. verify-full checks both the chain and the hostname.


Fixing it with Infrastructure as Code

If you manage Redshift through Terraform, bake the requirement into the parameter group so it can never drift back to plaintext:

resource "aws_redshift_parameter_group" "ssl_enforced" {
  name   = "prod-redshift-ssl"
  family = "redshift-1.0"

  parameter {
    name  = "require_ssl"
    value = "true"
  }
}

resource "aws_redshift_cluster" "analytics" {
  cluster_identifier        = "prod-analytics"
  node_type                 = "ra3.xlplus"
  cluster_parameter_group_name = aws_redshift_parameter_group.ssl_enforced.name
  encrypted                 = true
  # ... remaining configuration
}

For CloudFormation:

RedshiftSslParamGroup:
  Type: AWS::Redshift::ClusterParameterGroup
  Properties:
    Description: Enforces SSL connections
    ParameterGroupFamily: redshift-1.0
    Parameters:
      - ParameterName: require_ssl
        ParameterValue: "true"

How to prevent it from happening again

A one-time fix decays. Clusters get created from old templates, someone clones a parameter group, a default group slips into production. Put guardrails in place so the misconfiguration cannot reach production unnoticed.

Gate it in CI/CD with policy-as-code

If you run Terraform, add a Checkov or OPA policy that fails the plan when require_ssl is not true. A Checkov skip is loud and reviewable, unlike a setting that silently never existed.

# Run Checkov against your Terraform
checkov -d ./infra --check CKV_AWS_105

A minimal OPA/Rego policy for a Terraform plan looks like this:

package redshift.ssl

deny[msg] {
  rc := input.resource_changes[_]
  rc.type == "aws_redshift_parameter_group"
  param := rc.change.after.parameter[_]
  param.name == "require_ssl"
  param.value != "true"
  msg := sprintf("Redshift parameter group %s must set require_ssl=true", [rc.address])
}

Detect drift continuously

Policy-as-code only covers resources that pass through your pipeline. For everything else, run a continuous check. Lensix runs redshift_nossl across your accounts on a schedule and surfaces any cluster whose parameter group does not enforce SSL, including clusters created by hand or by a forgotten automation account.

Tip: Pair the scheduled scan with an alert to your team channel. The goal is to hear about a non-compliant cluster within minutes of its creation, not at the next quarterly audit.


Best practices

  • Require SSL on every cluster, in every environment. Dev and staging clusters often hold copies of production data. Treat them the same.
  • Use verify-full on clients. Enforcing SSL on the server is half the job. Validating the certificate on the client closes the man-in-the-middle gap.
  • Combine with encryption at rest. Set --encrypted with a customer-managed KMS key so data is protected on disk and in snapshots as well as on the wire.
  • Keep clusters off public subnets. SSL protects confidentiality, but a publicly reachable cluster still expands your attack surface. Place clusters in private subnets and reach them through a bastion or VPN.
  • Standardize on custom parameter groups. Never run production on a default parameter group. You cannot modify defaults, and they make consistent policy enforcement impossible.
  • Document the certificate bundle location. Distribute the AWS Redshift CA bundle through your config management so client teams do not disable verification out of frustration.

Enforcing SSL on Redshift is a small, well-understood change with an outsized payoff. It closes a quiet exposure, satisfies a control auditors always look for, and costs nothing beyond a maintenance-window reboot. Set require_ssl=true, verify your clients connect cleanly, and lock it in with policy-as-code so it stays that way.