Back to blog
Best PracticesCloud SecurityDatabasesGCPMonitoring & Logging

PostgreSQL Error Logging Not Configured on Cloud SQL

Learn why Cloud SQL PostgreSQL instances need log_min_messages configured, the risks of empty error logs, and step-by-step CLI, console, and Terraform fixes.

TL;DR

This check flags Cloud SQL PostgreSQL instances where the log_min_messages flag is set too permissively (or left at default), meaning real errors never get logged. Without error logs you lose your audit trail and your ability to debug failures or detect attacks. Fix it by setting log_min_messages to WARNING (or stricter) on the instance.

Logging is one of those things nobody notices until they need it, and by then it is usually too late. When a PostgreSQL instance starts rejecting connections, throwing constraint violations, or getting probed by someone running malformed queries, the error log is the first place an engineer looks. If that log is empty because the verbosity was never configured, you are debugging blind.

The Lensix sql_pgerrors check inspects your GCP Cloud SQL PostgreSQL instances and verifies that error-level logging is actually being captured. This post walks through what it looks at, why an empty error log is a genuine security and reliability problem, and exactly how to fix it.


What this check detects

The check examines the database flags on each Cloud SQL PostgreSQL instance, specifically the log_min_messages parameter. This flag controls the minimum severity level that PostgreSQL writes to its server log.

PostgreSQL message severities run from least to most severe like this:

  • DEBUG5 through DEBUG1
  • INFO
  • NOTICE
  • WARNING
  • ERROR
  • LOG
  • FATAL
  • PANIC

The log_min_messages flag sets the threshold. Anything at or above the configured level gets written to the log; anything below it is silently discarded. The default value in PostgreSQL is WARNING, which captures warnings, errors, fatals, and panics. The problem arises when the flag is loosened to something like ERROR, FATAL, or PANIC, which means warnings and lower-severity diagnostic messages never reach the log at all.

Note: Do not confuse log_min_messages with log_min_error_statement. The first controls which server messages get logged. The second controls the severity at which the SQL statement that caused an error is included alongside the message. Both matter, but this check is concerned with the former.

The check fails when log_min_messages is set to a level that excludes meaningful error and warning output, or when the relevant logging flags are absent and the effective configuration provides no useful diagnostic coverage.


Why it matters

An empty or near-empty error log hurts you on two fronts: security visibility and operational debugging.

You lose your forensic trail

When something goes wrong with a database, the logs are your evidence. Consider a few scenarios:

  • An application bug starts violating a unique constraint thousands of times an hour. Without warning and error logging, you have no record of when it began or how widespread it is.
  • An attacker probes your database with SQL injection payloads. Many of these throw syntax errors or type-cast failures. With error logging off, those failed attempts leave no trace.
  • A connection storm exhausts your max_connections limit. The rejection messages that would normally explain the outage are gone.

In an incident response situation, the absence of logs is not neutral. It actively slows down root cause analysis and can mean the difference between a thirty-minute fix and a multi-hour investigation.

It undermines compliance

Frameworks like PCI DSS, SOC 2, and HIPAA all expect database systems to produce audit and error logs. The CIS Benchmark for Google Cloud Platform specifically recommends configuring log_min_messages appropriately for Cloud SQL PostgreSQL instances. An auditor who asks for database error logs and is handed an empty file is going to write a finding.

Warning: Setting the verbosity too low has the opposite problem. If you set log_min_messages to DEBUG5 or INFO, you will flood Cloud Logging with noise, drive up log storage costs, and make it harder to find the signal. The goal is a sensible threshold, not maximum verbosity.

Cloud SQL logs feed Cloud Logging

On Cloud SQL, PostgreSQL server logs are automatically exported to Cloud Logging, where you can search them, build log-based metrics, and trigger alerts. That pipeline only works if PostgreSQL actually emits the messages in the first place. A misconfigured log_min_messages flag silences the source, and everything downstream sees nothing.


How to fix it

The fix is to set log_min_messages to WARNING (the recommended baseline) so that warnings, errors, fatals, and panics are all captured. You can do this through the console, the gcloud CLI, or infrastructure as code.

Option 1: gcloud CLI

Set the flag on your instance with gcloud sql instances patch:

gcloud sql instances patch my-postgres-instance \
  --database-flags=log_min_messages=WARNING

Danger: The --database-flags argument replaces the entire set of flags on the instance, it does not merge. If your instance already has other flags configured, list all of them in a single comma-separated value or you will silently wipe the others. Check the current flags first with gcloud sql instances describe my-postgres-instance --format="value(settings.databaseFlags)".

If your instance already has, say, log_checkpoints and log_connections set, include them all:

gcloud sql instances patch my-postgres-instance \
  --database-flags=log_min_messages=WARNING,log_checkpoints=on,log_connections=on

Warning: The log_min_messages flag does not require a restart, so this patch applies without downtime. Some other database flags do trigger a restart, however, so if you are batching several changes together, check each one before running the patch on a production instance.

Option 2: Google Cloud Console

  1. Open the Cloud SQL Instances page and select your PostgreSQL instance.
  2. Click Edit.
  3. Expand the Flags section.
  4. Click Add a database flag.
  5. Choose log_min_messages from the dropdown and set the value to WARNING.
  6. Click Save.

Option 3: Terraform

If you manage Cloud SQL with Terraform, add the flag to the settings block of your google_sql_database_instance resource:

resource "google_sql_database_instance" "postgres" {
  name             = "my-postgres-instance"
  database_version = "POSTGRES_15"
  region           = "us-central1"

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

    database_flags {
      name  = "log_min_messages"
      value = "WARNING"
    }

    # Keep any existing flags as their own blocks
    database_flags {
      name  = "log_connections"
      value = "on"
    }
  }
}

In Terraform each flag is its own database_flags block, so you do not have the same overwrite footgun as the CLI. Just make sure every flag you want is represented.

Tip: After applying the change, generate a deliberate warning to confirm logs are flowing. Connect to the instance and run a query that triggers a warning, then check Cloud Logging for the entry. A quick way is to cast a value out of range or reference a deprecated function. If it shows up in Logs Explorer under the cloudsql.googleapis.com/postgres.log resource, your pipeline works end to end.


How to prevent it from happening again

Fixing one instance is easy. Keeping every instance compliant as your fleet grows requires guardrails.

Catch it in Terraform CI with policy as code

If you provision Cloud SQL through Terraform, you can block non-compliant plans before they ever reach GCP. Here is a Sentinel-style or OPA policy approach using Conftest with Rego against a Terraform plan:

package main

deny[msg] {
  resource := input.resource_changes[_]
  resource.type == "google_sql_database_instance"

  flags := resource.change.after.settings[_].database_flags
  not has_log_min_messages(flags)

  msg := sprintf(
    "Cloud SQL instance '%s' must set log_min_messages to WARNING or stricter",
    [resource.change.after.name],
  )
}

has_log_min_messages(flags) {
  flag := flags[_]
  flag.name == "log_min_messages"
  flag.value == "WARNING"
}

Wire this into your pipeline so a terraform plan that omits the flag fails the build:

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

Enforce it organization-wide

Use a GCP Organization Policy or a recurring scan to detect drift across projects. Lensix runs the sql_pgerrors check continuously, so a newly created instance with logging disabled surfaces as a finding without anyone having to remember to look.

Tip: Pair the check with a log-based alert. Once logging is on, create a Cloud Monitoring alert on a spike in ERROR or FATAL messages. That way the logging you just enabled actively earns its keep instead of sitting unread.


Best practices

While you are in the flags configuration, it is worth setting the broader logging posture for the instance rather than touching just one flag in isolation.

  • Set log_min_messages to WARNING as the baseline. Go stricter only if you have a specific reason, and never looser.
  • Set log_min_error_statement to ERROR so the offending SQL is logged alongside the error message, which makes debugging far faster.
  • Enable log_connections and log_disconnections to track session activity, which is valuable for spotting unusual access patterns.
  • Enable log_checkpoints for performance and reliability diagnostics.
  • Tune log_min_duration_statement if you want slow-query logging, but set a reasonable threshold (for example 1000 ms) to avoid logging every query.
  • Set a retention policy on the Cloud Logging sink so error logs are kept long enough to satisfy your compliance requirements, typically 90 days to a year.

Logging configuration is not a one-time task. Every new instance inherits whatever your provisioning code or console defaults dictate, so the only durable fix is to bake the right flags into the templates that create those instances.

Get the baseline right once, enforce it in CI, and keep a continuous check running against your live fleet. That combination means an empty error log never becomes the reason your next incident took twice as long to resolve.