Migrating ASP.NET Apps on Azure App Service Plans from Windows to Linux

Linux app service plans have been around for a number of years, however there are still many apps running under Windows. With Linux Web Jobs becoming generally available at the end of 2024, there has never been a better time to consider a move across and save money. In this post we will look at some of the considerations when moving.
This page is part of the Azure Spring Clean 2025 event. If you have not had a chance yet to look at the other posts in the series I would encourage you to take a look after reading this one.
App Service Plans
App Service Plans are a great PaaS offering in Azure for hosting web apps in a managed environment, either on Windows or Linux. App Service on Linux however works slightly differently than running your applications under Windows. We have a small number of potential code changes to review. In addition App Service on Linux will automatically wrap your application in a container so there are a few other items to consider. That said the process is generally straight forward for most apps.
It is worth noting that all newer versions of .NET can run under Linux as long as you are not depending on Windows specific features. Any older .Net Framework apps would need to be migrated first before being able to run under Linux.
Code Changes
There should be minimal code changes needed when moving between running on Windows verses Linux as the compiled .NET .dll assembly is cross platform.
One of the most common issues encountered is with file paths. Windows primarily uses a backslash \
as a path separator (although forward slash is also supported). Linux on the other hand however, only uses a forward slash /
. If you have any hard coded paths in your codebase you will need to update these to use the Path.Combine
method or use the Path.DirectorySeparatorChar
property, before they will work.
Previously Visual Studio generated ResEx files that embedded a backslash \
in the underlying xml causing an issue running under Linux. This however, has been resolved now, though you may need to manually fix older files.
Its worth noting that some .Net methods do have different implementations or return different results under Linux vs Windows. Culture for example has been standardized in .Net 5 and later across Windows and Linux to use International Components for Unicode (ICU). However the System.Globalization.CultureInfo.GetCultures(System.Globalization.CultureTypes.SpecificCultures)
method returns different results on Linux compared to Windows, as it returns the cultures of the underlying operating system. Depending on your application this may or may not be an issue.
Building
A .Net application built on a Windows machine will generally run successfully on Linux. That said it is recommended to build your code on the same operating system that you are planning to execute the code on. This is to ensure that all the dependencies are present and you are not relying on non supported OS features.
I have found it is also useful to run your unit tests under the same OS as you are deploying on to ensure that there are no platform specific issues such as with file paths.
New App Service Plan
It is not possible to convert an existing app service plan from one operating system to another, neither is it possible to move an application from a Windows app service plan to a Linux app service plan. You will need to create a new app service plan with a unique name and then redeploy your applications on to the new plan.
Environment Variables
The naming constraints for application setting names (aka environment variables) under Linux are different than those on Windows. Under linux you can use only letters, numbers, and the underscore character while under Windows it will allow :
and .
in the names.
This is relevant when using nested settings in your apps. You may have used a format such as this Logging:LogLevel:Default
in the app settings. This however, is not valid under Linux. Instead you will need to use a double underscore Logging__LogLevel__Default
. The good news is that in C# the application code does not need to change as the configuration provider will handle the conversion for you.
For more information on nested settings see this post by Panagiotis Polyzois
Setting the Runtime Stack
In an App Service Plan configuration you need to set a Runtime stack. Under Windows if this was set incorrectly it would often work as different versions of the .NET are installed on the server side by side. Under Linux however the runtime stack is used to determine the version of .NET to use in the container that your code is placed in, so it is essential that it is set correctly for your application to work.
It is worth noting that if you are setting the Runtime stack via Bicep you will set the linuxFxVersion
property to the version of .NET you are using in a format like DOTNETCORE|8.0
. This is compared to a Windows App Service Plan where you would set the netFrameworkVersion
property to v8.0
.
Kudu
When you first look at Kudu and a Linux app service plans it can look quite different. Kudu is actually deployed in a container itself under Linux, however you can still access it via the same Kudu url.
The Linux version does not have a process explorer in the UI or a Web Jobs dashboard however you can still access the file system and run commands via SSH or Bash console to get the process information.
Screenshot of Kudu under Windows
Screenshot of Kudu under Linux
Web Jobs
Web Jobs allow you to write C# code that run in the background on your App Service Plan in a separate process. These can either be set to run on a schedule or continuously awaiting a trigger. From August of 2024 Web Jobs are now supported on Linux app service plans. If you have been using web jobs under windows you may have launched them via a .cmd, .ps1 or .bat file. These files are not supported under Linux so you will need to use a .sh file instead.
Personally I have been using Azure functions for most of my background processing, however Web Jobs are still a great option for some scenarios.
If you are interested in Learning more about web jobs on Linux there’s a great post by Massimiliano Donini on his blog.
Containerization
As mentioned before, by hosting under a Linux up service plan your code is automatically wrapped in a container before being executed. The advantage of the Linux app service plans over a custom container is that you don’t have to worry about patching the underlying .Net version or base image. You also do not have to worry about other concerns such as mapping ports.
Alternatively you can deploy your own custom containers into an app service plan instead. This might be something you would want to consider if you want to use some more advanced features of containerization such as running side cars or as a stepping stone before moving to another hosting platform.
Pricing and Tiers
App Service on Linux has a Free plan to help you get started and experiment. Linux isn’t supported on the paid Shared pricing tier (Known as D1 on Windows). All the other plan sizes however, are supported on Linux with the same CPU and Memory options that are available under Windows.
The pricing for Linux is generally cheaper than Windows although the savings may be different depending on your offer, country SKU etc. I have however, seen saving of about 40% on the standard windows price for equivalent SKUs. The full pricing can be found at on the App Service Plan pricing for Windows and Linux pages.
Conclusion
Overall the process of moving to Linux App Service plans is generally straight forward with minimal code changes needed. The cost savings can be significant and with the general availability of Web Jobs on Linux there has never been a better time to consider moving.
This post has been part of the Azure Spring Clean 2025 event.
Title Photo by Nick Fewings on Unsplash