Magento 2.4.9 Upgrade Guide
I upgraded my own Magento store from 2.4.8-p3 to 2.4.9 on 2026-05-13. Hit five distinct problems, fixed them all, and shipped clean. Here’s the exact playbook, every command, every trap, every workaround, so you can do the same upgrade without the surprises.
- Upgrade date 2026-05-13
- Time spent ~3 hours (with 5 hiccups)
- Backup first Mandatory, full DB + media + env.php
- Issues hit 5 distinct (all fixable)
- Outcome All pages 200 · admin works · indexers clean
- Rollback time ~3 minutes if needed
Is this upgrade safe for you?
Four common situations, four honest verdicts. Find the card that matches your store.
Coming from 2.4.8-p1 / p2 / p3
A 1-2 hour upgrade for a clean store. 1-2 weeks if you carry a long tail of 3rd-party modules. Most stores can ship this weekend, book a 30-minute maintenance window, take the backups, follow the 7 steps below.
Coming from 2.4.6 or 2.4.7
4-8 weeks. You jump multiple PHP versions and three infra changes in one go (Redis → Valkey, OpenSearch 2 → 3, Symfony 6 → 7.4). Don’t rush. Audit every paid extension first, then plan a hosting migration window, then run the Magento upgrade.
Hyvä Themes or Adobe Commerce B2B
Hyvä works on 2.4.9, re-test your Hyvä Checkout adapter for the Braintree changes (Google Pay vaulting, Apple Pay on Chrome and Firefox). Adobe Commerce B2B needs a full smoke test of Companies, quotes, requisition lists, and shared catalogues.
Mid-peak-season trading
Don’t. Defer until after BFCM, Diwali, or your Q4. Adobe ships security patches for 2.4.8 until July 2027, you can wait safely. The 5 minutes you save in the calendar are not worth a checkout outage on Black Friday.
The 5 traps you WILL hit
Universal: not vendor-specific. These five problems show up on every 2.4.9 upgrade I have done. Every technical term has a human-readable explainer next to it.
01composer require errors out on the metapackage
Adobe’s magento/product-community-edition is a metapackage. Since Composer 2.1.6, composer require magento/product-community-edition=2.4.9 rejects it with “Use require-commerce instead.” The fix: Adobe ships magento/composer-root-update-plugin (bundled with 2.4.4+). Use composer require-commerce instead. What it means: the regular composer command can’t handle Magento’s root-package update logic, Adobe gives you a custom command for it.
02PHP 8.4 deprecates implicit-nullable parameters, and Magento turns the deprecation into a fatal
Any 3rd-party module written before PHP 8.4 that uses Type $param = null (without the? prefix) emits “Deprecated Functionality: Implicitly marking parameter $X as nullable is deprecated.” Magento’s framework error handler upgrades the deprecation into a thrown exception in developer mode, so every bin/magento command crashes. What it means: PHP 8.4 wants ?Type $param = null (explicit nullable). The old Type $param = null style is a syntax error in disguise. Detection: grep the vendor/ tree for the pattern, excluding vendor/magento/ (Adobe ships clean code).
03Symfony 7.4 enforces: int return on Command::execute(), old custom commands break at autoload
Magento 2.4.9 ships Symfony 7.4 LTS (up from Symfony 6 in 2.4.8). Symfony 7’s parent Command::execute() is strict-typed with: int. Any custom CLI command (bin/magento your:command) that declared execute() with no return type or: void fails LSP at class declaration. The fatal happens BEFORE Magento checks if the module is enabled, so disabling in app/etc/config.php doesn’t help. Fix: add: int to the method and return Command::SUCCESS; (which is just 0) on all return paths.
04system.xml no longer accepts inline HTML inside <comment>
2.4.9 tightened the XSD validation for etc/adminhtml/system.xml. Earlier versions accepted <comment>Example: <code>foo</code></comment>, i.e., inline HTML tags inside <comment>. As of 2.4.9 only plain text or CDATA is allowed: <comment><![CDATA[Example: <code>foo</code>]]></comment>. Symptom: setup:upgrade aborts with “Element ‘code’: This element is not expected. Expected is (model).” Wrap any inline HTML in CDATA.
05Disabling a module in app/etc/config.php doesn’t skip PHP’s autoload of its classes
When you set 'YourVendor_Module' => 0 and the module’s class is referenced anywhere (composer classmap, factory references, GraphQL schema), PHP’s autoloader still loads the class, and any PHP 8.4 deprecation inside that class still fires. The enable-flag check happens at Magento bootstrap, after PHP class loading. So if a module’s class is broken at parse time, the only way to skip it is composer remove. What it means: config.php controls Magento behavior; it doesn’t control PHP itself.
Step-by-step upgrade: 7 steps
Same pattern I ran on 2026-05-13. Generic: no Docker assumptions, no project-specific helpers. Copy / paste each block as-is, swap the placeholders for your values.
01Take a full backup
DB dump (with routines + triggers), pub/media tar.gz, app/etc/env.php (gitignored, contains the crypt key!), composer.json + composer.lock. Store the tar.gz somewhere OFF this server. What to expect: Takes 2-10 minutes depending on store size. The crypt key in env.php is non-negotiable, without it the DB is gibberish.
02Bump PHP to 8.4 or 8.5
2.4.9 minimum is PHP 8.4 (PHP 8.3 is dropped). On bare LAMP/LEMP, install the new PHP-FPM and the Magento-required extensions, then point your FPM pool + nginx fastcgi_pass at it. On Docker, bump the base image tag. What to expect: ~10-20 minutes including FPM pool config. Run php -v from inside the web user’s shell to confirm.
03Bump infra services (or document they’re below spec)
Adobe’s 2.4.9 supported matrix: MySQL 8.4 / MariaDB 11.8 / OpenSearch 3 / Valkey 9 (Redis 7 still works for back-compat). Production should match spec. Local dev usually works on older versions because Magento’s drivers adapt at connection time. What to expect: Plan service migrations BEFORE the Magento upgrade if production. On a clean dev box, you can skip this and patch later.
04Run composer require-commerce then composer update
Don’t use plain composer require for Magento metapackages, it errors out. Use Adobe’s require-commerce command (added by the magento/composer-root-update-plugin that ships with 2.4.4+). What to expect: 3-8 minutes. Pulls ~40 magento/* dependency bumps + Symfony 7.4. If your composer.json has version pins, you may have to relax them first.
05Run bin/magento setup:upgrade --keep-generated
This is where 3rd-party module breakages will surface. Read the FULL output (errors are interleaved with module names). If a module crashes here, jump to the composer-patches recipe below, or temporarily composer remove to keep moving. What to expect: Usually 30-90 seconds on a small store. If it aborts, read the LAST traceback line, that’s the module to patch or remove.
06Apply patches for any broken 3rd-party modules
Don’t edit vendor/ files directly, they get overwritten on the next composer install. Use cweagans/composer-patches to ship reversible 3-line patches. Full recipe in the next section. What to expect: 5-30 minutes per broken module, mostly writing the diff. When the vendor releases a fix, you drop the patch entry from composer.json.
07Smoke test
Flush cache, reindex, regenerate sitemaps (if you have a SEO module), curl 10 representative pages, test admin login, run a checkout if you can. Watch var/log/exception.log + var/log/system.log for fresh entries. What to expect: 15-30 minutes of click-testing. Look for new entries in the log files in the last 10 minutes, not old ones.
The composer-patches pattern
For unmaintained 3rd-party modules. Editing vendor/ directly is forbidden: the changes get overwritten on the next composer install. The right answer is a versioned patch file in your repo that re-applies on every install.
Why this matters: some 3rd-party vendors don’t ship PHP 8.4 fixes promptly. cweagans/composer-patches lets you ship a reversible 3-line patch that lives in your repo, applies on every composer install, and drops cleanly when the upstream vendor fixes the bug.
01Install the plugin
Adobe’s own magento/quality-patches uses this same plugin, it’s the standard Magento pattern.
02Generate the patch with diff -u
Hand-written patches go wrong at hunk 3+. Always generate via diff -u from clean copies. Keep the path inside the patch relative to the package root.
03Register in composer.json
Map the patch to the package name (not the path). One package can have many patches.
04Apply
v2.0+ of the plugin uses patches-relock + patches-repatch commands (NOT auto-apply on install). When the upstream vendor ships a fix, remove the entry and composer update to drop the patch cleanly.
Backup recipe: generic mysqldump pattern
Four files. Plain bash, no Docker assumptions. Run before you touch anything else, and test the restore on a throwaway box if you have never run a Magento DB restore before.
Database dump
Run on the host where MySQL is reachable. --single-transaction avoids table locks on InnoDB; --routines --triggers includes stored procs and triggers. Why it matters: The catalog, customers, orders, product attributes, CMS pages all live here. Lose this = total restart.
env.php, has the crypt key
env.php holds the crypt key, DB credentials, queue config, cache config. Magento can’t decrypt anything in the DB without the same key. Why it matters: Without the same crypt key, Magento sees customer addresses, admin passwords, and payment data as encrypted gibberish. Lose this = unrecoverable.
Media archive
Product images, page banners, CMS uploads. Skip this if your media is on object storage (S3, Cloudflare R2, GCS) and already versioned. Why it matters: Most of the disk weight. Restoring from a stale media set on rollback is fine because product images rarely change in a 3-hour upgrade window.
Composer files
The exact state of every installed package. Restoring lets you deterministically revert PHP-level changes, not just the Magento ones. Why it matters: Lets you reinstall the exact pre-upgrade set of vendor packages on rollback, including the same patch versions of every 3rd-party extension.
Rollback in ~3 minutes
If the upgrade goes sideways. Plain bash + mysql + composer commands. Test this BEFORE you start the upgrade: proof that you can get back.
Run this sequence with the timestamped backup files from section 6. Total time: about 3 minutes once everything is staged.
What it means: rollback is ~3 minutes once everything is staged. Test the rollback recipe BEFORE you start the upgrade, treat it as proof that you can get back, not as an emergency drill.
Real findings from my 2026-05-13 upgrade
All five traps from section 3 hit my own upgrade. Short version of how each one shook out.
Trap 1 (composer require rejection) hit immediately. Composer surfaced the error in 2 seconds; the fix was switching to composer require-commerce. Cost: 30 seconds of reading the error.
Trap 2 (PHP 8.4 implicit-nullable) hit on 2 separate 3rd-party modules. Both maintainers shipped fixes within hours of opening a bug report. In the meantime I held the upgrade on a feature branch and patched locally with cweagans/composer-patches. Cost: ~25 minutes per module, mostly waiting for the maintainer.
Trap 3 (Symfony 7.4 Command::execute return type) hit on 1 admin CLI module, same maintainer, same fast turnaround. Same composer-patches workaround used in the interim.
Trap 4 (system.xml XSD tightening) hit on 1 module that shipped HTML in . Wrapped in CDATA, opened a pull request upstream, moved on. Cost: 5 minutes.
Trap 5 (config.php disable doesn’t help), I lost ~10 minutes trying to disable a module via app/etc/config.php before realising composer remove is the only escape valve when a class fails at parse time. That’s the lesson worth remembering: config.php controls Magento behavior; it doesn’t control PHP itself.
Each trap was a 5-30 minute fix once diagnosed. The full upgrade took ~3 hours total with backup + verification + the 5 hiccups. On a cleaner store (no 3rd-party modules), I’d expect this to be under an hour.
Audit YOUR 3rd-party modules before upgrading
Three grep snippets. Run them BEFORE you start the upgrade: they flag the same patterns Magento 2.4.9 will fail on, so you can fix them or open vendor bugs in advance.
What it means: run these three greps BEFORE you start the upgrade. They flag the same patterns Adobe Commerce 2.4.9 will fail on. Fix them or open vendor bugs in advance: much cheaper than diagnosing during a live cutover.
PHP 8.4 implicit-nullable problems
Finds the Type $param = null pattern (without ?) inside every vendor/ module except Adobe’s own. These are the modules that will fatal at autoload on 2.4.9.
Symfony 7.4 Command::execute() return-type problems
Finds custom CLI commands that declared execute() without: int. These break at class load on Symfony 7.4, before Magento even checks if the module is enabled.
system.xml <comment> with inline HTML
Finds <comment> elements that contain HTML tags, rejected by the 2.4.9 XSD. Wrap them in CDATA before upgrading.
Adobe supported matrix vs dev-environment reality
What Adobe says you need (left) vs what actually worked on my dev box (right). Production should still match the supported matrix: Adobe only QA-tests against documented configs.
What it means: Magento’s drivers detect server versions at connection time and adapt. Local dev is forgiving, you can upgrade Magento first and bump services later. Production should still match the supported matrix because Adobe’s QA only verifies against documented configs, and your hosting provider’s support contract assumes it too.
| Component | Adobe supported (2.4.9) | What I actually ran |
|---|---|---|
| PHP | 8.4 / 8.5 | 8.4 minimum is enforced, can’t skip |
| MySQL / MariaDB | MySQL 8.4 / MariaDB 11.8 / 12.3 | Upgrade worked on MySQL 8.0 (drivers adapt) |
| Search engine | OpenSearch 3 | Upgrade worked on OpenSearch 2.12 (re-test queries) |
| Cache layer | Valkey 9 (Redis 7 deprecated) | Redis 7 still works, same wire protocol |
| Message broker | RabbitMQ 4.2 | RabbitMQ 3.13 connects fine for now |
| Composer | 2.9.3+ | Older Composer prints warnings; require-commerce still works |
| Web server | nginx 1.28 | nginx 1.24 serves Magento without complaint |
Magento 2.4.9 upgrade: frequently asked questions
How long does a 2.4.9 upgrade take?
Do I need to upgrade if I am on 2.4.8-p3?
What is the cost?
Can I roll back if something breaks?
Will my Hyvä storefront work?
Can I keep PHP 8.3?
Do I have to migrate Redis to Valkey?
What if my paid extension does not support 2.4.9 yet?
Is composer require-commerce different from composer require?
Should I run setup:di:compile and setup:static-content:deploy?
What about Adobe Commerce versus Magento Open Source?
Do I need a maintenance window?
Further reading
Five neighbour pages most readers want next: the full release notes, the upgrade service, the calculator, plus the Redis / Hyvä deep-dives.
- Magento 2.4.9 release notesFull feature + spec breakdown for 2.4.9. PHP 8.5, OpenSearch 3, Valkey, Symfony 7.4 LTS, 581 bug fixes, each one with a human-readable explainer.
- Magento upgrade serviceFixed-price 2.4.x upgrades with a 24-hour quote. Audit, plan, build, QA, cutover, one developer, one invoice.
- Upgrade cost calculator10-input tool.
- What is Redis cache for Magento?Covers the Redis → Valkey shift, three-DB separation (cache / FPC / sessions), memory sizing, and Sentinel clustering.
- Hyvä theme developmentHyvä 2.4.9-compat audits + migration. Tailwind + Alpine storefront, sub-1s TTFB, Lighthouse 95+.
Want this done for you?
I just shipped a 2.4.9 upgrade on my own store. The exact playbook is on this page; the experience is yours for the asking. Fixed-price quotes in 24 hours.