早前记录过在 CentOS 中部署 ASP.NET Core 网站,现在宝塔面板已经直接支持创建 .NET 网站了,再次记录一下。
本文环境:VS2022、.NET 9、宝塔面板v11。
一、在 VS 中创建一个 .NET 9 网站,项目名称例:WebApplication1
发布选项:
关于“生成单个文件”选项,若勾选,发布后的项目启动文件不带后缀名(.dll),尝试在宝塔面板 v11.0.0 中无法顺利启动。
二、在 Linux 服务器上创建目录:/www/wwwroot/WebApplication1/
将发布后的文件上传到这个目录上,最终的文件结构如下:
三、进入宝塔面板,在 网站-Net项目 中安装 .Net环境管理,这里以 9.0.201 为例:
四、添加项目
项目名称:随意
运行路径:项目所在目录,本例中为:
/www/wwwroot/WebApplication1
启动命令:使用 dotnet 命令,dotnet 命令可以不使用全路径(/www/server/dotnet/9.0.201/dotnet),第一个参数是项目启动文件,--urls 是反向代理的服务器和端口。服务器名可以是*也可以是localhost。例:
dotnet WebApplication1.dll --urls=http://*:5000
项目端口:与启动命令中的端口号一致
开机启动:一般会勾选
启动用户:尽量用 www,最小权限原则。
正常情况下,网站已启动,如果未启动,检查配置,根据报错内容排查问题。
五、配置网站
添加域名、SSL 证书等。
友情提示:
网站版本更新重新发布后,需要手动重启网站才能生效,习惯于发布到 IIS(会自动生效)的同学要特别注意。
新手尽量以空项目或默认项目来部署,避免因特殊原因导致一系列其它问题。
nginx 反向代理并不会转发所有 Header,需要手动配置。

nginx.conf 中使用 resolver 设置了 DNS 后,nginx 会遵循这个指令,而不是直接使用系统的 hosts 配置。
所以,如果 resolver 设置了外部 DNS(譬如一般的公共 DNS),在 access_by_lua_block 中使用 resty.http 库发起对包含域名的网址的请求,会忽略 hosts 的设置。
解决方案:
1、修改 resolver 指向为本机 DNS。(AI 推荐,实测无效,可以研究一下配合 dnsmasq 使读取 hosts)
查看本机 DNS 的命令:
cat /etc/resolv.conf
或
nmcli dev show | grep DNS
2、request_uri 中直接请求带 IP 地址的网址。
要求这个网站允许直接使用 IP 地址访问。
需要禁用 SSL 验证(ssl_verify = false)。

在 access_by_lua_block 代码块中实现远程鉴权:
#鉴权-START #resolver 223.5.5.5; # cat /etc/resolv.conf access_by_lua_block { local http = require("resty.http") local httpc = http.new() httpc:set_timeout(500) -- 连接超时 local res, err = httpc:request_uri("https://鉴权地址/", { method = "GET", headers = { ["X-Real-IP"] = ngx.var.remote_addr, ["User-Agent"] = ngx.var.http_user_agent, ["X-Forwarded-Host"] = ngx.var.host, ["X-Forwarded-Uri"] = ngx.var.request_uri, }, ssl_verify = false, -- 禁用 SSL 验证 timeout = 500, -- 读取超时 }) if not res then ngx.log(ngx.ERR, "Failed to request: " .. err) end if res and res.status == 403 then ngx.exit(ngx.HTTP_FORBIDDEN) -- return ngx.redirect("https://一个显示403友好信息的页面.html") end } #鉴权-END
注意更改接口地址和友情显示 403 页面地址。
本示例仅捕获 403 状态码,500、408 等其它异常情况视为允许访问,请根据业务需求自行添加状态码的判断。
若超时也会进入
if not res then
代码块。建议将此代码部署在 nginx 主配置文件的 http 代码块中(宝塔面板中的路径:/www/server/nginx/nginx/conf/nginx.conf),如果你只想为单个网站鉴权,也可以放在网站配置文件的 server 块中。
若鉴权接口在私网中,建议将鉴权接口域名和私网 IP 添加到 hosts 文件中。
附
直接输出字符串
ngx.header.content_type = "text/plain"; ngx.say("hello world!")
输出到日志
ngx.log(ngx.ERR, "Response status: " .. res.status)
日志在网站的 站名.error.log 中查看。
宝塔面板查看方式:日志 - 网站日志 - 异常
若想获取服务器 CPU 使用率等信息并传递给远程鉴权接口,请参考此文。
常见问题
no resolver defined to resolve
原因:没有定义 DNS 解析器
解决方法:在 http 块或 server 块中添加
resolver 8.8.8.8 valid=30s;
,推荐使用接入商自己的公共 DNS,如果走内网,推荐使用本机 DNS。unable to get local issuer certificate
原因:没有配置 SSL 证书信息
解决方法:添加 request_uri 参数:
ssl_verify = true, -- 启用 SSL 验证 ssl_trusted_certificate = "证书路径", -- 指定 CA 证书路径
或
ssl_verify = false, -- 禁用 SSL 验证
若您不想用 lua,可以用 nginx 原生自带的 auth_request 模块来实现。

本文记录于 2024 年 11 月。
升级前 | 期望(最新正式版) | 最终选择 | |
操作系统 | Alibaba Cloud Linux 3 | Alibaba Cloud Linux 3 | Alibaba Cloud Linux 3 |
管理面板 | 宝塔面板 Linux 版 9.2.0 | 宝塔面板 Linux 版 9.2.0 | 宝塔面板 Linux 版 9.2.0 |
Web 服务 | nginx 1.24 | nginx 1.24 | nginx 1.24 |
脚本语言 | PHP 7.4 | PHP 8.2 | PHP 8.2 |
数据库 | PolarDB MySQL 5.6 | RDS MySQL 9.1 | PolarDB MySQL 8.0 |
论坛程序 | Discuz! X3.4 GBK | Discuz! X5.0 | Discuz! X3.5 UTF8 |
版本选择原因:
Discuz! X5 刚刚发布,生态尚未成熟,考虑到是老论坛升级,所以选择 X3.5。
Discuz! X3.5 目前最高支持 PHP 8.2、MySQL 8.0。
完整升级步骤:
购买新的 ECS、PolarDB,具体环境配置步骤参此文;
安装宝塔面板并配置;
安装 nginx 及 PHP;
创建网站、配置 SSL、伪静态、防盗链、可写目录禁执行、仅允许部分入口文件执行等(.conf);
配置 hosts;
如果是正式升级阶段,关闭论坛,防止产生新数据。
备份原网站程序、PolarDB 数据库;
PolarDB 创建快照。
测试阶段:从快照还原到新实例(MySQL 5.6 不能直接恢复到 MySQL 8.0),然后从 5.6 迁移到 8.0.x;
正式阶段:全量模式直接从原实例迁移到 8.0.x,若增量模式且存在触发器,建议从快照还原;
上传原网站程序到新的站点目录下;
按 Discuz! X 升级文档升级 X3.4 至 X3.5;详情见下文 ↓;
升级完成后切换到 PHP 8;
配置 OSS、Redis、更新缓存等;
测试论坛基本功能是否正常;检查附件是否能够正常上传;检查附件是否正常显示;全面检查控制台配置;
逐个开启插件并检查兼容性(短信通、马甲引擎等);
按二开备忘录逐个按需进行二开;
逐个修改调用论坛接口的项目及直接调用论坛数据库的项目;
调试 MAGAPP 接口;
尝试强制 https 访问;
将以上所有修改后的程序保留备份;发布升级公告并关闭论坛;重复以上步骤;修改域名解析;开启论坛;
配置 IP 封禁、定时器、日志、自动备份、配置其它 ECS 的 hosts 等;
查看搜索引擎中收录的地址,是否有无法访问的情况;
尝试将历史遗留的本地附件全部转移到 OSS;
建议 在新服上创建 3 个网站:
第 1 个用来尝试迁移、升级、二开,并测试所有功能;
第 2 个用来再次重复这些步骤,最终保留程序代码及 UGC 文件,作为最终的网站程序,以正式域名作为网站根目录路径;
第 3 个用来正式升级,主要功能是升级最新数据库。完成后修改第 2 个网站的数据库连接指向到最新的数据库,差异化同步新增的 UGC 文件到第 2 个网站。
Discuz! X 升级步骤及注意点:
因 PolarDB MySQL 不支持压缩,所以应移除 Discuz! 和 UCenter 代码中所有的 MYSQLI_CLIENT_COMPRESS,将 , MYSQLI_CLIENT_COMPRESS 替换为 /*, MYSQLI_CLIENT_COMPRESS*/。
升级前务必先修改 ./config/ 目录下的数据库/缓存连接信息,以防出现新站连接老库的情况;
按官方文档进行升级,升级前先修改一下:
【UC】先升级 UCenter 1.6 至 1.7。
将 /uc_server/data 权限改为 777 并递归。
打开 update_ucenter_adult.php 修改 $limit 值为 10000 以免执行超时。
因通信失败的“发送通知失败”可以直接改 URL 参数跳过就完成了,原因可能是因数据量大,发送改名通知执行改名时超时。等DZ升级完成后UC会自动重试改名通知,或单独写个 php 文件将 UCenter 的用户名同步到应用的数据库。
【DZ】运行到 /install/update_adult.php?step=innodb&table=pre_common_member_grouppm 时报错:Duplicate key name 'gpmid' ALTER TABLE common_member_grouppm ADD INDEX gpmid(gpmid);
解决方法:先删除索引,因保存时提示失败,应同时取消 gpmid 字段的自增,转换成功后再设置自增。
【DZ】运行到 /install/update_adult.php?step=file 一片空白而停止
正在解决
【问题】common_menber 表用户名字段编码转换失败
原因是部分用户名包含特殊字符(如全角空格),用以下语句查看两个表同一 uid 的不同用户:
SELECT u.uid, u.username AS ucenter_username, c.username AS common_username FROM pre_ucenter_members u JOIN pre_common_member c ON u.uid = c.uid WHERE u.username <> c.username
Discuz! 用户登录都是以 UCenter 中的用户名为主,所以可以写个小程序直接将 pre_ucenter_members 的用户名同步到 pre_common_member 中,另外 pre_ucenter_members 的用户数少于 pre_common_member 是正常的。若 pre_common_member_archive 表遇到错误同理。
如果用户登录慢,或打开 UCenter 慢,是因为 UC 正在通知 DZ 改用户名,每次打开一个页面会更改一个用户名,具体可以查看 pre_ucenter_notelist 中 closed 为 0 的队列,或进入 UC 后台-数据列表-通知列表 查看。
【问题】发布主题遇到错误:(1062) Duplicate entry '*' for key 'pid'
【原因】forum_post 中的 pid 不是自动增长的,而是由表 forum_post_tableid 中自动增长的 pid 生成的。如果生成的 pid 值已在 forum_post 表中存在,则会出现此错误。
【解决】迁移数据库时应关闭论坛,以防止 forum_post 表有新数据插入。
【问题】打开帖子页面 ./thread-***-1-1.html 显示 404 Not Found,而 ./forum.php?mod=viewthread&tid=*** 可以正常打开
【原因】未配置伪静态(可在宝塔面板中选择)
【问题】打开 UCenter 时报错:UCenter info: MySQL Query Error SQL:SELECT value FROM [Table]vars WHERE name='noteexists'
【解决】打开文件 ./uc_server/data/config.inc.php 配置数据库连接
【问题】打开登录 UCenter 后一片空白
【解决】将目录 ./uc_server/data/ 设为可写
需要将原来安装的插件文件移回 ./source/plugin/ 目录,并设置可写;
界面-表情管理,界面-编辑器设置-Discuz!代码
后续 Discuz! X3.5 小版本升级注意事项:
确认插件是否支持新版本(如短信通)
先创建一个新网站测试二开代码
保留 /config/、/data/、/uc_client/data/、/uc_server/data/、/source/plugin/,其它移入 old
上传文件
移回其它需要的文件,如:
-- 勋章/loading/logo/nv 等:/static/image/common/
-- 表情:/static/image/smiley/
-- 水印:/static/image/common/watermark.*
-- 风格:/template/default/style/t2/nv.png 等
-- 默认头像:/uc_server/images/noavatar_***.gif
-- 根目录 favicon.ico 等
-- 及其它非 DZ 文件
再次检查可写目录的写入权限和禁止运行 PHP 效果。

如果手机开启了自动横屏,那么网页就会跟着旋转,导致网页宽度变大,高度变小,页面布局不友好,我们可以通过判断屏幕方向 api,用 css 设置旋转。
HTML:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
<title>保持竖屏示例</title>
</head>
<body>
<div id="app">
<div class="page">
<div>把手机横过来试试</div>
<div>页面永远保持竖屏方向</div>
</div>
</div>
</body>
</html>
CSS:
body { margin: 0; }
.page { text-align: center; padding-top: 100px; }
@media screen and (orientation: landscape) {
body { width: 100vh; height: 100vw; overflow: hidden; }
.page.landscape-primary { transform: rotate(-90deg); transform-origin: top left; width: 100vh; height: 100vw; position: absolute; top: 100%; left: 0; }
.page.landscape-secondary { transform: rotate(90deg); transform-origin: top left; width: 100vh; height: 100vw; position: absolute; top: 0; left: 100%; }
}
JS:
function fn_set_orientation() {
var orientation = window.screen.orientation || window.screen.mozOrientation || window.screen.msOrientation;
const div = document.querySelector('.page');
console.log(orientation.type)
switch (orientation.type) {
case 'landscape-primary': {
div.classList.remove('landscape-secondary');
div.classList.add('landscape-primary');
break;
}
case 'landscape-secondary': {
div.classList.remove('landscape-primary');
div.classList.add('landscape-secondary');
break;
}
default: {
div.classList.remove('landscape-primary');
div.classList.remove('landscape-secondary');
break;
}
}
}
window.addEventListener("orientationchange", function () {
fn_set_orientation();
});
fn_set_orientation();
示例:
https://xoyozo.net/Demo/Orientation

客户端:Failed to connect to host.
服务端:无任何日志
原因:可能是域名、IP 或端口有误,或端口未开放。
客户端/服务端:530 Login incorrect.
原因:用户名或密码错误。
客户端/服务端:503 Use AUTH first.
原因:服务端开启了 FTPS 协议(FTP over TLS),且禁止了 FTP 协议(plain FTP)。
解决方法:不建议服务端恢复不安全的 FTP 协议,客户端应配置 client.Config 的 EncryptionMode、DataConnectionEncryption、SslProtocols、ValidateCertificate 等参数。
客户端/服务端:530 This server does not allow plain FTP. You have to use FTP over TLS.
原因:服务端启用了 FTPS。
解决方法:给 client.Config.EncryptionMode 正确的赋值(FtpEncryptionMode.Explicit 或 FtpEncryptionMode.Auto)。
服务端:[Error] TLS session of data connection not resumed.
原因:证书版本有误。
解决方法:给 client.Config.SslProtocols 赋值正确的 TLS 版本。
服务端:521 PROT P required
原因:服务端开启了“Require TLS session resumption on data connection when using PROT P”
解决方法:client.Config.DataConnectionEncryption = true;
客户端/服务端:450 TLS session of data connection has not resumed or the session does not match the control connection
原因:TLS 与控制连接不匹配,即服务端开启了“Require TLS session resumption on data connection when using PROT P”
解决方法:我暂时还不知道原因,临时关闭了“Require TLS session resumption on data connection when using PROT P”(不建议)。该问题出现在 FileZilla Server 0.x 版本,在 1.x 版本中没有该配置选项。


无法完成向远程代理 URL 发送请求。基础连接已经关闭:发送时发生错误。
解决办法:
IIS中“管理服务”的SSL证书过期了,停止,选择新证书,应用,启动。
如果证书名称都相同(例如“alias”),可以先选中一个启动,在 VS Web 部署的设置中验证连接,弹出“证书错误”,点击“查看详细信息”,检查“颁发给”中的域名是否正确,正确的话安装证书。


普通的 nginx http 反向代理 https 时是需要配置证书的,但我们又不可能由源域名的证书,所以要使用 nginx 的 stream 模块。普通的 nginx 反向代理属于第七层代理,而 stream 模块是第四层代理,通过转发的 tcp/ip 协议实现高功能,所以不需要证书。
我们这里以使用宝塔安装的 nginx 为例,其实其他系统也是类似,只要找到编译的 nginx 的源码目录就行了
编译前先将已经安装的 nginx 文件进行备份
通过 ps 命令查看 nginx 文件的路径。以下所有步骤都以自身 nginx 路径为准
# ps -elf | grep nginx
# cd /www/server/nginx/sbin/
# cp nginx nginx.bak
然后查看当前 nginx 编译的参数
/www/server/nginx/sbin/nginx -V
将 ./configure arguents:之后的内容复制到记事本备用(备注:我们这里其实使用的是 Tengine-2.3.1,所以下面的编译参数可能跟普通 nginx 不是很一样)
内容如下:
--user=www --group=www --prefix=/www/server/nginx --add-module=/www/server/nginx/src/ngx_devel_kit --with-openssl=/www/server/nginx/src/openssl --add-module=/www/server/nginx/src/ngx_cache_purge --add-module=/www/server/nginx/src/nginx-sticky-module --add-module=/www/server/nginx/src/lua_nginx_module --with-http_stub_status_module --with-http_ssl_module --with-http_v2_module --with-http_image_filter_module --with-http_gzip_static_module --with-http_gunzip_module --with-ipv6 --with-http_sub_module --with-http_flv_module --with-http_addition_module --with-http_realip_module --with-http_mp4_module --with-ld-opt=-Wl,-E --with-pcre=pcre-8.42 --with-cc-opt=-Wno-error --add-module=/www/server/nginx/src/ngx-pagespeed
进入 src 目录
cd /www/server/nginx/src
我们在上面的内容中加入两个参数
./configure --user=www --group=www --prefix=/www/server/nginx --add-module=/www/server/nginx/src/ngx_devel_kit --with-openssl=/www/server/nginx/src/openssl --add-module=/www/server/nginx/src/ngx_cache_purge --add-module=/www/server/nginx/src/nginx-sticky-module --add-module=/www/server/nginx/src/lua_nginx_module --with-http_stub_status_module --with-http_ssl_module --with-http_v2_module --with-http_image_filter_module --with-http_gzip_static_module --with-http_gunzip_module --with-ipv6 --with-http_sub_module --with-http_flv_module --with-http_addition_module --with-http_realip_module --with-http_mp4_module --with-ld-opt=-Wl,-E --with-pcre=pcre-8.42 --with-cc-opt=-Wno-error --add-module=/www/server/nginx/src/ngx-pagespeed --with-stream --with-stream_ssl_preread_module
并执行它
然后
make && make install
重启 nginx
service nginx restart
nginx -V 看看模块是不是加载了
新建个站点,配置反向代理
卸载
使用 nginx.bak 文件替换掉自编译的 nginx 文件,替换后重启 nginx。