☰
Ubuntu 22.04安装.NET 8 SDK与Runtime避坑指南
2026/10/21 3:11:17 网站建设 项目流程

直接说结论:Ubuntu 22.04 上装 .NET 没有想象中那么绕,但确实有几个坑值得提前避开。我最近刚在一台全新安装的 22.04 服务器上从零配好 .NET 8 环境,中间踩了微软源签名失效、运行时和 SDK 混淆这类问题,顺手把整个过程整理成这篇实操记录。不管你是在本机做开发,还是要在服务器上部署 ASP.NET Core 应用,这篇东西都能直接照着抄。

先说清楚一个概念:.NET SDK和.NET Runtime不是一回事。SDK 包含编译器、MSBuild、dotnet CLI 等一系列开发工具,适合在本机写代码、编译项目;Runtime 只负责运行已经编译好的程序。如果你只是部署别人写好的应用,装 runtime 就够了,没必要多占那几百 MB。但如果你连dotnet build都要跑,那就装 SDK。搞混这两者,是我见过最常见的安装翻车原因。

1. 准备工作:先搞清楚系统状态和版本选择

1.1 确认 Ubuntu 版本和架构

动手之前先确认系统的具体版本和 CPU 架构。命令很简单:

lsb_release -a uname -m
  • lsb_release -a会显示发行版信息,确认是 22.04.x 而不是 20.04 或 24.04。不同版本的源配置路径略有差异,硬套会报错。
  • uname -m显示x86_64就代表 amd64 架构,这是绝大多数服务器的类型。如果是aarch64(ARM 架构,比如树莓派或某些云厂商的 ARM 实例),后面装包时要选 arm64 版本,不能直接照抄 amd64 的命令。

我在某台 ARM 架构的实例上踩过一次坑:直接用 amd64 的安装脚本,装完以后运行dotnet --info直接报"command not found"。因为源配置里写的架构不匹配,包管理器根本找不到对应的软件包。所以第一步别省,看清楚架构再往下走。

1.2 .NET 版本该怎么选

现在(以当前时间点为准)主流的选择是 .NET 8,它是 LTS(长期支持)版本,微软会维护三年左右。.NET 9 属于 STS(短期支持),生命周期只有 18 个月,适合想尝鲜的开发者,不适合需要稳定环境的服务器。.NET 6 虽然还在支持期内,但已经进入了最后阶段,新项目不建议再用了。

打个比方:LTS 就像买房,合同期长、稳定;STS 就像租房,到期了就得搬,不搬就没维护。生产环境我劝你老老实实选 LTS,除非项目有明确需求非用某个 STS 特性不可。

# 查看当前系统是否已有 .NET 相关包 dpkg -l | grep -E 'dotnet|aspnet'

这一条命令能帮你快速排查系统里是否已经装过 .NET。如果有残留版本,建议先清干净再装新的,避免版本冲突。

1.3 安装基础依赖

.NET 在 Ubuntu 上需要一些底层依赖库,主要是 OpenSSL、ICU(国际化组件)、curl 等。绝大多数全新安装的 Ubuntu 22.04 都自带这些,但保险起见先跑一遍更新:

sudo apt update sudo apt install -y curl ca-certificates libstdc++6 libicu70

libicu70在 22.04 上默认叫这个名字,20.04 上则是libicu66,用错版本会报找不到依赖的错误。如果你已经安装了更高版本的 libicu,apt 会自动处理依赖关系,不需要手动指定。

提示:如果系统里有残留的旧版 .NET,建议先执行sudo apt remove --purge dotnet-* aspnetcore-*清理干净,再开始新安装。我见过两台机器因为新旧版本混合导致dotnet --list-sdks显示正常但编译时行为异常的情况,排查半天最后发现是全局环境变量在捣乱。

2. 通过 APT 源安装:最省心的官方路线

2.1 配置微软软件源

微软官方提供了 Ubuntu 的 APT 源,这是我最推荐的安装方式。好处是后续可以用sudo apt update && sudo apt upgrade直接升级 .NET,不用手动下载安装包。

先把微软的包签名密钥和源地址加进去。Ubuntu 22.04 用的代号是jammy,这一点必须写对。

# 下载并安装微软的 GPG 密钥 wget https://packages.microsoft.com/keys/microsoft.asc sudo mv microsoft.asc /etc/apt/trusted.gpg.d/microsoft.asc # 配置 APT 源 sudo sh -c 'echo "deb [arch=amd64 signed-by=/etc/apt/trusted.gpg.d/microsoft.asc] https://packages.microsoft.com/ubuntu/22.04/prod jammy main" > /etc/apt/sources.list.d/microsoft-prod.list'

如果你用的是 ARM 架构,把上面源地址里的arch=amd64改成arch=arm64。

这一步我想多说两句。网上很多老教程用的是curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor ...这种方式,会把密钥转成二进制格式再放进去。我实测在 22.04 上直接保存.asc文本格式也能正常工作,因为新版的 apt 支持直接读取 ASCII armor 格式的密钥。两种方式都行,但建议保持一致性,别混用。

2.2 安装 SDK 或 Runtime

源配好之后,先刷新包列表,然后安装:

sudo apt update

然后选择一种:

# 安装完整 SDK(开发机用) sudo apt install -y dotnet-sdk-8.0 # 只装 .NET Runtime(部署服务器用) sudo apt install -y dotnet-runtime-8.0 # 只装 ASP.NET Core Runtime(跑 Web 应用用) sudo apt install -y aspnetcore-runtime-8.0

这里有一个常见的误区:跑 ASP.NET Core 应用时不能只装dotnet-runtime-8.0。普通 runtime 不包含 ASP.NET Core 的组件,你装完以后运行 web 项目会报错说缺少Microsoft.AspNetCore.App框架引用。必须装aspnetcore-runtime-8.0才行。

SDK 自带完整的运行时,所以装了 SDK 就不用再单独装 runtime,反过来不行。

2.3 验证安装结果

安装完成后,验证一下:

dotnet --version dotnet --list-sdks dotnet --list-runtimes

正常输出应该能看到类似8.0.x的版本号。如果三个命令都报 command not found,大概率是 PATH 没配上。但用 apt 安装一般不会出现这个问题,除非你之前手动改过~/.bashrc里的 PATH。

再做一个实战测试,创建一个临时项目跑一下:

mkdir /tmp/dotnet-test && cd /tmp/dotnet-test dotnet new console -n HelloDotnet cd HelloDotnet dotnet run

看到终端打印出 "Hello, World!" 就说明环境没问题。这一步虽然简单,但能验证编译链路、运行时加载、依赖解析是否完整,比单纯看dotnet --version靠谱得多。

3. 通过官方脚本安装:适合多版本共存的场景

3.1 使用 dotnet-install 脚本

除了 APT 源,微软还提供了一个安装脚本,适合需要安装指定版本、或想多版本并存的场景。这个脚本的路径是公开的:

# 下载安装脚本 wget https://dot.net/v1/dotnet-install.sh chmod +x dotnet-install.sh # 安装最新的 .NET 8 SDK ./dotnet-install.sh --channel 8.0

脚本默认安装到$HOME/.dotnet,不会写入系统目录,也不需要 sudo。这有个好处:不会污染全局环境,卸载时直接删目录就行。但也有个坑:脚本装的 .NET 不在系统 PATH 里,需要手动添加:

echo 'export PATH=$HOME/.dotnet:$PATH' >> ~/.bashrc source ~/.bashrc

3.2 为什么有时候脚本比 APT 更合适

我个人的经验是:如果你是开发者,且要在同一台机器上切换 .NET 6 和 .NET 8 两个版本来验证兼容性,脚本方案比 APT 省心得多。APT 源每次 update 都会把微软源里的所有包刷一遍,版本多了之后偶尔会出现依赖锁定的问题。而脚本方案是完全独立安装,互不干扰。

你可以这样同时装两个版本:

./dotnet-install.sh --channel 6.0 ./dotnet-install.sh --channel 8.0

然后通过dotnet --list-sdks查看,会同时列出两个 SDK。运行时选择方面,在项目目录下放一个global.json文件即可指定用哪个版本:

{ "sdk": { "version": "8.0.100", "rollForward": "latestFeature" } }

rollForward指定了版本匹配的策略。latestFeature表示允许向上匹配同大版本内的最新功能版本;如果想严格锁定精确版本,可以设成disable。这个文件在 CI/CD 构建时特别有用,能保证构建环境的 SDK 版本完全一致。

注意:手动脚本安装的版本不会出现在 APT 的包管理记录里,以后升级时需要手动再跑一次脚本。如果安装到系统级目录(用--install-dir /usr/share/dotnet),卸载时直接删目录,但我不建议这么做,容易把系统 PATH 搞乱。

3.3 直接下载二进制包

如果你连脚本也不想跑,也可以直接从微软官网下载 tar.gz 压缩包手动解压:

mkdir -p $HOME/dotnet tar zxf dotnet-sdk-8.0.100-linux-x64.tar.gz -C $HOME/dotnet export PATH=$HOME/dotnet:$PATH dotnet --version

这种方式的优点是完全可控,想放哪放哪,尤其适合做离线部署(在内网环境里不能访问外网时)。缺点是升级也得手动下载解压,而且要注意下载的版本号要和官方发布页面对应上,复制错链接是常事。

4. 部署 ASP.NET Core 应用时的环境配置

4.1 设置环境变量

装完 .NET 只是第一步,真正把应用跑起来还需要一些环境配置。ASP.NET Core 默认会监听localhost:5000,但在服务器上你一般得让它监听0.0.0.0才能被外部访问。有两种方式:

# 方式一:运行前设置环境变量 export ASPNETCORE_URLS=http://0.0.0.0:5000 dotnet YourApp.dll # 方式二:启动时指定 dotnet YourApp.dll --urls "http://0.0.0.0:5000"

如果你的应用里配置了applicationhost.json或者 launchSettings.json,这个值可能被覆盖。部署到 Linux 上时,建议在 appsettings.json 里显式设置"Urls": "http://0.0.0.0:5000",或者干脆在 systemd 服务文件里通过Environment=ASPNETCORE_URLS=http://0.0.0.0:5000来指定。

我实际部署某项目时遇到过一个问题:在开发机上跑dotnet run一切正常,但发布到服务器后怎么都访问不到。排查了一圈才发现是 launchSettings.json 里写死了 localhost 监听地址,发布出来的 DLL 仍然在读这个配置。后来把发布目录里的 launchSettings 删掉,问题就解决了。

4.2 HTTPS 开发证书

本地开发如果用到 HTTPS,需要先设置开发证书:

dotnet dev-certs https dotnet dev-certs https --trust

在 Ubuntu 上,--trust需要手动将证书导入系统信任区。它会提示你输入 sudo 密码。如果在服务器上做 HTTPS 部署,不建议用 dev-certs,而是用反向代理(比如 Nginx)来做 TLS 终止,或者申请正规的 CA 证书。

4.3 用 systemd 把应用变成后台服务

在生成环境服务器上,推荐用 systemd 托管 .NET 应用,这样系统重启时应用会自动拉起,crash 了也会自动重启。创建一个 service 文件:

sudo nano /etc/systemd/system/yourapp.service

内容如下:

[Unit] Description=My .NET Web Application [Service] WorkingDirectory=/var/www/yourapp ExecStart=/usr/bin/dotnet /var/www/yourapp/YourApp.dll Restart=always RestartSec=10 KillSignal=SIGINT Environment=ASPNETCORE_ENVIRONMENT=Production Environment=ASPNETCORE_URLS=http://0.0.0.0:5000 [Install] WantedBy=multi-user.target

然后执行:

sudo systemctl daemon-reload sudo systemctl enable yourapp.service sudo systemctl start yourapp.service

这里KillSignal=SIGINT很重要。.NET 应用收到 SIGINT 时能优雅关机,执行挂起请求的收尾工作(比如保存状态、清理连接池)。如果用默认的 SIGTERM,有时会导致进程直接退出,某些数据库事务来不及回滚。

查看日志用:

sudo journalctl -u yourapp.service -f

4.4 内存与 GC 配置

在服务器上部署 .NET 应用,内存占用经常是运维关注的重点。默认情况下 .NET 的 GC 是服务器模式,会根据机器物理内存自动调节堆大小。在内存有限的 VPS(比如 1GB 或 2GB)上,可以考虑用ServerGC和GCConserveMemory配置:

export DOTNET_gcServer=1 export DOTNET_GCConserveMemory=9

或者在 csproj 里设置:

<PropertyGroup> <ServerGarbageCollection>true</ServerGarbageCollection> <ConserveMemory>9</ConserveMemory> </PropertyGroup>

但这里要提醒一点:不建议在容器环境里盲目调 GC 参数。我见过有团队在 Docker 里把 ServerGC 关掉,结果应用在小并发下频繁 GC,CPU 飙到 80%。先跑起来看监控数据,再决定要不要调,比听网上建议瞎改强。

5. 常见问题与排查技巧实录

5.1 证书验证失败:无法从 packages.microsoft.com 获取源

这是搭 APT 源时最常见的问题,报错信息一般长这样:

W: GPG error: https://packages.microsoft.com/ubuntu/22.04/prod jammy InRelease: The following signatures couldn't be verified because the public key is not available

出现这个问题的原因在于/etc/apt/trusted.gpg.d/microsoft.asc没有成功下载,或者权限不对。我碰到过一次是 wget 下载时网络中断,生成的文件是空文件,apt 读不到密钥自然就报错。

解法很简单:重新下载密钥,确认文件非空:

sudo rm -f /etc/apt/trusted.gpg.d/microsoft.asc wget -q https://packages.microsoft.com/keys/microsoft.asc ls -l microsoft.asc # 确认文件大小不是 0 sudo mv microsoft.asc /etc/apt/trusted.gpg.d/microsoft.asc sudo apt update

5.2 安装了 SDK 但 dotnet 命令找不到

这个问题通常在手动解压安装或者脚本安装后出现。原因很简单:PATH 没配置好。

echo $PATH

看看输出里有没有 dotnet 所在目录。如果没有,参照前面第 3 节的方法添加 PATH。如果你用的是 snap 安装方式,命令路径不一样,建议直接卸载 snap 版,改用 apt 或脚本方式。

还有一个隐蔽的问题:如果你是通过sudo apt install dotnet-sdk-8.0装的,但dotnet命令位于/usr/bin/dotnet里,普通用户执行没问题,但用 sudo 执行时如果 sudo 环境没保留用户 PATH,也可能报 command not found。这种情况可以用sudo -i或直接sudo /usr/bin/dotnet验证。

5.3 运行应用时报缺 Microsoft.AspNetCore.App

It was not possible to find any compatible framework version The framework 'Microsoft.AspNetCore.App', version '8.0.0' was not found.

这个报错十有八九是只装了dotnet-runtime-8.0没装aspnetcore-runtime-8.0。不需要重新安装 SDK,执行:

sudo apt install -y aspnetcore-runtime-8.0

然后验证:

dotnet --list-runtimes

应该能看到Microsoft.AspNetCore.App 8.0.x这一行。如果运行环境里同时有多个 .NET 大版本(比如 6 和 8),发布时把目标框架编错也会出现类似问题。用dotnet publish -f net8.0指定目标框架可以避免。

5.4 多版本冲突:解决 .NET 6 和 .NET 8 共存

我见过不少开发者用 apt 先装了 .NET 6,再装 .NET 8,结果用dotnet --list-sdks看到两个版本都在,但dotnet run总是跑在低版本上。这是因为没有指定启动版本。

解决方案就是前面提到的global.json。每个项目根目录放一个,Git 提交到代码库以后,所有开发者和 CI 服务器都会自动用相同版本构建,这个习惯值得从第一天开始养成。

dotnet new globaljson --sdk-version 8.0.100 --roll-forward latestFeature

5.5 环境变量 LD_LIBRARY_PATH 的坑

这个问题比较隐蔽。如果你从源码编译过某些原生依赖(比如 openssl),系统里的LD_LIBRARY_PATH被改写,.NET 可能加载不了 ICU 或者 SSL 库,报错为:

Process terminated. Couldn't find a valid ICU package installed on the system.

有一些教程建议装libicu后设置export DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1。我得提醒一句:这个变量能绕过问题,但等于关闭了全球化功能。如果你的应用需要处理日期格式化、Unicode 排序、时区转换,关掉这个会引发诡异行为,比如日期格式错乱、大小写转换异常。正确做法是保证libicu70已安装,并清除掉LD_LIBRARY_PATH里的无效项。

5.6 磁盘空间不足

SDK 完整安装后占用约 500MB 到 1GB 空间。如果服务器的/分区比较小,装完可能会看到No space left on device。

df -h

如果确实空间紧张,可以只装 ASP.NET Core Runtime(大约 80MB),用本地开发机发布好自包含应用,再拷贝到服务器上运行。自包含发布方式能把 .NET 运行时打进发布包里,服务器上完全不需要装 .NET,之前见过一些嵌入式场景没有外网权限,就把整个发布目录打包传上去直接用。

5.7 官方易踩坑速查表

现象原因解决办法
apt update 时报密钥错误微软 GPG 密钥缺失或损坏重新下载microsoft.asc到 trusted.gpg.d
dotnet 命令找不到PATH 未配置export PATH 或重开终端
运行 Web 应用报缺 ASP.NET Core只装了 runtime安装aspnetcore-runtime-8.0
编译时 SDK 版本不对无 global.json项目中添加 global.json
服务启动后被 killOOM 或 systemd 配置不对查看 journalctl 日志,检查 Restart 参数
访问外部 API 报 SSL 错误缺 ca-certificatessudo apt install -y ca-certificates

6. 离线安装与自包含发布的补充方案

在一些内网环境、银行/企业服务器(不连外网)里装 .NET,标准安装流程完全不可行。这时有两种替代方案,都值得记录。

6.1 离线安装包方式

在有网络的环境下载好 .deb 包,然后拷贝到内网机器上安装。微软源里的包可以直接用apt download抓取:

apt download dotnet-sdk-8.0 aspnetcore-runtime-8.0

会得到几个.deb文件,传到内网机器后执行:

sudo dpkg -i *.deb

注意依赖关系:.deb不能自动拉取依赖,所以还需要把libicu70等依赖也一并下载。如果内网机器用的是离线镜像源,也可以用apt install在线安装依赖,只对 .NET 包做 dpkg 安装。

6.2 自包含发布

对于应用开发者来说,自包含发布是最省心的一种部署方式。发布命令:

dotnet publish -c Release -r linux-x64 --self-contained true /p:PublishSingleFile=true

这一条命令会生成一个包含运行时和应用的目录(或单文件),拷贝到任何 x64 Linux 服务器上都能直接运行。目标服务器完全不需要装 .NET。

linux-x64对应 x86_64 架构,linux-arm64对应 ARM 架构。发布平台必须和目标机器架构一致,不能用 x64 的发布包跑在 arm64 上,反过来也一样。

自包含的代价是体积:单文件大概在 60-80MB,而框架依赖发布只有 2-10MB。但有的时候这个体积换来的省心程度是值得的。

我在某个离线项目里把整套 .NET 8 应用做成自包含单文件,拷贝到目标机器上直接./app就起来。目标机器连libicu都不用装,只要内核版本不太旧就行。

7. 我踩过且不想让你再踩的几个坑

最后分享三个特别容易忽略的细节,都是真金白银的教训。

第一个是时区问题。.NET 在 Linux 上获取本地时间依赖 ICU 和 tzdata。如果目标机器的/etc/localtime没配好,DateTime.Now会返回 UTC 时间。部署完应用最好看一眼日志里的时间戳,如果和本地时间差了 8 个小时,先查系统时区而不是改代码:

timedatectl set-timezone Asia/Shanghai

第二个是运行账号权限。不要图省事用 root 跑 .NET 服务。我见过有团队用 root 跑 Web 应用,结果应用某个接口存在路径穿越漏洞,黑客直接读了 /etc/shadow。正确做法是创建专用账号,只给应用目录的读写权限:

sudo useradd -m -d /var/www/yourapp yourappuser sudo chown -R yourappuser:yourappuser /var/www/yourapp

然后在 systemd 服务文件的[Service]段加上User=yourappuser和Group=yourappuser。

第三个是诊断工具。装完 .NET 以后,dotnet-counters、dotnet-dump这些全局工具建议顺手装上。线上应用 CPU飙到 100%、内存只增不减的时候,有没有这些工具决定了你要排查一天还是一小时:

dotnet tool install --global dotnet-counters dotnet tool install --global dotnet-dump

等真正出事时你会感谢此刻装的工具。举个真实例子,某应用的托管服务内存持续上涨,用dotnet-counters monitor --process-id <pid>盯着gc-heap-size和working-set两个计数器,十分钟就定位到是某静态集合无限增长导致的,这比靠猜靠谱太多。

Ubuntu 22.04 装 .NET 这件事本身不难,难的是装完之后让它稳定地跑起来。希望这篇记录能帮你少走一段弯路。如果安装过程中遇到什么奇怪的报错,欢迎对照上面的排查表逐条过一遍,大概率能找到答案。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询