Check RDS Auto Minor Version Upgrade and Pending Maintenance

A paper wall calendar with dates circled in red marker

Photo by Towfiqu barbhuiya on Unsplash

RDS auto minor version upgrade is the AutoMinorVersionUpgrade flag on each DB instance and on Aurora and Multi-AZ DB clusters. Read it with DescribeDBInstances and DescribeDBClusters, then call DescribePendingMaintenanceActions to see which updates are queued and their AutoAppliedAfterDate and ForcedApplyDate. For Aurora, one instance with the flag off blocks automatic upgrades for the whole cluster.

Minor versions carry the security and bug fixes for your database engine. When auto minor version upgrade is off, those fixes wait for someone to apply them by hand, and when a version reaches end of support, AWS upgrades it anyway on its own schedule. Either way, you want to know before the maintenance window does something you didn’t plan for.

This example gives you a TypeScript script for the AWS SDK for JavaScript v3 that lists every database with RDS auto minor version upgrade turned off and every pending maintenance action with its dates. With --names and --apply, it turns the setting back on. For databases stuck on an old major version, use the script to find RDS and Aurora databases on extended support instead.

How does RDS auto minor version upgrade work?

AWS marks one minor version per engine version as the automatic upgrade target. A database is upgraded to it when it runs an older minor version and has the setting on. RDS schedules the upgrade in the maintenance window, runs a precheck, upgrades, runs post-upgrade checks and marks it complete. AWS is clear that automatic upgrades incur downtime, with the length depending on the engine and database size.

Three details matter for an audit:

  • Off doesn’t mean never. AWS states that for critical security issues or when a version reaches its end-of-support date, RDS and Aurora apply a minor version upgrade even when the setting is off.
  • Aurora decides at the cluster. The Aurora maintenance guide recommends setting it on the DB cluster, not on individual instances. If any instance in the cluster has it off, the cluster isn’t upgraded automatically, and the cluster’s maintenance window takes precedence over instance windows. Aurora Global Database doesn’t support automatic minor version upgrades.
  • Turning it on can trigger an outage. The ModifyDBInstance reference says an outage occurs when the upgrade is enabled for the window, a newer minor version is available and RDS has enabled automatic patching for the engine version. Otherwise the change applies as soon as possible without an outage.

PostgreSQL’s own versioning policy is a good argument for leaving it on: the project recommends always running the current minor release and considers minor upgrades less risky than staying on an old one.

What do pending maintenance actions and their dates mean?

DescribePendingMaintenanceActions returns one entry per resource with at least one queued action. The action types are db-upgrade, system-update, os-upgrade, hardware-maintenance, ca-certificate-rotation and serverless-platform-version-update.

Field What the API reference says
AutoAppliedAfterDate Applied during the first maintenance window after this date
ForcedApplyDate Applied as soon as possible on this date, regardless of the maintenance window. There might be a delay of one or more days
CurrentApplyDate The effective date, taking opt-in requests and both dates above into account
OptInStatus Which opt-in request, if any, has been received

ForcedApplyDate is the one to watch: on that date the change can land at 2 p.m. on a Tuesday. The script sorts pending actions by it so the nearest forced date is at the top.

What does the script do?

  1. Reads clusterspaginateDescribeDBClusters flags Aurora and Multi-AZ DB clusters with AutoMinorVersionUpgrade false.
  2. Reads instancespaginateDescribeDBInstances flags standalone instances and Aurora members with the setting off, and prints each one’s PreferredMaintenanceWindow.
  3. Lists pending maintenancepaginateDescribePendingMaintenanceActions returns every queued action with its dates. It only reports them and never calls ApplyPendingMaintenanceAction.
  4. Turns the setting on with --applyModifyDBCluster or ModifyDBInstance with AutoMinorVersionUpgrade: true and ApplyImmediately: false, only for identifiers in --names.

Prerequisites

  • Node.js 18 or later, npm, tsx and @aws-sdk/client-rds.
  • A read-only profile for the report; the guide to AWS SDK v3 credential providers with fromIni and fromSSO explains how profiles are resolved.
  • An agreed maintenance window per environment, so you can tell a bad window from a bad setting.

Which IAM permissions does it need?

rds-maintenance-audit-policy.json

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ReadDatabasesAndMaintenance",
      "Effect": "Allow",
      "Action": [
        "rds:DescribeDBInstances",
        "rds:DescribeDBClusters",
        "rds:DescribePendingMaintenanceActions"
      ],
      "Resource": "*"
    },
    {
      "Sid": "EnableAutoMinorWithApply",
      "Effect": "Allow",
      "Action": [
        "rds:ModifyDBInstance",
        "rds:ModifyDBCluster"
      ],
      "Resource": [
        "arn:aws:rds:*:123456789012:db:*",
        "arn:aws:rds:*:123456789012:cluster:*"
      ]
    }
  ]
}

Remove the second statement for an audit-only role. The free IAM policy generator for TypeScript code builds the list from any variant of the script you write.

The script to check RDS auto minor version upgrade

check-rds-auto-minor-version-upgrade.ts

// check-rds-auto-minor-version-upgrade.ts
// Reports RDS DB instances and DB clusters with auto minor version upgrade turned off, plus every
// pending maintenance action with its dates. Report only unless you pass --apply with --names,
// which turns auto minor version upgrade on for the named instances or clusters.
// Usage:
//   npx tsx check-rds-auto-minor-version-upgrade.ts [--regions us-east-1,eu-west-1] [--names db1,cluster1 --apply]
import {
  ModifyDBClusterCommand,
  ModifyDBInstanceCommand,
  RDSClient,
  paginateDescribeDBClusters,
  paginateDescribeDBInstances,
  paginateDescribePendingMaintenanceActions,
} from "@aws-sdk/client-rds";

const args = process.argv.slice(2);
const flag = (name: string): string | undefined => {
  const i = args.indexOf(name);
  return i >= 0 ? args[i + 1] : undefined;
};
const list = (v: string | undefined) => (v ?? "").split(",").map((s) => s.trim()).filter(Boolean);
const regions = list(flag("--regions") ?? process.env.AWS_REGION ?? "us-east-1");
const names = new Set(list(flag("--names")));
const apply = args.includes("--apply");

interface SettingRow {
  Region: string;
  Kind: "cluster" | "instance" | "member";
  Name: string;
  Engine: string;
  Version: string;
  Window: string;
  AutoMinor: string;
  Result: string;
}

interface PendingRow {
  Region: string;
  Resource: string;
  Action: string;
  Description: string;
  AutoAppliedAfter: string;
  ForcedApply: string;
  CurrentApply: string;
  OptIn: string;
}

const day = (d: Date | undefined) => (d ? d.toISOString().slice(0, 10) : "");

async function scanRegion(region: string, settings: SettingRow[], pending: PendingRow[]): Promise<void> {
  const rds = new RDSClient({ region });
  const found: SettingRow[] = [];

  // Aurora and Multi-AZ DB clusters: the cluster-level setting is the one that counts.
  for await (const page of paginateDescribeDBClusters({ client: rds }, {})) {
    for (const c of page.DBClusters ?? []) {
      if (c.AutoMinorVersionUpgrade !== false) continue;
      found.push({
        Region: region,
        Kind: "cluster",
        Name: c.DBClusterIdentifier ?? "?",
        Engine: c.Engine ?? "?",
        Version: c.EngineVersion ?? "?",
        Window: c.PreferredMaintenanceWindow ?? "",
        AutoMinor: "off",
        Result: "",
      });
    }
  }

  // DB instances. For Aurora, one member with the setting off stops automatic upgrades for the cluster.
  for await (const page of paginateDescribeDBInstances({ client: rds }, {})) {
    for (const db of page.DBInstances ?? []) {
      if (db.AutoMinorVersionUpgrade !== false) continue;
      found.push({
        Region: region,
        Kind: db.DBClusterIdentifier ? "member" : "instance",
        Name: db.DBInstanceIdentifier ?? "?",
        Engine: db.Engine ?? "?",
        Version: db.EngineVersion ?? "?",
        Window: db.PreferredMaintenanceWindow ?? "",
        AutoMinor: db.DBClusterIdentifier ? `off (cluster ${db.DBClusterIdentifier})` : "off",
        Result: "",
      });
    }
  }

  for await (const page of paginateDescribePendingMaintenanceActions({ client: rds }, {})) {
    for (const res of page.PendingMaintenanceActions ?? []) {
      const resource = (res.ResourceIdentifier ?? "?").split(":").slice(-2).join(":"); // db:name or cluster:name
      for (const a of res.PendingMaintenanceActionDetails ?? []) {
        pending.push({
          Region: region,
          Resource: resource,
          Action: a.Action ?? "?",
          Description: (a.Description ?? "").slice(0, 50),
          AutoAppliedAfter: day(a.AutoAppliedAfterDate),
          ForcedApply: day(a.ForcedApplyDate),
          CurrentApply: day(a.CurrentApplyDate),
          OptIn: a.OptInStatus ?? "",
        });
      }
    }
  }

  if (apply) {
    for (const row of found) {
      if (!names.has(row.Name)) {
        row.Result = "skipped (not in --names)";
        continue;
      }
      try {
        // ApplyImmediately false keeps any other queued change waiting for the maintenance window.
        if (row.Kind === "cluster") {
          await rds.send(new ModifyDBClusterCommand({ DBClusterIdentifier: row.Name, AutoMinorVersionUpgrade: true, ApplyImmediately: false }));
        } else {
          await rds.send(new ModifyDBInstanceCommand({ DBInstanceIdentifier: row.Name, AutoMinorVersionUpgrade: true, ApplyImmediately: false }));
        }
        row.Result = "turned on";
      } catch (err) {
        row.Result = `error: ${err instanceof Error ? err.name : String(err)}`;
      }
    }
  }
  settings.push(...found);
}

async function main(): Promise<void> {
  if (apply && names.size === 0) {
    console.error("--apply needs --names with the instance or cluster identifiers to change.");
    process.exit(1);
  }
  const settings: SettingRow[] = [];
  const pending: PendingRow[] = [];
  for (const region of regions) {
    try {
      await scanRegion(region, settings, pending);
    } catch (err) {
      console.error(`${region}: ${err instanceof Error ? `${err.name}: ${err.message}` : String(err)}`);
    }
  }
  console.log(`Auto minor version upgrade turned off: ${settings.length}`);
  if (settings.length) console.table(settings);
  pending.sort((a, b) => (a.ForcedApply || "9999").localeCompare(b.ForcedApply || "9999"));
  console.log(`Pending maintenance actions: ${pending.length}`);
  if (pending.length) console.table(pending);
  if (!apply) console.log("Report only. Pending actions are listed, never applied.");
}

main().catch((err) => {
  console.error(err);
  process.exit(1);
});

How do you run it?

Terminal

npm install @aws-sdk/client-rds
npm install --save-dev tsx typescript @types/node

# Report settings and pending maintenance in two Regions
AWS_PROFILE=readonly npx tsx check-rds-auto-minor-version-upgrade.ts --regions us-east-1,eu-west-1

# Turn the setting on for one cluster and one instance
AWS_PROFILE=rds-admin npx tsx check-rds-auto-minor-version-upgrade.ts --regions us-east-1 --names orders-aurora,billing-mysql --apply

Sample output

Output

Auto minor version upgrade turned off: 3
┌─────────┬─────────────┬────────────┬───────────────────┬─────────────────────┬──────────┬───────────────────────┬───────────────────────────────┬────────┐
│ (index) │ Region      │ Kind       │ Name              │ Engine              │ Version  │ Window                │ AutoMinor                     │ Result │
├─────────┼─────────────┼────────────┼───────────────────┼─────────────────────┼──────────┼───────────────────────┼───────────────────────────────┼────────┤
│ 0       │ 'us-east-1' │ 'cluster'  │ 'orders-aurora'   │ 'aurora-postgresql' │ '16.4'   │ 'sun:05:00-sun:05:30' │ 'off'                         │ ''     │
│ 1       │ 'us-east-1' │ 'member'   │ 'orders-aurora-2' │ 'aurora-postgresql' │ '16.4'   │ 'tue:07:00-tue:07:30' │ 'off (cluster orders-aurora)' │ ''     │
│ 2       │ 'us-east-1' │ 'instance' │ 'billing-mysql'   │ 'mysql'             │ '8.0.39' │ 'wed:04:10-wed:04:40' │ 'off'                         │ ''     │
└─────────┴─────────────┴────────────┴───────────────────┴─────────────────────┴──────────┴───────────────────────┴───────────────────────────────┴────────┘
Pending maintenance actions: 2
┌─────────┬─────────────┬─────────────────────────┬─────────────────┬─────────────────────────────────────────────┬──────────────────┬──────────────┬──────────────┬───────┐
│ (index) │ Region      │ Resource                │ Action          │ Description                                 │ AutoAppliedAfter │ ForcedApply  │ CurrentApply │ OptIn │
├─────────┼─────────────┼─────────────────────────┼─────────────────┼─────────────────────────────────────────────┼──────────────────┼──────────────┼──────────────┼───────┤
│ 0       │ 'us-east-1' │ 'db:users-pg'           │ 'system-update' │ 'New Operating System patch is available'   │ '2027-09-14'     │ '2027-10-12' │ '2027-09-14' │ ''    │
│ 1       │ 'us-east-1' │ 'cluster:orders-aurora' │ 'os-upgrade'    │ 'New Operating System upgrade is available' │ ''               │ ''           │ ''           │ ''    │
└─────────┴─────────────┴─────────────────────────┴─────────────────┴─────────────────────────────────────────────┴──────────────────┴──────────────┴──────────────┴───────┘
Report only. Pending actions are listed, never applied.

Names and dates are illustrative. orders-aurora is off at the cluster level and one member is off too, so turning it on at the cluster is the fix. users-pg has an OS patch that will be applied in the first window after 14 September and forced on 12 October.

What should you do with each finding?

  • Setting off on purpose. Some teams pin versions and upgrade by hand after testing. That’s fine if someone owns it; AWS still forces upgrades for critical security issues and end of support.
  • Setting off by accident. Turn it on with --apply, then fix your infrastructure code (auto_minor_version_upgrade in Terraform, AutoMinorVersionUpgrade in CloudFormation) so the next deploy doesn’t switch it off again.
  • A pending action with a close ForcedApplyDate. Apply it yourself in a quiet window with aws rds apply-pending-maintenance-action --opt-in-type next-maintenance, or immediate if you’re ready now. undo-opt-in cancels a next-maintenance request, but an immediate request can’t be undone.
  • A single-AZ database in the list. Maintenance on a single instance means downtime for the whole database; the script to find RDS instances without automated backups and a review of single-AZ RDS databases in production belong in the same pass.

To be told about upgrades rather than find them, subscribe to RDS events: Aurora announces automatic minor version upgrades in advance with RDS-EVENT-0156. The guide to publish an SNS message with AWS SDK v3 helps if you route those events into your own alerts.

Troubleshooting

  • The cluster is on, but Aurora still isn’t upgrading. One member instance is off. The script lists members separately; turn the setting on at the cluster so every instance follows it.
  • An action you just applied is still listed. AWS documents that DescribePendingMaintenanceActions is eventually consistent. Wait a few minutes and run the script again.
  • InvalidDBInstanceStateFault or InvalidDBClusterStateFault on --apply. The database is busy or stopped; retry when it’s available.
  • AccessDenied. Check Region-level service control policies; the guide to troubleshoot AWS IAM access denied errors walks through it.

Ask ChatWithCloud instead

For a quick answer, ask ChatWithCloud “Which RDS databases have pending maintenance, and when is it forced?” It writes AWS SDK for JavaScript v2 code, runs it on your machine with your AWS profile and summarizes the result; the ChatWithCloud use cases for AWS operations show similar questions. Generated code runs without a confirmation step, so keep a read-only profile for questions like this one.

Frequently asked questions

How do I check if auto minor version upgrade is enabled on RDS?

Run aws rds describe-db-instances --query "DBInstances[].[DBInstanceIdentifier,AutoMinorVersionUpgrade]". For Aurora, also run aws rds describe-db-clusters and check AutoMinorVersionUpgrade on the cluster.

Does RDS auto minor version upgrade cause downtime?

Yes. AWS states that automatic upgrades incur downtime, which depends on the engine and database size. They run in the maintenance window.

Will RDS upgrade my database if auto minor version upgrade is off?

It can. AWS applies a minor version upgrade even with the setting off for critical security issues or when a version reaches its end-of-support date.

What is ForcedApplyDate in RDS pending maintenance?

The date when a pending maintenance action is applied as soon as possible, regardless of the maintenance window. AWS notes there might be a delay of one or more days after it.

Related guides

Ask your AWS account in plain English

Your first 15 runs are free, with no OpenAI key needed.

npx chatwithcloud