Upgrading Sitecore XP 10.2 to 10.5 – lessons learned

Back on-prem, and straight to 10.5

I’ve spent the last years mostly in SitecoreAI and XM Cloud land. Then I got a project where they needed an upgrade, and suddenly i was back in IIS, SIF and web.config again.

The plan was originally 10.4, then 10.5 landed and we decided to skip straight to it. Since 10.1 you can upgrade directly to any later version so no need to hop through 10.3 and 10.4 on the way.

This post is about why 10.5 is worth it, where it bit me, and a checklist for your own upgrade.

Why 10.5, and is it actually faster?

Sitecore XP 10.5 shipped on 5 August 2026. It’s not a release full of shiny authoring features. It’s more a hardening and housekeeping release.

Faster where it counts

The performance list in the release notes is better than I expected:

  • MVC rendering is faster in environments with many sites. Running multi-site on one instance? This one’s for you.
  • Cache removal by predicate no longer takes a global lock, so a big cache clear doesn’t make everything else wait in line.
  • CompositeDataProvider now caches its path resolving.
  • Recycle Bin cleanup of archived items is batched, instead of one huge delete.
  • Profiling processors for placeholders and renderings are switched off on CD servers. Less overhead on every rendering, for free!
  • Solr optimize no longer runs after every index operation, so indexing jobs finish sooner. Schedule it yourself if you need it.

Safer

This is the real reason to upgrade. 10.5 patches a critical pre-authentication XAML cache poisoning issue and a post-auth remote code execution chain. BinaryFormatter is gone, replaced with JSON serialization. The Package Installer is disabled by default, which shrinks the attack surface (but at the same time “killing” an old time favourite, though it can be enabled again through configs).

There’s also a nice publishing fix: incremental publish no longer skips items because of a race condition. If you’ve ever had “why didn’t that item publish?” tickets, you know.

One infrastructure lift instead of two

10.5 supports SQL Server 2025, Solr 10 and Windows Server 2025, and drops SQL Server 2019 and Solr 8/9. That sounds like extra work, and it is. Landing on 10.4 would have meant doing the Solr and SQL lift again in a year or two.

Lessons learned the hard way

Most of my time didn’t go to Sitecore itself. It went to the tools around it, and to years of build habits that 10.5 no longer tolerates.

The approach: start from a clean 10.5

Instead of upgrading the old instance in place, start fresh and bring your databases over:

  1. Install a clean Sitecore XP 10.5 with the installation wizard (SIA).
  2. Restore your old 10.2 databases next to it.
  3. Run the upgrade scripts from the 10.5 upgrade files zip against them.
  4. Switch your connection strings to the upgraded databases.
  5. Publish your solution to the website folder.

This should allow the site to run with the upgraded databases. And you get a clean 10.5 install to compare against, which comes in handy later in the build part.

The clean install: SQL Server 2025 meets Web Deploy

The installation wizard runs SIF under the hood, so this hits you already in step 1.

Web Deploy can’t deploy the databases. This was the biggest time thief of the whole project. Web Deploy 4.0’s database provider dates from around 2017 and doesn’t recognize modern DacFx. Against SQL Server 2025 it fails with ERROR_DACFX_NEEDED_FOR_SQL_PROVIDER, whatever you install.

I tried registry keys, copying DLLs around and a full Visual Studio with SSDT. Nothing helped. What worked was skipping the databases in SIF and deploying them myself:

  1. Set "Skip.Install.Databases": true directly in the child configs (xconnect-xp0.json, sitecore-XP0.json). The XP0 wrapper config doesn’t pass the parameter down.
  2. Publish each dacpac from the WDP package with SqlPackage.
  3. Create the SQL users yourself, with the passwords SIF wrote into ConnectionStrings.config.

Test this before you start a long install run. If this throws, go straight to the workaround:

& "C:\Program Files\iis\Microsoft Web Deploy V3\msdeploy.exe" -verb:dump -source:dbDacFx=nonexistent.dacpac -verbose

A few smaller ones:

  • Run SIF from Windows PowerShell 5.1 as admin, not PowerShell 7. In pwsh the IIS and DISM cmdlets break in confusing ways.
  • SQL Server 2025’s self-signed certificate isn’t trusted by default. Add TrustServerCertificate=True to every connection string, or install a proper certificate.
  • SIF copies your license file but doesn’t rename it. It has to be called license.xml, in every folder, including xConnect’s job folders.
  • On newer IIS, SIF 2.4.1’s website start/stop task fails with “The property ‘Value’ cannot be found”. A small patch in Invoke-ManageWebsiteTask.ps1 fixes it.
  • Solr 10 requires Basic Auth. Edit security.json as plain text, not through a ConvertFrom-Json round trip. A broken file gives you Solr’s own 404 page, which looks like a wrong URL.

The build: .NET 4.8.1 and the vanishing dependencies

10.5 targets .NET Framework 4.8.1. The NuGet packages give a hard NU1202 error on 4.8. Bump TargetFrameworkVersion in every csproj and install the 4.8.1 Developer Pack on every build machine.

Sitecore.Kernel stopped leaking dependencies. 10.2 pulled in HtmlAgilityPack, Newtonsoft.Json, System.Linq.Async, PdfSharp and friends transitively. 10.5 doesn’t. Code that compiled for years suddenly can’t find a type. It’s not a code bug, just add the package to that project and move on.

Sitecore’s web.config then promised a version in a binding redirect that wasn’t on disk, and the app domain wouldn’t start. The fix was to pin those packages to the versions Sitecore ships, in one shared .targets file every project imports.

Don’t find the drift one crash at a time like I did. Diff your webroot against a clean 10.5 install and get the whole list at once:

Get-ChildItem "$cleanBin\*.dll" | ForEach-Object {
$deployed = Join-Path $deployedBin $_.Name
if (-not (Test-Path $deployed)) { return }
$ref = [Reflection.AssemblyName]::GetAssemblyName($_.FullName).Version
$dep = [Reflection.AssemblyName]::GetAssemblyName($deployed).Version
if ($ref -ne $dep) { "$($_.Name): clean=$ref deployed=$dep" }
}

Not every difference is a problem. Packages your own code references on newer versions can stay. And a few assemblies, like System.Web.Http, are listed with an exact version in Sitecore’s <compilation> section. Lowering the redirect won’t help there, the DLL on disk has to be that version.

Let AI help you find the drift. Connect the Sitecore docs MCP in VS Code or Cursor (I wrote about how to set it up here) and use it together with the AI in your editor. Give it the diff output, your csproj files and web.config, and let it find broken package versions or references to wrong dll versions etc. Much faster than going through 70 projects by hand! You could probably even point it to this blogpost to help you get started.

The first boot: one bug at a time

Clean out your old hotfixes. Our hotfix package list still had 10.2 cumulative fixes and security patches in it. If they are resolved, clean it out.

Read the event log, not the browser. With customErrors on, every failure looks the same: a redirect to 500.html. The real exception was in the Windows Application event log (source ASP.NET, event 3005). Fix one thing, publish, read the new entry. It was almost always a different bug. We had six in a row.

Two more event log tricks:

  • An entry with no stack trace and an “access exception” usually means file permissions.
  • An entry cut off mid-sentence hit the event log’s ~30 KB limit. Set customErrors to RemoteOnly for a moment and open the URL on the server to see the full error. Then set it back.

Hunt for stale environment values. A site definition hostName with no Debug transform, so the site resolver fell back to the empty default site. And an old xConnect hostname and certificate thumbprint in the connection strings.

That last one was sneaky. Every Experience Editor ribbon button builds an xConnect client in a static constructor. One missing certificate poisoned the type, and the whole ribbon spun forever. Search App_Config for old hostnames, prefixes and thumbprints before you start testing.

The databases: ownership, shards and warm-up

Shard maps remember where they used to live. Moving the xDB collection databases? If you’re moving from another SQL host, e.g. Azure SQL. The shard map manager stores server and database names in __ShardManagement.ShardsGlobal, and every shard has its own copy in ShardsLocal. Update both, or xConnect’s health check times out against the old server.

Warm up xConnect. xConnect takes 1–3 minutes to start after a recycle. Requests in that window got a bare IIS 500.0 with 0x80070032. Turn on IIS Application Initialization: AlwaysRunning, preloadEnabled, and /healthz/ready as the initialization page. Now requests wait instead of failing.

One gotcha there: if the site only has an HTTPS binding, set enabledProtocols to https. Otherwise preload never fires.

My checklist if you’re doing the same

If I would do this again tomorrow, this is the order I’d go in:

  1. Read 10.5’s own upgrade guide. From 10.1 and up you upgrade directly, so use the 10.5 upgrade files zip, not the ones for 10.3 or 10.4. The UpdateApp tool is only needed for modules like EXM and SXA.
  2. Start from a clean 10.5: install it with the wizard, restore your old databases, run the upgrade scripts, switch connection strings and publish.
  3. Check every module against the compatibility table before you commit to a date.
  4. Line up the infrastructure: SQL Server 2022 or 2025, Solr 10 with Basic Auth, and time for a full reindex. The Solr schema file is now called managed-schema.xml.
  5. Pre-flight the server: Windows PowerShell 5.1 as admin, W3SVC and WAS running, and the msdeploy test from above.
  6. Search your code for Sitecore.Convert and FileUtil.LoadObject/SaveObject. With BinaryFormatter gone they return null or silently do nothing. Also look for GraphQL Playground references and ParallelForeachProxy overrides.
  7. Bump to .NET 4.8.1, pin shared packages in one place, diff your bin against a clean install, and let AI with the Sitecore docs MCP go through the drift.
  8. Empty your old hotfix list, then check if 10.5 needs patches of its own.
  9. Check environment values: hostnames, certificate thumbprints, license.xml, TrustServerCertificate.
  10. Warm up xConnect and make sure /healthz/ready says Healthy before anyone starts testing.
  11. Regression test the changed defaults in the table below.

Defaults that changed in 10.5

SettingNew behaviourWhat to do
Publishing.CheckDuplicateNameOnPublishtrue: items with a duplicate sibling name are skipped, with a warning in the logSearch publish logs for skips, or set it to false
Package Installer and DesignerDisabled by defaultDeploy zSitecore.InstallPackage.Enable.config where you need it
DeviceDetection.PerformanceProfileBalanced instead of LowMemory, uses more memorySet LowMemory on tight servers
Solr optimizeNo longer runs after index operationsSchedule it if you need it
Application InsightsInstrumentation keys removedUse AppInsightsConnectionString

The full list is in the breaking changes for 10.5. It’s worth reading the whole page, it’s not that long.

Wrapping up

Was it worth skipping 10.4? For us, yes. One infrastructure lift, a much better security baseline, and some real performance work under the hood.

Most of the pain wasn’t 10.5’s fault. It was old tools meeting a new SQL Server, and years of build habits finally catching up. Once that was sorted, the site came up and we moved on to functional testing.

After two years in SitecoreAI, it’s also a good reminder of everything SaaS takes off your plate. But plenty of XP sites will be around for years, and 10.5 is a good place for them to land. If you’re still on 10.2 or older, don’t wait for the next one.

Hit something I didn’t? Let me know, would love to hear about it!