.NET apps
Install the .NET 8, 9 or 10 SDK, deploy ASP.NET Core and other C# web apps with dotnet publish, bind Kestrel to $PORT through ASPNETCORE_URLS and fix common errors.
ZoPanel builds .NET web applications with dotnet publish, runs the published app as a service of the hosting account and puts nginx with SSL in front of it. Use it for ASP.NET Core (MVC, Razor Pages, Blazor Server, minimal APIs) and any other .NET app that serves HTTP.
Install the .NET SDK
An administrator installs it once per server:
- Open Runtimes.
- On the .NET SDK card, click the version to install (.NET 10.0, .NET 9.0 or .NET 8.0). The task log shows the progress. Install every version your apps target: they live side by side.
ZoPanel offers the SDK versions Microsoft supports: .NET 10 (LTS, supported until November 2028), and .NET 9 and .NET 8, both supported until 10 November 2026. Each package (dotnet-sdk-10.0, …) also contains the matching ASP.NET Core runtime:
| System | Where the SDK comes from | Architectures |
|---|---|---|
| Ubuntu 22.04 | 8.0 from Ubuntu's packages; 9.0 and 10.0 from Canonical's .NET backports repository (ppa:dotnet/backports), added automatically |
amd64 and arm64 |
| Ubuntu 24.04 | 8.0 and 10.0 from Ubuntu's packages; 9.0 from the .NET backports repository | amd64 and arm64 |
| Debian 12, 13 | Microsoft's package repository, added automatically | amd64: 8.0, 9.0, 10.0. arm64: 10.0 only |
Important: Microsoft publishes the .NET 8 and .NET 9 packages for Debian only for amd64. On a Debian arm64 server the card offers only .NET 10 and explains why; for an app that targets net8.0 or net9.0 there, retarget it to net10.0, use an Ubuntu server, or build and run it as a container (see Deploy with Docker). If Microsoft's repository has no configuration for the Debian release, the install stops with ".NET is not available for Debian … yet: … (deploy with a Dockerfile instead)".
Which SDK builds the app
The app's Version selects the SDK. Auto-detect sets it from the project's <TargetFramework> (net9.0 → 9.0; with <TargetFrameworks>, the newest one). During the build ZoPanel adds a global.json to the release that pins that SDK version, so the build log shows "Using the .NET 9.0 SDK". If the repository has its own global.json (in the project folder or above it), that file decides and ZoPanel does not add one. With no version, the newest installed SDK is used.
If the SDK for the version is not installed, the deployment stops with "the .NET 9.0 SDK is not installed (installed: …)". Install it under Runtimes, or change the project's target framework.
What ZoPanel detects
A project is treated as .NET when its root directory contains a .csproj file, a .sln file, or a .csproj one folder below. ZoPanel then looks for projects up to two folders deep (for example src/MyApp/MyApp.csproj), skipping bin, obj and hidden folders:
- A web project (
Microsoft.NET.Sdk.Web) is preferred and labelled ASP.NET Core. Otherwise the first project withMicrosoft.NET.Sdkis used, labelled .NET. - If there are several projects, the build log notes which one is published.
For a project MyApp.csproj, the proposed commands are:
| Field | Command |
|---|---|
| Install command | dotnet restore 'MyApp.csproj' |
| Build command | dotnet publish 'MyApp.csproj' -c Release -o out --no-restore |
| Start command | ASPNETCORE_URLS=http://127.0.0.1:$PORT dotnet out/'MyApp.dll' |
Detection runs in a fixed order: a package.json, composer.json, Gemfile or Java build file in the same directory is matched before .NET. If the wrong framework is detected, set Root directory to the project folder or turn off Auto-detect and set the commands yourself. A Procfile with a web: line replaces the start command.
Deploy a .NET app
- In Websites → New website, choose Git deploy, and enter the domain and the repository. See Git deploy.
- Open the website's Deploy tab and check Framework and the commands under Build & run.
- To change a command, turn off Auto-detect, edit it and click Save & redeploy.
- Add your settings under Environment variables.
- Click Deploy now.
The new release goes live only after it answers on the Health check path (default /) with a status below 500 within 90 seconds. Until then the previous release keeps running. To build with another SDK, turn off Auto-detect and pick the Version (see Which SDK builds the app).
To deploy without Git, create the website with Node / Proxy, upload the source to domains/<domain>/source and choose Uploaded files (File Manager) as the source. Upload the source code, not a publish folder: ZoPanel runs the restore and publish itself.
Port and URL
Kestrel must listen on 127.0.0.1 at the port in PORT. The proposed start command does this with ASPNETCORE_URLS=http://127.0.0.1:$PORT. Keep that prefix if you edit the command, and do not hard-code URLs elsewhere:
- Remove
UseUrls(...)calls andKestrel:Endpointsentries inappsettings.json: they overrideASPNETCORE_URLS. - Do not add an HTTPS endpoint to Kestrel. nginx terminates SSL and talks to the app over plain HTTP.
PORT changes between the two blue/green slots (the second slot uses the base port plus 10000), so never write the number into your configuration.
Behind nginx
nginx passes X-Forwarded-For and X-Forwarded-Proto. To make the app see the visitor's IP and https (for redirects, cookies and OAuth callbacks), enable the forwarded headers middleware early in Program.cs:
using Microsoft.AspNetCore.HttpOverrides;
var app = builder.Build();
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
The proxy is on 127.0.0.1, which ASP.NET Core trusts by default.
Assembly name
The start command assumes the published DLL is named after the project file. If the project sets <AssemblyName>, change the start command to out/<AssemblyName>.dll.
Environment variables
Variables under Environment variables are available during the build and at runtime. ZoPanel also sets PORT and HOST=127.0.0.1.
- Names use letters, digits and
_, start with a letter or_, and have at most 64 characters. Values are one line, up to 8,192 characters. Up to 200 variables. - Values are kept in a root-only file and passed by systemd, never on the command line.
ASP.NET Core reads environment variables as configuration. Use a double underscore for nested keys:
| Variable | Configuration key |
|---|---|
ConnectionStrings__Default |
ConnectionStrings:Default |
Smtp__Host |
Smtp:Host |
ASPNETCORE_ENVIRONMENT |
The environment name (Production when not set) |
For a MariaDB or PostgreSQL database created in ZoPanel, use 127.0.0.1 as the server. See Databases.
Memory
The app, its build and everything else the account runs share the package's RAM (MB). A process over the limit is stopped by the kernel and ZoPanel restarts the app. During a deployment, the build and a short overlap of the old and new release need memory at the same time.
ASP.NET Core projects use server garbage collection by default, which keeps more memory per CPU core. On small packages, switch to workstation GC or cap the heap with environment variables (heap values are hexadecimal):
DOTNET_gcServer=0
DOTNET_GCHeapHardLimit=0x20000000
0x20000000 is 512 MiB. See Packages and limits.
Files and logs
- The app can write only inside the account's home directory and its private
/tmp. Keep uploads in a Persistent paths entry (for examplewwwroot/uploads) or in the database: each release is a new folder, and the last 5 are kept for Rollback. - Application output on the Deploy tab shows the last 400 lines of the app's console output. Turn on Live to follow it.
- The restore and publish output of each deployment is in Tasks. nginx logs are on the Logs tab.
Troubleshooting
| Message or symptom | What to do |
|---|---|
| "the .NET SDK is not installed — install it under Runtimes" | No SDK on the server. Ask the administrator to install one on the .NET SDK card under Runtimes. |
| "the .NET 9.0 SDK is not installed (installed: …)" | The project targets a version whose SDK is missing. Install it under Runtimes, or change the target framework. |
| ".NET 8.0 cannot be installed: Microsoft publishes .NET 8 and 9 packages for Debian on amd64 only…" | Debian on arm64. Use .NET 10, an Ubuntu server or a container. |
| ".NET is not available for Debian … yet" when installing | Microsoft has no repository for this Debian release. Use Ubuntu or deploy with a Dockerfile. |
NETSDK1045: The current .NET SDK does not support targeting .NET 10.0 |
The Version was set by hand to an older SDK than the target framework, or the repository's global.json pins an older SDK. Turn Auto-detect back on, or fix global.json. |
A compatible .NET SDK was not found |
The repository's global.json asks for an SDK version that is not installed. Install that version, or relax rollForward in global.json. |
| "The app did not respond on port … within 90s (it must listen on $PORT)" | Kestrel listens elsewhere (often localhost:5000, or a URL from appsettings.json or UseUrls). Keep ASPNETCORE_URLS=http://127.0.0.1:$PORT and remove other endpoint settings. |
| "The app exited during startup" | Read Application output. The application to execute does not exist means the DLL name in the start command is wrong (see Assembly name). |
Redirects or generated links use http://, or every visitor IP is 127.0.0.1 |
Add the forwarded headers middleware shown above. |
| The app restarts with no error | The account reached its RAM limit. Set DOTNET_gcServer=0 or DOTNET_GCHeapHardLimit, or raise the package's RAM (MB). |
Related
- Node.js and Python apps: deployment pipeline, ports and blue/green releases
- Git deploy
- Deploy with Docker
- Packages and limits
- Host ASP.NET Core on Linux with Nginx (Microsoft)
- Configure ASP.NET Core to work with proxy servers (Microsoft)