Back to blog
Best PracticesCloud SecurityDatabasesGCPOperations & Compliance

Cloud SQL SSL Not Enforced: Encrypt Your GCP Database Connections

Cloud SQL accepts unencrypted connections by default, exposing credentials and data in transit. Learn how to enforce SSL/TLS and prevent it with IaC and policy.

TL;DR

This check flags Cloud SQL instances that accept unencrypted connections, meaning database credentials and query data can travel in plaintext. Set the instance to require SSL/TLS so every client connection is encrypted in transit.

By default, a Cloud SQL instance will happily accept connections that do not use SSL/TLS. That means an application can connect to your MySQL, PostgreSQL, or SQL Server database without encrypting the session at all. The sql_nossl check looks at the SSL configuration on each Cloud SQL instance and fails when encryption is not enforced.

It is one of those settings that looks harmless in a dev environment and turns into a real liability the moment that instance handles anything sensitive. Let's walk through what it catches, why it matters, and how to lock it down.


What this check detects

The check inspects the settings.ipConfiguration.sslMode (or the older requireSsl flag) on each Cloud SQL instance in your GCP project. It fails when the instance is configured to allow non-SSL connections.

In practice there are three states you will see:

  • ALLOW_UNENCRYPTED_AND_ENCRYPTED — both plaintext and SSL connections are accepted. This is the default and it fails the check.
  • ENCRYPTED_ONLY — only SSL/TLS connections are accepted. This passes.
  • TRUSTED_CLIENT_CERTIFICATE_REQUIRED — SSL plus a valid client certificate is required. This passes and is the strongest option.

Note: Google introduced sslMode to replace the binary requireSsl field. If your instance still uses requireSsl: false, the API maps it to ALLOW_UNENCRYPTED_AND_ENCRYPTED, so the same check logic applies.


Why it matters

When SSL is not enforced, your database is reachable over an unencrypted channel. Anyone who can observe traffic on that path can read what is going across the wire, and that includes the credentials used to authenticate.

Here is where this bites people in the real world:

  • Plaintext credentials. The username and password handshake for MySQL and PostgreSQL can be intercepted on an unencrypted connection. Once an attacker has those, they have your database.
  • Sensitive data in the clear. Query results, including PII, payment data, session tokens, and anything else stored in the database, travel unencrypted. A passive sniffer captures all of it.
  • Public IP exposure. If the instance has a public IP and a loose authorized network range (a common pairing with the default SSL setting), the unencrypted endpoint is reachable from outside your VPC entirely.
  • Compliance failures. PCI DSS, HIPAA, SOC 2, and most internal data-handling standards require encryption in transit. An instance that allows plaintext connections is a finding waiting to happen during an audit.

The classic attack scenario is a compromised host or misconfigured peering inside the network. The attacker does not need to break into the database directly. They sit on the network path, watch the connection handshake, lift the credentials, and connect as a legitimate client. No exploit required, just an unencrypted channel and patience.

Warning: Encryption in transit is not the same as encryption at rest. Cloud SQL encrypts data at rest by default, but that does nothing to protect the connection itself. You need both.


How to fix it

Before you flip the switch, confirm that your clients can actually speak SSL. If an application connects without SSL today and you suddenly require it, that application will fail to connect. Check your connection strings and drivers first.

1. Find the affected instances

List your instances and check the SSL mode:

gcloud sql instances list --format="table(name, settings.ipConfiguration.sslMode)"

Inspect a single instance in detail:

gcloud sql instances describe INSTANCE_NAME \
  --format="value(settings.ipConfiguration.sslMode)"

2. Confirm your clients have certificates

If you are enforcing SSL only (not client certs), most modern drivers handle the server certificate automatically. If you want to require client certificates too, create one first:

gcloud sql ssl client-certs create CLIENT_CERT_NAME client-key.pem \
  --instance=INSTANCE_NAME

# Download the server CA and the client cert
gcloud sql instances describe INSTANCE_NAME \
  --format="value(serverCaCert.cert)" > server-ca.pem

gcloud sql ssl client-certs describe CLIENT_CERT_NAME \
  --instance=INSTANCE_NAME \
  --format="value(cert)" > client-cert.pem

3. Enforce SSL on the instance

Danger: Changing SSL mode drops existing connections that do not meet the new requirement. Run this during a maintenance window and confirm every client uses SSL, or you will cause an outage for applications still connecting in plaintext.

To require encrypted connections:

gcloud sql instances patch INSTANCE_NAME \
  --ssl-mode=ENCRYPTED_ONLY

For the stricter setting that also requires a valid client certificate:

gcloud sql instances patch INSTANCE_NAME \
  --ssl-mode=TRUSTED_CLIENT_CERTIFICATE_REQUIRED

4. Console steps

If you prefer the UI:

  1. Open the Cloud SQL instance in the Google Cloud console.
  2. Go to Connections, then the Security tab.
  3. Under SSL mode, select Allow only SSL connections or Require trusted client certificates.
  4. Click Save. The instance applies the change without a restart, but in-flight non-SSL connections will be dropped.

5. Verify

gcloud sql instances describe INSTANCE_NAME \
  --format="value(settings.ipConfiguration.sslMode)"
# Expect: ENCRYPTED_ONLY  or  TRUSTED_CLIENT_CERTIFICATE_REQUIRED

Tip: Use the Cloud SQL Auth Proxy or the language connectors instead of managing certificates by hand. The proxy establishes a mutually authenticated, encrypted tunnel automatically, so you get strong transit security without wiring certs into every application.


How to prevent it from happening again

Fixing one instance is easy. Stopping the next one from launching without SSL is the part that actually sticks. Bake the requirement into your infrastructure code and your guardrails.

Terraform

Set the SSL mode explicitly in your google_sql_database_instance resource so it can never be created in the default state:

resource "google_sql_database_instance" "main" {
  name             = "prod-db"
  database_version = "POSTGRES_15"
  region           = "us-central1"

  settings {
    tier = "db-custom-2-7680"

    ip_configuration {
      ssl_mode    = "ENCRYPTED_ONLY"
      ipv4_enabled = false
    }
  }
}

Policy as code

Block any non-compliant instance before it ever reaches the cloud. With Google's Organization Policy you cannot enforce SSL mode directly, but you can require private IP and pair it with a CI/CD check. Here is a Conftest/OPA rule against a Terraform plan:

package sql.ssl

deny[msg] {
  resource := input.resource_changes[_]
  resource.type == "google_sql_database_instance"
  ssl := resource.change.after.settings[_].ip_configuration[_].ssl_mode
  ssl == "ALLOW_UNENCRYPTED_AND_ENCRYPTED"
  msg := sprintf("Cloud SQL instance '%s' must enforce SSL (set ssl_mode to ENCRYPTED_ONLY)", [resource.address])
}

Wire that into your pipeline so a pull request that introduces a plaintext-capable instance fails the build:

terraform plan -out=tfplan
terraform show -json tfplan > plan.json
conftest test plan.json --policy ./policy

Tip: Run Lensix on a schedule against every project so drift gets caught even when someone changes a setting outside of Terraform. CI gates catch what goes through the pipeline; continuous scanning catches everything else.


Best practices

  • Default to private IP. Disable public IP and connect over a VPC or the Auth Proxy. SSL enforcement plus no public endpoint is a much stronger posture than either alone.
  • Prefer the Auth Proxy or connectors. They handle certificate rotation and mutual TLS for you, which removes a whole class of misconfiguration.
  • Use TRUSTED_CLIENT_CERTIFICATE_REQUIRED for sensitive workloads. Requiring a client certificate adds an authentication factor beyond the database password.
  • Rotate client certificates. They expire after ten years by default, but rotate them on a schedule that matches your credential policy, not the maximum lifetime.
  • Tighten authorized networks. Even with SSL enforced, do not leave 0.0.0.0/0 in your allowed ranges. Scope access to known CIDRs or rely on private connectivity.
  • Audit across all projects. The default-insecure setting means new instances start out failing this check. Treat it as a recurring scan, not a one-time cleanup.

Enforcing SSL on Cloud SQL is a small change with an outsized payoff. It closes off credential interception and plaintext data leakage, it keeps auditors happy, and once it is encoded in Terraform and your CI gates, it stays fixed. Find the instances still running the default, schedule the change, and move on knowing your database connections are encrypted end to end.