The Migration That Looks Done — But Isn't
The move from an AWS EC2 instance to a dedicated server follows a pattern most teams know by heart. Back up the database. Copy the application files. Install the required runtimes. Restore the database. Update connection strings. Start the services.
And then something doesn't work. A service starts but behaves differently from the old server. SQL Agent jobs fail silently. An application can't locate a configuration value that was never in any config file you copied. You spend hours comparing the two servers trying to find what's different.
What's different is the Windows registry. Every major service running on a Windows Server — SQL Server, IIS, custom .NET Windows services — stores configuration in the registry. The database backup gets the data. The registry holds the environment those services were built to run in. Copy one without the other and you have a server that's half-migrated.
What the Registry Actually Stores
The Windows registry is a hierarchical database built into the operating system. For server migration purposes, the two hives that matter most are:
HKLM\SOFTWARE\— application and service configuration (memory limits, instance settings, license keys, application-specific values)HKLM\SYSTEM\CurrentControlSet\Services\— every Windows service's startup configuration, account credentials, and parameters
When a service is installed on Windows, it writes its configuration here. When it runs, it reads from here. When you move to a new server and only restore the application files, the new installation has its own default registry values — not the tuned, production-specific values from the server you're leaving behind.
SQL Server Registry Keys
SQL Server is the most common example of a service whose behaviour is shaped almost entirely by registry configuration that never appears in any file you would think to copy.
The instance-level configuration — maximum server memory, max degree of parallelism, backup compression defaults, and the cost threshold for parallelism — lives under:
HKLM\SOFTWARE\Microsoft\MSSQLServer\MSSQLServer
A fresh SQL Server installation on the new dedicated server will have default values for all of these. If your production instance was tuned to use 24 GB of memory and the new one defaults to unlimited, SQL Server will consume all available RAM within hours of load hitting it.
The SQL Server Agent configuration — job scheduling behaviour, alert operators, token security — lives under:
HKLM\SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent
And the Windows service entries that control how SQL Server and SQL Agent start, which account they run under, and what startup parameters they use are at:
HKLM\SYSTEM\CurrentControlSet\Services\MSSQLSERVER
HKLM\SYSTEM\CurrentControlSet\Services\SQLSERVERAGENT
HKLM\SYSTEM\CurrentControlSet\Services\MSSQLFDLauncher
Export all of these before you decommission the source server:
reg export "HKLM\SOFTWARE\Microsoft\MSSQLServer" C:\migration\sqlserver-config.reg /y
reg export "HKLM\SYSTEM\CurrentControlSet\Services\MSSQLSERVER" C:\migration\mssql-service.reg /y
reg export "HKLM\SYSTEM\CurrentControlSet\Services\SQLSERVERAGENT" C:\migration\sqlagent-service.reg /y
Custom Windows Services
Any .NET application deployed as a Windows service has its own registry entry under HKLM\SYSTEM\CurrentControlSet\Services\[ServiceName]. This entry controls the service display name, the path to the executable, the startup type (automatic, manual, delayed), and the service account it runs under.
When you reinstall the service on the new server, Windows creates a new registry entry with whatever values the installer provides. If your production configuration differed from the installer defaults — a specific service account, a modified executable path, a delayed start — those differences exist only in the registry of the old server.
Many applications also store runtime configuration in HKLM\SOFTWARE\[CompanyName]\[ApplicationName]. This is common for values that change between environments and shouldn't be in a config file — internal API endpoints, server-specific paths, licence keys. If your application reads from the registry and that key doesn't exist on the new server, the service will either use a hardcoded fallback or fail entirely, often without a clear error.
Export every service and application key that is relevant to your stack:
# Windows service entry
reg export "HKLM\SYSTEM\CurrentControlSet\Services\YourServiceName" C:\migration\your-service.reg /y
# Application-specific config
reg export "HKLM\SOFTWARE\YourCompany\YourApp" C:\migration\yourapp-config.reg /y
IIS Configuration
IIS stores most of its site and application pool configuration in C:\Windows\System32\inetsrv\config\applicationHost.config — not the registry — so that file should be part of your migration checklist alongside the registry export. However, some IIS service-level settings and the W3SVC service entry are registry-based:
HKLM\SOFTWARE\Microsoft\InetStp
HKLM\SYSTEM\CurrentControlSet\Services\W3SVC
HKLM\SYSTEM\CurrentControlSet\Services\WAS
The more reliable approach for IIS is to use the built-in export tool rather than raw registry manipulation:
# Export all IIS configuration
%windir%\system32\inetsrv\appcmd list site /config /xml > C:\migration\iis-sites.xml
%windir%\system32\inetsrv\appcmd list apppool /config /xml > C:\migration\iis-apppools.xml
# Import on the new server
%windir%\system32\inetsrv\appcmd add site /in < C:\migration\iis-sites.xml
%windir%\system32\inetsrv\appcmd add apppool /in < C:\migration\iis-apppools.xml
Importing Registry Keys on the New Server
With the .reg files transferred to the new server, importing them is straightforward:
reg import C:\migration\sqlserver-config.reg
reg import C:\migration\mssql-service.reg
reg import C:\migration\sqlagent-service.reg
reg import C:\migration\your-service.reg
reg import C:\migration\yourapp-config.reg
Do this before starting any services on the new server. If a service has already started and cached its configuration, restart it after importing so the new registry values take effect.
One important caveat: service account passwords are not stored in the registry in plain text and will not be carried over by the export. After importing the service entries, open Services (services.msc), locate each service, and re-enter the service account credentials under the Log On tab.
The Complete Migration Checklist
Working through migrations repeatedly, this is the checklist that accounts for everything the obvious steps miss:
- Database — full backup with
BACKUP DATABASE, restore on the new instance, verify withDBCC CHECKDB - SQL Server configuration — export
HKLM\SOFTWARE\Microsoft\MSSQLServerand service entries - SQL Agent jobs — script all jobs from SSMS (right-click Jobs → Script Job As) or use
msdbsystem tables - Application files — binaries, published output, static assets
- Web.config / appsettings.json — with environment-specific values updated for the new server
- IIS sites and application pools — via appcmd export or IIS Manager shared configuration
- SSL certificates — export with private key (
.pfx), import in Certificate Manager on the new server - Custom Windows services — export registry entries, reinstall executables, re-enter service account passwords
- Application registry keys — any
HKLM\SOFTWARE\[Company]\[App]entries your applications read - Scheduled tasks — export via Task Scheduler or
schtasks /query /fo LIST /v > tasks.txt - Windows Firewall rules — export via
netsh advfirewall export C:\migration\firewall.wfw - Environment variables — system-level variables under
HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment - Hosts file —
C:\Windows\System32\drivers\etc\hostsif internal DNS entries were added manually
Test Before You Cut Over
The value of running both servers in parallel — even briefly — is that you can verify the new server under real conditions before switching DNS. Run your application against the new server with a small subset of traffic, or run your integration tests pointed at it directly. The registry issues surface immediately under real load in ways that a quick visual check of the running services will not reveal.
The time investment in a proper registry export adds less than 30 minutes to any migration. The time lost diagnosing why a service behaves differently on the new server without that export can take days.