Back to blog
AWSBest PracticesCloud SecurityDatabasesNetworking

Redshift Cluster Not in a VPC: Why It Matters and How to Fix It

Learn why Amazon Redshift clusters outside a VPC are a security risk, and how to migrate them into a VPC with snapshots, CLI, and Terraform.

TL;DR

This check flags Redshift clusters running on the legacy EC2-Classic platform instead of inside a VPC, which strips you of security groups, network ACLs, and private subnet isolation. Migrate the cluster into a VPC by creating a cluster subnet group and restoring from a snapshot into your VPC.

Amazon Redshift is where a lot of organizations park their most sensitive aggregated data: customer records, financial reporting, behavioral analytics, the works. When a cluster sits outside a VPC, it falls back on the EC2-Classic networking model, where the controls you rely on every day simply do not exist. This check, redshift_novpc, looks for exactly that condition and tells you which clusters need to move.


What this check detects

The redshift_novpc check inspects each Redshift cluster in your AWS account and reports any cluster that is not deployed inside a Virtual Private Cloud (VPC). In API terms, a cluster running in a VPC has a populated VpcId field. When that field is empty, the cluster is using EC2-Classic networking.

EC2-Classic is the original flat network AWS launched with in 2006. Every instance and managed resource shared a single, AWS-owned network space. AWS began retiring EC2-Classic in 2022, but clusters created in older accounts, or restored from very old snapshots, can still end up outside a VPC.

Note: A Redshift cluster outside a VPC uses a cluster security group (the EC2-Classic style), which only supports CIDR/IP and EC2 security group ingress rules. Once inside a VPC, the cluster uses standard VPC security groups, giving you far finer control over inbound and outbound traffic.


Why it matters

Network isolation is the first line of defense for a data warehouse. A cluster outside a VPC loses several controls at once:

  • No private subnets. You cannot place the cluster in a private subnet with no route to an internet gateway. Without that, restricting external reachability depends entirely on the cluster security group, which is coarse.
  • No VPC security groups or network ACLs. You lose stateful security groups and stateless NACLs, both of which are core to defense in depth.
  • No VPC endpoints. You cannot route traffic to Redshift over an interface VPC endpoint or keep it off the public internet entirely.
  • No VPC Flow Logs. You lose the network-level visibility that flow logs give you for forensics and anomaly detection.
  • No enhanced features. Enhanced VPC routing, which forces COPY and UNLOAD traffic through your VPC, requires the cluster to be in a VPC.

The practical attack scenario is straightforward. An EC2-Classic cluster with a permissive cluster security group, say one that allows 0.0.0.0/0 on port 5439, is reachable from anywhere on the internet. Combine that with weak or leaked database credentials and an attacker can connect directly and start running queries against your warehouse. Because you have no flow logs and limited network segmentation, you may not notice until data is already gone.

Warning: If you still have any EC2-Classic resources, AWS may have already scheduled them for forced migration. Treat a non-VPC Redshift cluster as a ticking clock, not just a config preference. Plan the move on your terms before AWS does it on theirs.


How to fix it

You cannot toggle a running cluster from EC2-Classic into a VPC in place. The supported path is to restore from a snapshot into a VPC. Here is the full sequence.

1. Confirm the cluster is outside a VPC

aws redshift describe-clusters \
  --query 'Clusters[?VpcId==`null`].[ClusterIdentifier,ClusterStatus]' \
  --output table

Any cluster returned here has no VpcId and is running on EC2-Classic.

2. Create a cluster subnet group

A Redshift cluster in a VPC needs a subnet group that spans the subnets it can launch into. Use private subnets across at least two Availability Zones.

aws redshift create-cluster-subnet-group \
  --cluster-subnet-group-name prod-redshift-subnets \
  --description "Private subnets for prod Redshift" \
  --subnet-ids subnet-0a1b2c3d subnet-0e4f5g6h

3. Take a fresh manual snapshot

Danger: The migration involves restoring a new cluster and then deleting the old one. Take a manual snapshot first and verify it completes. If you delete the source cluster before the restore is validated, that data is gone.

aws redshift create-cluster-snapshot \
  --snapshot-identifier prevpc-migration-snapshot \
  --cluster-identifier my-legacy-cluster

# Wait until it is available
aws redshift wait snapshot-available \
  --snapshot-identifier prevpc-migration-snapshot

4. Restore the snapshot into the VPC

aws redshift restore-from-cluster-snapshot \
  --cluster-identifier my-cluster-vpc \
  --snapshot-identifier prevpc-migration-snapshot \
  --cluster-subnet-group-name prod-redshift-subnets \
  --vpc-security-group-ids sg-0123456789abcdef0 \
  --publicly-accessible false \
  --enhanced-vpc-routing

A few choices here matter:

  • --publicly-accessible false keeps the cluster off the public internet, which is what you want for almost every warehouse.
  • --enhanced-vpc-routing forces COPY/UNLOAD traffic through your VPC so it can be governed by your routing and endpoints.
  • --vpc-security-group-ids should point to a tightly scoped security group, not a default one.

5. Cut over and decommission

Update your application connection strings and BI tools to the new cluster endpoint, validate queries, then delete the old cluster.

Danger: The command below permanently deletes the legacy cluster. Only run it after you have confirmed the new VPC cluster is serving traffic correctly and you have a retained final snapshot.

aws redshift delete-cluster \
  --cluster-identifier my-legacy-cluster \
  --final-cluster-snapshot-identifier my-legacy-final-snapshot

Terraform example

If you manage infrastructure as code, defining the cluster with a subnet group from the start avoids the whole problem. The presence of cluster_subnet_group_name means the cluster lives in a VPC.

resource "aws_redshift_subnet_group" "main" {
  name       = "prod-redshift-subnets"
  subnet_ids = [aws_subnet.private_a.id, aws_subnet.private_b.id]
}

resource "aws_redshift_cluster" "warehouse" {
  cluster_identifier        = "prod-warehouse"
  node_type                 = "ra3.xlplus"
  number_of_nodes           = 2
  database_name             = "analytics"
  master_username           = "admin"
  manage_master_password    = true

  cluster_subnet_group_name = aws_redshift_subnet_group.main.name
  vpc_security_group_ids    = [aws_security_group.redshift.id]
  publicly_accessible       = false
  enhanced_vpc_routing      = true
  encrypted                 = true
}

Tip: Restoring into a different node type (for example moving from older DC2 nodes to RA3) during this migration is a good time to modernize. RA3 separates storage from compute and works only inside a VPC, so the migration and the upgrade can happen in one step.


How to prevent it from happening again

The reliable fix is to make non-VPC clusters impossible to create and to catch any drift early.

Block it with an SCP

An AWS Organizations service control policy can deny cluster creation that does not reference a subnet group. Combined with the fact that new accounts no longer support EC2-Classic, this closes the door.

Gate it in CI/CD with policy as code

If you use Terraform, add a Checkov or OPA/Conftest check to your pipeline. Checkov already ships a relevant policy:

checkov -d . --check CKV_AWS_87   # Redshift should not be publicly accessible
checkov -d . --check CKV_AWS_71   # Redshift should use enhanced VPC routing

A simple Conftest/Rego rule to require a subnet group:

package main

deny[msg] {
  resource := input.resource.aws_redshift_cluster[name]
  not resource.cluster_subnet_group_name
  msg := sprintf("Redshift cluster '%s' must specify cluster_subnet_group_name", [name])
}

Tip: Run Lensix on a schedule against every account so a manually restored or imported cluster outside a VPC gets flagged within minutes, not at the next audit. Continuous scanning catches the cases that slip past pipeline gates.


Best practices

  • Always launch Redshift in a VPC, in private subnets. There is no good reason to expose a warehouse to the public internet.
  • Set publicly_accessible = false. Reach the cluster over a VPN, Direct Connect, or a bastion inside the VPC.
  • Scope security groups tightly. Allow port 5439 only from the specific application security groups and CIDR ranges that need it. No 0.0.0.0/0.
  • Enable enhanced VPC routing. Keep COPY and UNLOAD traffic inside your network where it can be logged and controlled.
  • Turn on VPC Flow Logs for the subnets hosting the cluster so you have network telemetry for investigations.
  • Encrypt at rest with KMS and require SSL for client connections so isolation is backed by encryption.
  • Use interface VPC endpoints for the Redshift API and related services to avoid traffic traversing the public internet.

Moving a cluster into a VPC is a one-time effort that pays off across every other control you layer on top of it. Once the network boundary is real, security groups, flow logs, private endpoints, and encryption all start working together instead of papering over a flat, public-by-default network.