Photo by Ian Talmacs on Unsplash
RDS extended support charges apply to RDS for MySQL, RDS for PostgreSQL and Aurora databases still running a major version after its end of standard support. They are billed per vCPU-hour, on standby instances too: $0.100 in years 1 and 2 and $0.200 in year 3 in US East as of September 2026. Upgrading the major version stops them.
RDS enrolls databases in Extended Support automatically. When a major version reaches its end of standard support, every instance still on it keeps running and starts paying a surcharge the next day. For a fleet that includes MySQL 8.0, which left standard support on 31 July 2026, that surcharge is already on the bill.
This example is for engineers who own RDS or Aurora fleets and want a list of every database in or near Extended Support, with dates from the RDS API instead of a spreadsheet and an estimate of the RDS extended support charges each one adds. It only reads. Before you plan upgrades, run the script to find idle RDS instances: a database nobody connects to is cheaper to delete than to upgrade.
How are RDS extended support charges calculated?
The charge is per vCPU per hour for every instance on the old major version, Multi-AZ standbys included. Aurora Serverless v2 is billed per ACU-hour instead. The rate doubles in year 3.
| Capacity | Years 1 and 2 | Year 3 |
|---|---|---|
| RDS for MySQL / PostgreSQL, Aurora provisioned (per vCPU-hour) | $0.100 | $0.200 |
| Aurora Serverless v2 (per ACU-hour) | $0.085 | $0.170 |
US East (N. Virginia) prices as of September 2026, from the AWS Price List API (AmazonRDS offer, ExtendedSupport usage types) and the Amazon RDS for MySQL pricing page. Other Regions differ.
Worked example
A Multi-AZ db.r6g.xlarge (4 vCPUs) on MySQL 8.0 in its first Extended Support year: 4 vCPUs × 2 instances × $0.100 × 730 hours = $584 a month. A single db.t3.medium (2 vCPUs) on MySQL 5.7, which entered year 3 pricing on 1 March 2026: 2 × $0.200 × 730 = $292 a month. Together, $876 a month, or about $10,500 a year, for two databases.
Which versions are in Extended Support?
| Engine and major version | End of standard support | Year 3 pricing from | End of Extended Support |
|---|---|---|---|
| RDS for MySQL 5.7 | 29 Feb 2024 | 1 Mar 2026 | 30 Jun 2029 |
| RDS for MySQL 8.0 | 31 Jul 2026 | 1 Aug 2028 | 31 Jul 2029 |
| RDS for PostgreSQL 12 | 28 Feb 2025 | 1 Mar 2027 | 29 Feb 2028 |
| RDS for PostgreSQL 13 | 28 Feb 2026 | 1 Mar 2028 | 28 Feb 2029 |
| RDS for PostgreSQL 14 | 28 Feb 2027 | 1 Mar 2029 | 28 Feb 2030 |
From the RDS for MySQL and PostgreSQL release calendars as of September 2026. RDS standard support runs at least until the community’s end of life; the PostgreSQL project supports each major version for 5 years, per the PostgreSQL versioning policy. Aurora has its own calendar, and RDS for MariaDB has no Extended Support. The script doesn’t rely on this table: it reads the dates from DescribeDBMajorEngineVersions.
What does the script do?
- Loads lifecycle dates
DescribeDBMajorEngineVersionsreturns, per engine and major version, theopen-source-rds-standard-supportandopen-source-rds-extended-supportperiods with start and end dates. - Reads clusters and instances
DescribeDBClustersgives the Aurora cluster’sEngineLifecycleSupport;DescribeDBInstancesgives each instance’s engine, version, class and Multi-AZ flag. - Resolves the major version
DescribeDBEngineVersionsmaps an engine version such as5.7.44-rds.20260902to itsMajorEngineVersion, so no version strings are parsed by hand. - Counts vCPUs
ec2:DescribeInstanceTypeson the matching EC2 type (db.r6g.xlargetor6g.xlarge) gives the default vCPUs. - Estimates the chargevCPUs × copies × rate × 730 hours, with the year 3 rate from two years after Extended Support started. Serverless v2 instances are listed without an estimate because ACUs vary.
Prerequisites
- Node.js 18 or later, npm and
tsx, plus@aws-sdk/client-rdsand@aws-sdk/client-ec2. - A read-only profile, configured as in the guide to AWS SDK v3 credential providers and named profiles.
- The rate for your Region, if it isn’t US East: pass
--rate-y1and--rate-y3.
Which IAM permissions does it need?
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadRdsLifecycleAndInstances",
"Effect": "Allow",
"Action": [
"rds:DescribeDBMajorEngineVersions",
"rds:DescribeDBEngineVersions",
"rds:DescribeDBInstances",
"rds:DescribeDBClusters",
"ec2:DescribeInstanceTypes"
],
"Resource": "*"
}
]
}
Every action is a describe call. You can confirm the list with the IAM policy generator for TypeScript SDK code.
The script to find RDS databases on Extended Support
// find-rds-databases-on-extended-support.ts
// Report only. Lists RDS for MySQL / PostgreSQL instances and Aurora cluster instances whose major engine
// version is in RDS Extended Support (or reaches it within --days), with lifecycle dates, enrollment setting,
// vCPUs and an estimated monthly Extended Support charge. Nothing is modified.
// Usage:
// npx tsx find-rds-databases-on-extended-support.ts [--regions us-east-1,eu-west-1] [--days 180]
// [--rate-y1 0.100] [--rate-y3 0.200]
import {
RDSClient,
DescribeDBEngineVersionsCommand,
paginateDescribeDBClusters,
paginateDescribeDBInstances,
paginateDescribeDBMajorEngineVersions,
} from "@aws-sdk/client-rds";
import { EC2Client, DescribeInstanceTypesCommand, type _InstanceType } from "@aws-sdk/client-ec2";
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 regions = (flag("--regions") ?? process.env.AWS_REGION ?? "us-east-1")
.split(",")
.map((r) => r.trim())
.filter(Boolean);
const warnDays = Number(flag("--days") ?? "180");
// Per vCPU-hour, US East (N. Virginia), from the AWS Price List API (September 2026). Other Regions differ.
const rateYear1And2 = Number(flag("--rate-y1") ?? "0.100");
const rateYear3 = Number(flag("--rate-y3") ?? "0.200");
const HOURS_PER_MONTH = 730;
const DAY_MS = 86_400_000;
const ENGINES = new Set(["mysql", "postgres", "aurora-mysql", "aurora-postgresql"]);
let estimatedTotal = 0;
interface Lifecycle {
standardEnd?: Date;
extendedStart?: Date;
extendedEnd?: Date;
}
interface Row {
Region: string;
Instance: string;
Engine: string;
Major: string;
Status: string;
StandardEnds: string;
ExtendedEnds: string;
Enrollment: string;
vCPUs: string;
EstPerMonth: string;
}
const day = (d: Date | undefined): string => (d ? d.toISOString().slice(0, 10) : "?");
const errText = (err: unknown): string => (err instanceof Error ? `${err.name}: ${err.message}` : String(err));
// Lifecycle dates per engine and major version, e.g. "postgres|13".
async function lifecycles(rds: RDSClient): Promise<Map<string, Lifecycle>> {
const map = new Map<string, Lifecycle>();
for await (const page of paginateDescribeDBMajorEngineVersions({ client: rds }, {})) {
for (const v of page.DBMajorEngineVersions ?? []) {
const entry: Lifecycle = {};
for (const l of v.SupportedEngineLifecycles ?? []) {
if (l.LifecycleSupportName === "open-source-rds-standard-support") entry.standardEnd = l.LifecycleSupportEndDate;
if (l.LifecycleSupportName === "open-source-rds-extended-support") {
entry.extendedStart = l.LifecycleSupportStartDate;
entry.extendedEnd = l.LifecycleSupportEndDate;
}
}
map.set(`${v.Engine}|${v.MajorEngineVersion}`, entry);
}
}
return map;
}
// Asks RDS which major version an engine version belongs to instead of parsing version strings.
async function majorVersion(rds: RDSClient, cache: Map<string, string>, engine: string, version: string) {
const key = `${engine}|${version}`;
if (!cache.has(key)) {
const out = await rds.send(
new DescribeDBEngineVersionsCommand({ Engine: engine, EngineVersion: version, IncludeAll: true }),
);
cache.set(key, out.DBEngineVersions?.[0]?.MajorEngineVersion ?? "?");
}
return cache.get(key) ?? "?";
}
// vCPUs of a DB instance class, taken from the matching EC2 instance type (db.r6g.large -> r6g.large).
async function vcpus(ec2: EC2Client, cache: Map<string, number | undefined>, dbClass: string) {
if (!cache.has(dbClass)) {
let count: number | undefined;
try {
const out = await ec2.send(
new DescribeInstanceTypesCommand({ InstanceTypes: [dbClass.replace(/^db\./, "") as _InstanceType] }),
);
count = out.InstanceTypes?.[0]?.VCpuInfo?.DefaultVCpus;
} catch {
count = undefined; // RDS-only classes have no EC2 equivalent
}
cache.set(dbClass, count);
}
return cache.get(dbClass);
}
async function scanRegion(region: string): Promise<Row[]> {
const rds = new RDSClient({ region });
const ec2 = new EC2Client({ region });
const dates = await lifecycles(rds);
const majors = new Map<string, string>();
const cpuCache = new Map<string, number | undefined>();
const clusterEnrollment = new Map<string, string>();
for await (const page of paginateDescribeDBClusters({ client: rds }, {})) {
for (const c of page.DBClusters ?? []) {
if (c.DBClusterIdentifier) clusterEnrollment.set(c.DBClusterIdentifier, c.EngineLifecycleSupport ?? "?");
}
}
const rows: Row[] = [];
const now = Date.now();
for await (const page of paginateDescribeDBInstances({ client: rds }, {})) {
for (const db of page.DBInstances ?? []) {
const engine = db.Engine ?? "";
if (!ENGINES.has(engine) || !db.EngineVersion) continue;
const major = await majorVersion(rds, majors, engine, db.EngineVersion);
const life = dates.get(`${engine}|${major}`) ?? {};
const stdEnd = life.standardEnd?.getTime();
const inExtended = stdEnd !== undefined && now > stdEnd;
const daysLeft = stdEnd !== undefined ? Math.ceil((stdEnd - now) / DAY_MS) : undefined;
if (!inExtended && (daysLeft === undefined || daysLeft > warnDays)) continue;
const enrollment = db.DBClusterIdentifier
? clusterEnrollment.get(db.DBClusterIdentifier) ?? "?"
: db.EngineLifecycleSupport ?? "?";
const dbClass = db.DBInstanceClass ?? "?";
let cpuText = "?";
let estimate = "-";
if (dbClass === "db.serverless") {
cpuText = "ACU-based";
if (inExtended) estimate = "per ACU-hour";
} else {
const cpu = await vcpus(ec2, cpuCache, dbClass);
// Multi-AZ instance deployments pay for the standby too; cluster members are listed one by one.
const copies = db.MultiAZ && !db.DBClusterIdentifier ? 2 : 1;
if (cpu !== undefined) {
cpuText = copies === 2 ? `${cpu} x2 (Multi-AZ)` : String(cpu);
if (inExtended) {
// Year 3 pricing starts two years after Extended Support starts.
const year3Start = life.extendedStart ? new Date(life.extendedStart) : undefined;
year3Start?.setUTCFullYear(year3Start.getUTCFullYear() + 2);
const year3 = year3Start ? now >= year3Start.getTime() : false;
const monthly = cpu * copies * (year3 ? rateYear3 : rateYear1And2) * HOURS_PER_MONTH;
estimatedTotal += monthly;
estimate = `$${monthly.toFixed(2)}${year3 ? " (year 3)" : ""}`;
}
}
}
rows.push({
Region: region,
Instance: db.DBInstanceIdentifier ?? "?",
Engine: `${engine} ${db.EngineVersion}`,
Major: major,
Status: inExtended ? "EXTENDED SUPPORT" : `standard ends in ${daysLeft} days`,
StandardEnds: day(life.standardEnd),
ExtendedEnds: day(life.extendedEnd),
Enrollment: enrollment.replace("open-source-rds-", ""),
vCPUs: cpuText,
EstPerMonth: estimate,
});
}
}
return rows;
}
async function main(): Promise<void> {
const rows: Row[] = [];
for (const region of regions) {
try {
rows.push(...(await scanRegion(region)));
} catch (err) {
console.error(`${region}: ${errText(err)}`);
process.exitCode = 1;
}
}
if (rows.length) console.table(rows);
const charged = rows.filter((r) => r.Status === "EXTENDED SUPPORT").length;
console.log(`${charged} instances in Extended Support, ${rows.length - charged} reaching it within ${warnDays} days.`);
console.log(`Estimated Extended Support charge: about $${estimatedTotal.toFixed(2)}/month (vCPU-priced instances only).`);
console.log("Report only: nothing was modified.");
}
main().catch((err) => {
console.error(errText(err));
process.exit(1);
});
How do you run it?
npm install @aws-sdk/client-rds @aws-sdk/client-ec2
npm install --save-dev tsx typescript @types/node
AWS_PROFILE=readonly npx tsx find-rds-databases-on-extended-support.ts --regions us-east-1,eu-west-1 --days 180
Sample output
┌─────────┬─────────────┬───────────────┬─────────────────────────────┬───────┬─────────────────────────────┬──────────────┬──────────────┬────────────────────┬───────────────────┬────────────────────┐
│ (index) │ Region │ Instance │ Engine │ Major │ Status │ StandardEnds │ ExtendedEnds │ Enrollment │ vCPUs │ EstPerMonth │
├─────────┼─────────────┼───────────────┼─────────────────────────────┼───────┼─────────────────────────────┼──────────────┼──────────────┼────────────────────┼───────────────────┼────────────────────┤
│ 0 │ 'us-east-1' │ 'orders-db' │ 'mysql 8.0.46' │ '8.0' │ 'EXTENDED SUPPORT' │ '2026-07-31' │ '2029-07-31' │ 'extended-support' │ '4 x2 (Multi-AZ)' │ '$584.00' │
│ 1 │ 'us-east-1' │ 'legacy-cms' │ 'mysql 5.7.44-rds.20260902' │ '5.7' │ 'EXTENDED SUPPORT' │ '2024-02-29' │ '2029-06-30' │ 'extended-support' │ '2' │ '$292.00 (year 3)' │
│ 2 │ 'us-east-1' │ 'reporting-1' │ 'aurora-postgresql 13.23' │ '13' │ 'EXTENDED SUPPORT' │ '2026-02-28' │ '2029-02-28' │ 'extended-support' │ 'ACU-based' │ 'per ACU-hour' │
│ 3 │ 'eu-west-1' │ 'billing-pg' │ 'postgres 14.24' │ '14' │ 'standard ends in 153 days' │ '2027-02-28' │ '2030-02-28' │ 'extended-support' │ '2' │ '-' │
└─────────┴─────────────┴───────────────┴─────────────────────────────┴───────┴─────────────────────────────┴──────────────┴──────────────┴────────────────────┴───────────────────┴────────────────────┘
3 instances in Extended Support, 1 reaching it within 180 days.
Estimated Extended Support charge: about $876.00/month (vCPU-priced instances only).
Report only: nothing was modified.
Names are illustrative. orders-db is the biggest line because Multi-AZ doubles it. legacy-cms is in year 3, so every month of delay costs twice what it did in 2025. billing-pg isn’t charged yet, but PostgreSQL 14 leaves standard support on 28 February 2027, which gives it about five months to upgrade.
How do you stop the charges?
Upgrading to a major version in standard support stops the charge; so does deleting the database. Charges restart if the new version later enters Extended Support too, so aim for the newest version your application supports.
- Rehearse on a copySnapshot the instance, restore the snapshot to a new instance and upgrade the copy first. Check the RDS automated backup settings before you rely on them for rollback.
- Pick the pathRDS for MySQL upgrades go 5.7 to 8.0 and 8.0 to 8.4. Until Extended Support ends, RDS doesn’t upgrade the major version for you, so you choose the window.
- Upgrade and verifyRun the major version upgrade in a maintenance window, then rerun the script to confirm the instance left the list.
Can you opt out without upgrading?
EngineLifecycleSupport can be set to open-source-rds-extended-support-disabled at create or restore time, and on an existing instance or cluster with modify-db-instance or modify-db-cluster. On a database that’s already past its standard support date, that triggers an automatic upgrade to the next supported major version, so treat it as an upgrade you don’t control. For Aurora, set it on the cluster.
Warning: a restored snapshot of an old version is enrolled in Extended Support by default. Restoring test copies of legacy-cms for an afternoon is fine; leaving them running isn’t. Clean up with the script to delete old RDS manual snapshots once the upgrade is done.
Troubleshooting
vCPUsshows?. The instance class has no EC2 equivalent thatDescribeInstanceTypesrecognizes. Look up its vCPUs in the RDS instance class table and multiply by hand.- An Aurora instance isn’t listed. Aurora dates come from the Aurora calendar, which can differ from RDS for the same engine version. Check that
DescribeDBMajorEngineVersionsreturns theaurora-mysqloraurora-postgresqlversion. - The estimate differs from the bill. The script uses 730 hours and default vCPUs. Customized processor features and Regional rates change the real figure; compare with the billing breakdown by service for last month.
An upgrade window is also a good time to fix other settings: run the scripts to find unencrypted RDS instances and Aurora clusters and find RDS instances without deletion protection. EKS has the same kind of surcharge for old Kubernetes versions; the script to find EKS clusters on extended support covers it. If the upgrade retires a database instead, check whether a proxy still points at it with the script to find unused RDS proxies that bill with no connections.
Ask ChatWithCloud instead
For a quick check in one Region, ask ChatWithCloud “Which RDS instances in us-east-1 run MySQL 5.7 or 8.0?” It writes AWS SDK for JavaScript v2 code, runs it with your profile on your machine and explains the result, one profile and Region per session. SDK v2 reached end of support in September 2025, so newer fields such as lifecycle dates may be missing from its answers; how ChatWithCloud turns questions into AWS SDK calls covers the loop and its limits. Because it runs changes without asking, connect ChatWithCloud to a read-only profile first.
Frequently asked questions
How much are RDS extended support charges?
In US East as of September 2026, $0.100 per vCPU-hour in years 1 and 2 and $0.200 in year 3, for every instance including Multi-AZ standbys. A 2-vCPU instance costs about $146 a month in year 1.
When do RDS Extended Support charges start?
The day after the major version’s RDS end of standard support date. They stop when you upgrade to a version in standard support or delete the database.
Does Aurora have Extended Support charges too?
Yes, for Aurora MySQL and Aurora PostgreSQL versions past standard support: per vCPU-hour on provisioned instances and per ACU-hour on Aurora Serverless v2.
Can I turn off RDS Extended Support on an existing database?
Yes, by setting EngineLifecycleSupport to open-source-rds-extended-support-disabled. If the database is already past its standard support date, RDS then upgrades it to the next supported major version automatically.
Related guides
Ask your AWS account in plain English
Your first 15 runs are free, with no OpenAI key needed.
npx chatwithcloud