This check fires when the SSL/TLS server certificate on a GCP Cloud SQL instance has passed its expiry date. Expired certificates break encrypted client connections and can take applications offline. Rotate the server certificate, push the new CA to your clients, and automate rotation before the next expiry.
Cloud SQL instances that require SSL serve a server certificate to authenticate the database to connecting clients. That certificate has a fixed lifetime. When it expires, any client that verifies the server certificate (which is the whole point of using SSL) will refuse to connect, or your connections silently fall back to unencrypted traffic if you have not enforced SSL properly. The sql_certexpired check flags instances where the active server certificate is already past its expirationTime.
This is one of those issues that stays invisible until the exact moment it becomes a production outage. Certificates do not warn you on their way out unless something is watching them. This post covers what the check looks at, why an expired Cloud SQL certificate is more dangerous than it sounds, and how to fix and prevent it.
What this check detects
Cloud SQL maintains a set of server certificates per instance. Each instance has a server CA certificate with a createTime, a certSerialNumber, and an expirationTime. The Lensix sql_certexpired check inspects the active server certificate on each Cloud SQL instance and compares its expirationTime against the current time. If that timestamp is in the past, the instance fails the check.
Note: Cloud SQL server CA certificates are typically issued with a 10 year validity for the default per-instance CA, but managed and customer-managed CA configurations can produce much shorter lifetimes. Do not assume you have a decade. Instances created or rotated under different CA modes may carry certificates measured in months.
The check looks specifically at the server side certificate that the instance presents. It is distinct from client certificates, which are the certificates your application presents to authenticate itself. Both matter, but an expired server certificate affects every client at once, which is why it gets its own check.
Why it matters
An expired server certificate is not a slow-burn compliance finding. It is a ticking outage. Here is what actually happens when the certificate lapses.
Clients that verify the cert stop connecting
If your application connects with sslmode=verify-ca or verify-full (PostgreSQL) or --ssl-mode=VERIFY_CA / VERIFY_IDENTITY (MySQL), the driver checks the server certificate against the CA you provided. Once it expires, verification fails and the connection is rejected. Every pod, function, and batch job that talks to the database starts throwing connection errors at the same time.
Insecure fallback hides the problem
Some clients are configured loosely, for example sslmode=require without CA verification, or SSL set to "optional" on the instance. In those setups an expired certificate may not cause an outage at all. That sounds better, but it is worse from a security standpoint. The connection still happens, just without real authentication of the server, which leaves you open to man-in-the-middle attacks. You get neither the safety of a hard failure nor the protection SSL was supposed to provide.
Danger: An instance with SSL set to optional and an expired certificate is a quiet downgrade attack waiting to happen. An attacker positioned on the network path can present their own certificate, and a client that no longer trusts the real one may accept anything or fall back to plaintext. Treat optional SSL plus an expired cert as an active exposure, not a future risk.
Business impact
- Unplanned downtime. Database connectivity failures cascade into every service that depends on the instance.
- Compliance findings. PCI DSS, HIPAA, and SOC 2 all expect encryption in transit. An expired certificate undermines that control and shows up in audits.
- Painful recovery under pressure. Rotating a server CA requires pushing the new CA to clients. Doing that during an active outage, with no runbook, is far more stressful than doing it on schedule.
How to fix it
The fix is to rotate the server certificate and distribute the new CA to your clients. Cloud SQL supports a managed rotation flow that adds a new certificate alongside the old one so you can update clients before the switch. If the certificate is already expired, you may need to move faster, but the steps are the same.
1. Inspect the current certificate
gcloud sql ssl server-ca-certs list \
--instance=INSTANCE_NAME \
--project=PROJECT_ID
This shows the active certificate, its serial number, and its expiration. If you see an expirationTime in the past on the active cert, this check is correctly flagging a real problem.
2. Add a new server CA certificate
gcloud sql ssl server-ca-certs create \
--instance=INSTANCE_NAME \
--project=PROJECT_ID
This creates a new "upcoming" server CA while the current one remains active. The instance now presents two valid CAs, which gives clients a window to trust the new one before you complete rotation.
3. Download the new CA and distribute it
gcloud sql ssl server-ca-certs list \
--instance=INSTANCE_NAME \
--project=PROJECT_ID \
--format="value(cert)" > server-ca-new.pem
Ship server-ca-new.pem to every client: update secrets, config maps, mounted files, or whatever your apps use to load the CA. Verify a test client can connect with the new CA before moving on.
Warning: If your clients pin the CA file and you complete rotation before they have the new certificate, those clients will fail to verify the server and lose connectivity. Distribute the new CA to all clients first, then rotate. Skipping this ordering turns a routine maintenance task into an outage.
4. Rotate to the new certificate
Danger: The command below changes the active server certificate on a production database instance. Any client still relying solely on the old CA will be unable to verify the connection immediately afterward. Confirm all clients trust the new CA before running this.
gcloud sql ssl server-ca-certs rotate \
--instance=INSTANCE_NAME \
--project=PROJECT_ID
After rotation, the new certificate becomes active. Once you have confirmed every client is healthy, drop the old one:
gcloud sql instances list-server-cas \
--instance=INSTANCE_NAME
# Older CLI versions and the API also expose a rotation-rollback if needed.
Console steps
- Open the Cloud SQL instance in the Google Cloud Console.
- Go to Connections, then the Security tab.
- Under Manage server certificates, click Create new certificate.
- Download the new server CA and distribute it to your clients.
- Return and click Rotate certificate once clients trust the new CA.
Terraform note
The google_sql_ssl_cert resource manages client certificates, not the server CA rotation, so this is one operation where the imperative gcloud flow or the API is the practical path. What you should manage in Terraform is the enforcement of SSL itself:
resource "google_sql_database_instance" "main" {
name = "prod-pg"
database_version = "POSTGRES_15"
region = "us-central1"
settings {
tier = "db-custom-2-7680"
ip_configuration {
ssl_mode = "ENCRYPTED_ONLY"
}
}
}
Setting ssl_mode to ENCRYPTED_ONLY (or TRUSTED_CLIENT_CERTIFICATE_REQUIRED for mutual TLS) ensures connections cannot quietly fall back to plaintext, which closes the insecure fallback gap described earlier.
How to prevent it from happening again
Certificate expiry is entirely predictable, which means it is entirely preventable. The goal is to never learn about an expiry from an outage.
Monitor expiry continuously
Run a scheduled check that reads the active server certificate expiry across every instance and alerts well before the date. This is exactly what the Lensix sql_certexpired check does on a recurring basis, and it is the difference between a calm scheduled rotation and a 2 a.m. page.
Tip: Do not alert only on already-expired certificates. Add a warning threshold, for example 60 and 30 days out, so you have a comfortable rotation window. By the time a cert is actually expired, you are already in incident mode.
A lightweight script you can run in CI or a cron job:
#!/usr/bin/env bash
set -euo pipefail
THRESHOLD_DAYS=30
now_epoch=$(date +%s)
for instance in $(gcloud sql instances list --format="value(name)"); do
expiry=$(gcloud sql ssl server-ca-certs list \
--instance="$instance" \
--format="value(expirationTime)" | head -n1)
[ -z "$expiry" ] && continue
expiry_epoch=$(date -d "$expiry" +%s)
days_left=$(( (expiry_epoch - now_epoch) / 86400 ))
if [ "$days_left" -lt "$THRESHOLD_DAYS" ]; then
echo "WARN: $instance server cert expires in $days_left days ($expiry)"
fi
done
Gate SSL enforcement in CI/CD
Use policy-as-code to block any Cloud SQL instance that allows unencrypted connections. An OPA/Conftest rule against your Terraform plan keeps insecure fallback from ever shipping:
package cloudsql.ssl
deny[msg] {
resource := input.resource_changes[_]
resource.type == "google_sql_database_instance"
ssl_mode := resource.change.after.settings[_].ip_configuration[_].ssl_mode
ssl_mode == "ALLOW_UNENCRYPTED_AND_ENCRYPTED"
msg := sprintf("Instance %v allows unencrypted connections", [resource.address])
}
Document the rotation runbook
Write the rotation steps down once and store them with the instance. A short runbook that lists which clients consume the CA, where the CA file lives for each, and the exact gcloud commands turns a 30 minute scramble into a five minute task.
Best practices
- Enforce SSL with no fallback. Set
ssl_modetoENCRYPTED_ONLYat minimum. Optional SSL is the trap that turns an expired cert into a silent security hole. - Verify the CA on the client side. Use
verify-caorverify-fullso connections actually authenticate the server. Without verification, the certificate is decoration. - Centralize CA distribution. Store the server CA in a secret manager rather than baking it into images. Rotating then means updating one secret, not rebuilding containers.
- Consider the Cloud SQL Auth Proxy or connectors. The Auth Proxy and language connectors handle TLS and certificate management for you, which removes a whole class of expiry problems from your application code.
- Rotate on a schedule, not on expiry. Pick a recurring rotation cadence well inside the certificate lifetime so you are always operating with margin to spare.
- Track expiry as an inventory metric. Treat certificate expiry across all instances as a fleet-level number you watch, the same way you watch patch levels or backup status.
An expired Cloud SQL certificate is one of the cleaner problems in cloud security: the cause is obvious, the fix is well-defined, and the prevention is pure automation. The only real failure mode is not watching for it. Set up continuous monitoring, enforce SSL with verification, and rotate before the deadline, and this check should stay green indefinitely.

