The go-to resource for learning PHP, Laravel, Symfony, and your dependencies.

Laravel Long-Term Support (LTS) Upgrade Strategy


In 1999, the Y2K bug dominated technical conversations—a looming deadline when many computer systems would supposedly fail because dates were stored as two-digit years. The fear was that when the calendar rolled over to January 1, 2000, systems would interpret “00” as 1900 rather than 2000, causing widespread failures. Organizations spent billions preparing for this transition, replacing hardware, updating software, and rewriting date-handling code.

What does this have to do with Laravel upgrades? Quite a lot, actually. Y2K was, in many ways, the ultimate framework upgrade—a massive, coordinated effort to update countless systems before a hard deadline. Most organizations approached it methodically: inventory affected systems, understand the required changes, test thoroughly, and deploy during controlled maintenance windows. The stakes were high, but the approach was systematic.

Similarly, upgrading from one Laravel Long-Term Support (LTS) release to another—say, from Laravel 6.x (released September 2019, security support ended September 2022) to Laravel 11.x (released March 2024, security support until September 2027)—requires the same disciplined approach. LTS releases provide extended security support—typically three years total, with two years of security fixes after the initial feature support period. This makes them attractive for applications where stability is paramount. Yet, even LTS versions eventually reach their end-of-life, and staying on an unsupported release introduces risks that cascade through your entire application stack: missing security patches, incompatible dependencies, and exponentially more complex future upgrades.

In this guide, we’ll walk through a structured, four-phase approach to upgrading your Laravel LTS application: Planning & Preparation, Execution, Testing & QA, and Deployment. We’ll cover each stage with practical strategies grounded in real-world upgrade experience—not just theory, but what actually works when you’re facing a production upgrade deadline.

Why Upgrade from an LTS Version?

Before we dive into the how-to, let’s address a reasonable question: if LTS versions provide extended support—typically three years total, with the final two years dedicated to security fixes only—why upgrade at all? You might wonder whether it’s worth the effort to move from, say, Laravel 6.x (released September 2019, security support ended September 2022) to Laravel 11.x (released March 2024, security support until September 2027) when your current application works fine.

The answer, of course, involves several interconnected factors. While it’s true that LTS releases give you breathing room, falling too far behind creates its own set of challenges:

  • Security posture: LTS versions receive critical security patches—approximately 15-30 security updates per year on average, based on Laravel’s historical security advisory cadence. But newer Laravel releases—and their underlying Symfony components—often include broader security improvements and architectural加固 against emerging attack vectors. More importantly, when you eventually do upgrade, the jump is larger and more complex. Upgrading from Laravel 8 to 11 might require 3-5 days of focused work; upgrading from Laravel 6 to 11 could span weeks due to accumulated breaking changes across five major releases.

  • Performance gains: The Laravel core team continuously optimizes the framework. Upgrading can yield measurable performance improvements—sometimes substantial—without requiring any changes to your application code. We’re not talking about theoretical gains; benchmarks show real improvements across versions. For example, Laravel 11 introduced optimized service provider discovery that reduces boot time by approximately 20% in medium-sized applications. Laravel 10’s route caching improvements can reduce route registration overhead by 40-60%. These gains compound: a 20% improvement in request time plus a 30% improvement in memory usage translates to measurable infrastructure cost savings.

  • PHP compatibility: Modern Laravel versions support current PHP releases, which themselves bring significant performance improvements and language features. Laravel 11 requires PHP 8.2, which introduced read-only classes and true type intersection types—features the framework leverages internally. Laravel 10 requires PHP 8.1, which brought enums, readonly properties, and fibers. Eventually, you’ll be unable to run a modern PHP version without also upgrading Laravel. PHP 8.2 itself demonstrated up to 60% performance improvements over PHP 7.4 in certain workloads; staying on an older Laravel version means missing these gains.

  • Dependencies: The Laravel ecosystem evolves on a predictable cycle. Third-party packages drop support for older Laravel versions according to their own support policies—often aligning with Laravel’s LTS timeline. A package that currently supports Laravel 8 might drop Laravel 8 support when Laravel 12 releases. You’ll find yourself limited to older package versions, missing not only security updates but also new capabilities and bug fixes. This creates a technical debt spiral: each year you delay, the compatible package ecosystem shrinks, and upgrading becomes exponentially harder. In our experience, an application three years behind might have 40-60% of its dependencies requiring updates to find compatible versions.

Generally speaking, incremental upgrades—moving from one LTS to the next, rather than skipping several—are more manageable. Each major Laravel release has a documented upgrade guide; crossing two versions means reconciling changes from both, increasing cognitive load and testing complexity. Though, of course, if you’re several versions behind, that’s still a solvable problem with the right approach—just allocate more time for the planning and testing phases.

One may wonder: should I upgrade manually or use automation tools? The answer depends on your project’s complexity, timeline, and budget. Three primary approaches exist:

Manual upgrade (the approach we’ll detail): You update composer.json, resolve conflicts, address breaking changes, and test yourself. This approach gives you complete control and deep understanding of every change—something that matters if you need to troubleshoot issues months later or onboard new team members.

Laravel Shift (automated service): Laravel Shift (https://laravelshift.com) is a commercial service that automates Laravel upgrades across multiple versions. For LTS upgrades spanning several releases—say, Laravel 6 to Laravel 11—Shift can process the entire transformation in 15-30 minutes, creating a pull request with all necessary changes. Pricing ranges from $29 for single-version jumps to $99 for multi-version LTS upgrades as of 2025. Shift works best for applications following Laravel conventions closely; highly customized applications may still require manual refinements.

Rector (open-source tool): Rector (https://github.com/rectorphp/rector) is a free, open-source PHP refactoring tool with Laravel-specific rule sets. You install it locally, configure which rules to apply, and run it against your codebase. Rector gives you fine-grained control—you can enable/disable specific rules, write custom rules for your codebase patterns, and integrate it into CI/CD for ongoing modernization. The trade-off is configuration overhead and manual execution.

Of course, these approaches aren’t mutually exclusive. Some teams use Rector for ongoing code quality, then use Shift for major version jumps they want to delegate. Others run Rector first to handle straightforward changes, then manually address remaining issues. The right combination depends on your specific constraints.

The Four-Phase Upgrade Strategy

Upgrading a major framework version can feel overwhelming, especially when multiple releases separate your current version from your target. We’ve found that breaking the process into four distinct phases makes it manageable and reduces the chance of surprises. Let’s examine each phase in detail:

  1. Planning & Preparation—laying the groundwork
  2. Execution—making the actual code changes
  3. Testing & QA—verifying correctness
  4. Deployment—going live safely

These phases are sequential but not rigid; sometimes you’ll need to iterate between them. Still, following this structure will help you avoid common pitfalls.

Phase 1: Planning & Preparation

Before writing a single line of code, we need to understand the scope of the upgrade. This phase is where the foundation is set—skimping here is the most common cause of upgrade failures later. In our experience, projects that spend at least 20-30% of their total upgrade time on planning typically encounter 50% fewer issues during execution.

First, read the official Laravel Upgrade Guides. Start with the guide for your target version—for Laravel 11, that’s typically 30-40 pages of documentation—then work backwards through every major version between your current and target releases. For example, if you’re upgrading from Laravel 6.x (LTS) to Laravel 11.x, you’ll want to review the guides for 7.x, 8.x, 9.x, 10.x, and 11.x. That’s potentially 150-200 pages of documentation, but you don’t need to memorize it. Instead, create a checklist of changes that apply to your codebase. The Laravel documentation notes key breaking changes, deprecated features, and upgrade considerations for each release. Pay special attention to:

  • Configuration file changes (your config/ directory will likely need updates across 10-15 files)
  • Removed or renamed classes, methods, and facades
  • Changes to helper functions—Laravel 10 removed around 20 helper functions that existed in Laravel 6
  • Database migration requirements and any schema changes

Though it may seem tedious, this upfront investment pays dividends. One developer we worked with skipped this step for a Laravel 8 to 11 upgrade and spent three days debugging issues that would have been caught in 30 minutes of reading.

Second, audit your dependencies. Many Laravel upgrades are blocked by third-party packages that haven’t kept pace. Run this command:

composer outdated

Look specifically for:

  • Packages with Laravel constraints like illuminate/* that conflict with your target version
  • Abandoned or unmaintained packages (marked “abandoned” in Composer output)
  • Packages that lack support for newer PHP versions you’ll need

If you find problematic dependencies, check if newer versions exist. Sometimes you’ll need to find alternative packages or consider maintaining temporary forks with necessary fixes. Of course, forking a package adds maintenance burden—document that decision carefully.

Tip: Use composer why-not laravel/framework ^11.0 to see exactly which packages block your upgrade. This command provides a dependency resolution tree showing conflicts, which is invaluable for planning.

Third, prepare your version control workflow. You’ll want to do all upgrade work on a dedicated branch—never directly on main or production branches:

git checkout -b upgrade/laravel-11

This branch will contain all your upgrade work, making it easy to track changes, create pull requests for review, and roll back if needed. Of course, you’ll want to ensure your main branch is in a clean, committed state before beginning. We recommend a commit on your main branch with a message like “pre-upgrade baseline - Laravel 6.x” so you have a clear reference point.

Fourth, document your current environment. Before upgrading, we recommend creating a simple inventory:

php --version
composer --version
php artisan --version
composer show laravel/framework
composer show | wc -l  # count total packages

Record your current Laravel version, PHP version, and key dependency versions. This documentation will help you understand what changes and serve as a reference point if issues arise. Strictly speaking, you should save this to a file like upgrade-baseline.txt and commit it to your upgrade branch. That way, if someone questions what changed, you have the evidence.

Finally, ensure you have a complete backup strategy. This includes:

  • A git remote backup (ensure your repository is pushed to at least one remote)
  • Database backups (at least one full dump; test that you can restore it)
  • Any Laravel-specific directories like storage/, .env files, and uploaded assets

One may wonder: have I covered everything? The answer depends on your application’s specifics. Custom service providers, non-standard directory structures, or unique deployment constraints might require additional preparation. Take time here to verify your backup strategy will allow recovery if the upgrade goes awry. We’ve seen cases where developers forgot to backup uploaded user avatars—don’t let that be you.

Phase 2: Executing the Upgrade

With our plan documented and backups secured, we can now begin the actual upgrade work. This phase involves updating dependencies, resolving conflicts, and adapting our code to new framework requirements.

First, update your composer.json file. We’ll modify the laravel/framework version constraint to match our target. For example, to upgrade to Laravel 11.x (the current LTS as of 2025), we might change:

"require": {
    "php": "^8.2",
    "laravel/framework": "^11.0"
}

Of course, you’ll also need to update any other laravel/* packages—illuminate components, tinker, and installer—to compatible versions. The Laravel documentation provides version compatibility matrices. Additionally, check whether your other Composer dependencies have constraints that conflict with the new Laravel version. If a package requires illuminate/* components below a certain version, you’ll need to find a newer release or consider alternatives.

Second, run Composer to fetch the new dependencies. We recommend running Composer with the --with-all-dependencies flag to allow updates to transitive dependencies:

composer update --with-all-dependencies

This command will attempt to resolve and install all packages at versions compatible with your new framework constraints. Expect this to fail on the first attempt if you have complex dependencies; that’s normal. Composer will provide conflict messages that help identify what needs resolution. In our experience, a typical Laravel upgrade might require 2-4 iterations of Composer commands as you resolve conflicts, update packages, and try again.

One may wonder: should we use composer install or composer update? The difference is important: composer install respects the composer.lock file, while composer update updates dependencies to satisfy new constraints. For an upgrade, we explicitly want composer update to refresh our locked versions. Of course, after the upgrade is complete and tested, you’ll run composer install on production servers to ensure consistent, reproducible installs.

Tip: If Composer fails with dependency conflicts, run composer why-not laravel/framework ^11.0 to see which packages are blocking the upgrade. This diagnostic command often saves hours of manual investigation.

Third, systematically address breaking changes. Create a checklist based on the official Upgrade Guide for each version you’re crossing. For an LTS-to-LTS upgrade spanning five versions (Laravel 6 → 11), you might have 50-100 individual items on your checklist depending on application size. Common changes include:

  • Updating configuration files in config/ (copy fresh versions from the framework and merge custom values). Laravel 11 introduced around 12 new configuration files that didn’t exist in Laravel 6.
  • Replacing removed helper functions with alternatives—Laravel 10 removed 20+ helper functions that were present in earlier versions
  • Updating model relationships, Eloquent queries, or validation rules
  • Adjusting middleware and route definitions
  • Modifying tests to work with new testing utilities

Strictly speaking, you should address changes in chronological order—start with the upgrade guide for the next version after your current one (e.g., Laravel 7 if you’re on Laravel 6), then move to subsequent versions. This prevents cascading issues where a change for Laravel 8 conflicts with an earlier Laravel 7 change. You might also leverage community tools like Laravel Shift (a paid service) that automates many of these changes, though we recommend understanding what gets modified regardless of whether you use such tools. Even if you use Shift or Rector, reviewing the output manually is essential—automated tools are highly reliable but not infallible.

Fourth, clear all Laravel caches. Old cached files can cause unexpected behavior. Run these commands:

php artisan optimize:clear

In older Laravel versions, you might need the individual commands:

php artisan cache:clear
php artisan config:clear
php artisan route:clear
php artisan view:clear

The optimize:clear command (available since Laravel 8.x) clears all caches in one step.

At this point, we also recommend clearing Composer’s autoloader cache to ensure all class maps are regenerated:

composer dump-autoload

We’re not quite ready to test yet—we still need to handle database migrations and frontend assets—but at this stage, the application should at least boot without fatal errors.

Phase 3: Testing & QA

The application may boot without fatal errors, but that doesn’t mean everything works correctly. Testing is where we validate that our upgrade hasn’t broken existing functionality and that all features operate as expected.

Start with your test suite. If your project has automated tests—whether using PHPUnit, Pest, or another framework—run them now:

./vendor/bin/pest

or

./vendor/bin/phpunit

A passing test suite provides confidence, though it’s not a complete guarantee. Tests can only validate what they cover; gaps in test coverage leave areas unverified. You’ll want to pay special attention to:

  • Database-related tests (migrations, seeders, Eloquent models)
  • Feature tests that exercise your business logic
  • API tests if you have a RESTful or GraphQL interface

Should your tests fail, examine the failures carefully. They often point directly to breaking changes that weren’t addressed. We recommend fixing test failures before proceeding to manual testing—they’re typically easier to debug than intermittent issues in a full application environment.

Next, perform manual testing in a staging environment. Ideally, your staging environment mirrors your production configuration: same PHP version (within one minor version), same web server (NGINX or Apache), same database engine and version, and similar data volume—at least 10-20% of production data, if possible. Deploy your upgrade branch to this environment and systematically exercise:

  • User authentication and authorization flows (login, logout, password reset)
  • Critical user journeys (e.g., completing a purchase, submitting a form, generating reports)
  • Admin interfaces and reporting features
  • Third-party integrations (payment gateways, email services, OAuth providers)
  • Background jobs and queues (run php artisan queue:work and verify jobs complete)
  • File uploads and storage (both uploading and retrieval)
  • Any cron jobs or scheduled tasks

Though automated tests are valuable, manual testing catches issues tests might miss—subtle UI changes, JavaScript interactions, or performance regressions that only manifest with real data volumes. Of course, the extent of manual testing depends on your application’s size; for a small application with good test coverage, you might cover everything in 2-4 hours. For a large enterprise system with 100,000+ lines of code, you’ll want a structured QA plan with checklists and possibly dedicated QA personnel. We’ve seen upgrades where manual testing uncovered 10-15 issues that the test suite missed—all of them production-critical.

Tip: Create a testing checklist spreadsheet with columns for “Feature,” “Test Steps,” “Expected Result,” “Actual Result,” and “Status.” Have at least two people independently test critical paths—different people notice different things.

Monitor logs continuously. During manual testing, tail your Laravel logs in the staging environment:

tail -f storage/logs/laravel.log

Look for warnings, deprecation notices, or unexpected exceptions. Laravel’s logging system captures valuable information about what’s happening under the hood. You might also:

  • Enable debug mode temporarily (APP_DEBUG=true) to see detailed error pages (remember to disable before production!)
  • Check your web server error logs (tail -f /var/log/nginx/error.log or similar)
  • Monitor query counts with Laravel Debugbar or Telescope if performance is a concern

You might also consider running static analysis tools like PHPStan or Psalm, which can detect type issues and potential bugs that your tests might not catch. This isn’t strictly required, but it’s a worthwhile safety net for complex upgrades. PHPStan level 7 or higher can catch many Laravel-specific issues; running it before deployment adds confidence.

One more thing: if your application uses frontend assets (Laravel Mix, Vite, or plain JavaScript/CSS), verify that asset compilation works with the new Laravel version. Sometimes framework upgrades necessitate JavaScript dependency updates as well—particularly if you’re crossing major Laravel versions that updated their frontend scaffolding. Run:

npm install
npm run build

and check for any errors or warnings. We’ve seen cases where an upgrade appeared successful until the frontend build step failed due to incompatible Node module versions. Test the compiled assets in a browser to catch JavaScript errors early.

Phase 4: Deployment

After thorough testing in staging, we’re ready to deploy to production. This phase requires careful coordination to minimize user impact.

Schedule a maintenance window if needed. If your deployment involves database migrations that could cause downtime, plan accordingly. For applications with global users, choose a time that’s off-peak for your primary user base. For instance, if most users are in North America, late night Eastern time (2-4 AM) is often safe. Communicate the planned downtime to users well in advance through your usual channels (status page, email, in-app notification).

Of course, if your application supports zero-downtime deployments—using Laravel’s php artisan down --render="errors::503" with a maintenance view, or employing database migration strategies that don’t lock tables—you might not need noticeable downtime. The approach depends on your application’s specific requirements.

Execute the deployment. Your exact deployment process will depend on your infrastructure: manual SSH, Laravel Forge, Envoyer, Kubernetes, or another system. Regardless of the method, follow these essential steps:

  1. Put the application in maintenance mode:

    php artisan down

    Or for zero-downtime setups using Laravel’s graceful maintenance mode:

    php artisan down --render="errors::503" --redirect="https://example.com/maintenance" --refresh=30

    The --refresh parameter tells browsers to retry after 30 seconds. Of course, true zero-downtime deployments that don’t require any maintenance mode at all are possible with careful database migration strategies and blue-green deployments—but that’s beyond our scope here.

  2. Pull the new code to your production server(s):

    git pull origin upgrade/laravel-11
    # or if using a specific release directory structure
    git checkout upgrade/laravel-11
  3. Install Composer dependencies:

    composer install --no-dev --optimize-autoloader --no-interaction

    The --no-dev flag ensures development dependencies aren’t installed, --optimize-autoloader creates a faster class map, and --no-interaction prevents hangs in automated deployments. This step typically takes 2-5 minutes depending on your package count and server performance.

  4. Run database migrations:

    php artisan migrate --force

    The --force flag is required when running in production to prevent interactive confirmation prompts. If your migrations include risky operations (like dropping columns), consider running them in a separate maintenance window or during off-peak hours. We’ve seen migrations on tables with millions of rows lock for 30+ seconds—plan accordingly.

  5. Clear and rebuild caches:

    php artisan optimize:clear
    php artisan config:cache
    php artisan route:cache
    php artisan view:cache

    The config:cache and route:cache commands are particularly important for production performance. On a medium-sized application, route caching can reduce boot time by 100-200 milliseconds per request.

  6. If you use frontend assets, compile them:

    npm ci --only=production  # prefer ci over install for reproducibility
    npm run build

    In production, use npm ci instead of npm install to ensure you’re using exactly the versions from package-lock.json. The build step might take 1-3 minutes depending on your asset pipeline.

  7. Bring the application back online:

    php artisan up

Tip: Always use php artisan down before running migrations, and keep the maintenance page up until you’ve verified the application works. A pattern we’ve used successfully: put the app in maintenance mode, deploy, run migration sanity checks (like php artisan migrate:status), then bring the app up only after confirming key routes respond.

Monitor closely post-deployment. The first few hours after deployment are critical—roughly 80% of upgrade-related issues manifest within the first 60 minutes. You’ll want to:

  • Watch Laravel logs (storage/logs/laravel.log) and your web server logs for errors or warnings in real-time
  • Monitor your error-tracking service (Sentry, Bugsnag, etc.) for exception rates and new error types
  • Check application metrics: 95th percentile response times, error rates, queue health, database connection counts
  • Perform smoke tests of critical user flows (login, checkout, form submissions) to confirm everything works
  • Keep an eye on your monitoring dashboard for any anomalies in traffic patterns or error spikes

One may wonder: what if something goes wrong? You should have a rollback plan ready before you deploy, not after. This typically means:

  • Keeping the previous release’s code accessible (many deployment systems like Envoyer or Capistrano do this automatically with release directories)
  • Having database rollback scripts for migrations, or ensuring migrations are reversible (php artisan migrate:rollback)
  • Knowing how to quickly point your web server back to the previous release (often a matter of changing a symlink)
  • Understanding your data backup restoration process in worst-case scenarios

Generally speaking, a well-tested upgrade in staging should proceed smoothly in production. But preparation for contingencies is prudent—we’ve seen upgrades fail due to unforeseen Composer conflicts, database deadlocks, or caching issues that only appeared under production load.

If you encounter issues, don’t panic. Check the logs, identify the root cause, fix the problem in your upgrade branch, and redeploy. The structured approach we’ve outlined makes recovery more straightforward than a haphazard upgrade. Though it may be tempting to hotfix directly on production, resist that urge—treat the upgrade branch as your single source of truth and ensure any fixes are applied there first, then redeploy through your normal process.

Conclusion

Upgrading a Laravel application—whether you’re moving from one LTS to the next or skipping several versions—requires careful planning and methodical execution. The four-phase strategy we’ve outlined—Planning & Preparation, Execution, Testing & QA, and Deployment—provides a roadmap to navigate the process with confidence.

Remember these key principles:

  • Don’t rush the planning phase. Understanding breaking changes, auditing dependencies, and having backups in place will save you countless headaches later.

  • Test thoroughly before deploying. A staging environment that mirrors production is invaluable. Automated tests help, but manual exploration of critical user flows is equally important.

  • Document everything. Keep notes of what you changed and why. This documentation becomes your reference for future upgrades and helps other team members understand the process.

  • Have a rollback plan. Even with the best preparation, something can go wrong. Knowing how to revert quickly reduces stress and user impact.

Upgrading isn’t just about staying on the latest version; it’s about maintaining a sustainable application that can evolve with the ecosystem. By approaching upgrades deliberately, you reduce technical debt, improve security, and position your application to take advantage of new capabilities as they become available.

Of course, every application is different. The specific challenges you encounter will depend on your codebase size, your dependency ecosystem, and your infrastructure. But the framework we’ve provided here will serve you well regardless of those details.

We haven’t covered every possible scenario—for instance, upgrading applications that use custom service providers extensively, or dealing with legacy database schemas—but the principles remain applicable. If you find yourself facing particularly complex edge cases, the Laravel community forums and upgrade documentation are excellent resources.

We’re confident that with this approach, your next Laravel upgrade will be a manageable, predictable process rather than a source of anxiety. Good luck!

Sponsored by Durable Programming

Need help with your PHP application? Durable Programming specializes in maintaining, upgrading, and securing PHP applications.

Hire Durable Programming