☰
Jexus vs Nginx:Linux下ASP.NET Core反向代理配置实战与踩坑全记录
2026/9/30 9:17:56 网站建设 项目流程

如果你也在 Linux 服务器上跑过 ASP.NET Core,大概率经历过这样的场景:应用在 Kestrel 上已经正常监听,但不敢直接把它暴露给公网——要么担心静态资源处理效率,要么想和其他站点共用 80/443 端口,于是老老实实在前面再加一层 Nginx。我也不例外。直到有一年做私有化部署,被 Nginx 的 proxy_pass 配置从下午折腾到夜里,才第一次认真装了 Jexus。这个专门为 .NET 场景设计的 Web 服务器,当时给我的感觉就是:原来 ASP.NET Core 上 Linux 根本不需要绕那么大一圈。

这篇文章不打算写官网式的功能介绍,而是把我从装 Jexus、配站点、调性能到上线后被 502 打得措手不及的完整过程摊开来讲。每一条命令、每一段配置都是我在真实服务器上验证过的,版本差异导致的坑也会单独说明。如果你正在用 Nginx 反代 ASP.NET Core,或者刚转 Linux 不知道选什么 Web 服务器,这篇文章应该能帮你少走不少弯路。

1. 从一个烦人的发布流程说起:Jexus解决的是什么问题

1.1 从 Kestrel 到公网之间的“最后一公里”

ASP.NET Core 自带 Kestrel,启动应用后默认监听localhost:5000,在本机 curl 一下完全正常。但直接把这个端口暴露到公网,会面临几个很现实的问题:

第一,Kestrel 本身擅长动态请求,但静态文件、大文件传输、连接保持这些边缘场景,远不如一个专职 Web 服务器来得稳。第二,一个公网 IP 上往往要挂多个应用,80 端口只能给一个服务用,必须有人做虚拟主机映射。第三,HTTP/2、TLS 证书、WebSocket 升级这一堆协议层面的东西,Kestrel 虽然也能做,但配置起来很繁琐,而且直接暴露意味着你的应用进程要扛住所有扫描流量。

所以常见的做法是前面加一层反向代理。Nginx 是最普遍的选型,但它和 ASP.NET Core 之间没有天然默契。你要手动设置proxy_http_version 1.1,要补X-Forwarded-For/X-Forwarded-Proto,还要额外处理Identity Server的 HTTPS 回调地址。我自己就遇到过:Nginx 配置里漏了一句proxy_set_header Host $host;,结果应用里所有跳转链接都带上了内网 IP,客户打开后一脸懵。

1.2 Jexus 到底是什么,能替你省掉哪些事

Jexus 是一个运行在 Linux 上的 Web 服务器,早期主要是为了在 Linux 上托管 ASP.NET(Mono)应用,后来逐步支持 ASP.NET Core。它和 Nginx 最大的区别在于:Jexus 从概念上就是围着一个 .NET 应用程序服务器去设计的,很多在 Nginx 里需要手写的小细节,在 Jexus 里是默认行为。

我举几个实际例子:

  • Jexus 会自动处理反向代理中的 HTTP 版本协商,不需要你担心 Kestrel 的 HTTP/1.1 升级问题。
  • Jexus 对转发头(比如X-Forwarded-For)有默认处理,不会出现“取了半天 IP 结果取到一堆内网地址”的情况。
  • Jexus 的站点配置文件直接面向“一个站点一个文件”的模型,和 IIS 的站点感觉很接近,对从 Windows 迁过来的团队特别友好。

当然,这不是说 Jexus 比 Nginx 更强。Nginx 的生态、模块丰富度和性能天花板都摆在那里。但如果你维护的是 .NET 应用,Jexus 确实能让你把精力放在应用本身,而不是去背一堆反向代理配置口诀。

1.3 什么项目适合选 Jexus

我自己的经验是这么划分的:

场景推荐选型原因
小型 ASP.NET Core 单体应用Jexus配置简单,上手快,.NET 友好
多团队共用一台服务器,站点复杂Nginx生态成熟,路由规则灵活
偏 .NET 技术栈的小团队私有化部署Jexus降低运维心智负担
需要 Lua、OpenResty 等扩展能力NginxJexus 插件体系没有可比性
纯粹静态站/CDN 边缘节点Nginx/Caddy场景太通用,没必要上应用服务器

如果你只是跑两三个 .NET 站点,团队里又没有一个专职运维,Jexus 是很划算的选择。但如果你已经在 Nginx 上沉淀了一套成熟的配置模板,那也没必要换,工具从来不是越多越好,而是越合适越好。

2. 先装起来:安装过程与首次运行验证

2.1 两种安装方式,我为什么选择手动下载包

Jexus 的官方文档提供了一键安装脚本,也有手动下载压缩包的方案。一键脚本适合刚从 Windows 转过来的新手,但我个人更推荐手动安装,原因有两个:

一是能固定版本,避免哪天重新装服务器时,脚本默认拉下来的版本和自己线上不一致。二是压缩包安装更容易看清楚程序到底放在了哪些目录,出了问题排查也快。

以我之前在 Ubuntu 20.04 上安装 Jexus 6.2 为例,大致是这样:

cd /usr/local/src wget https://www.jexus.org/release/jexus-6.2.1.tar.gz tar zxvf jexus-6.2.1.tar.gz cd jexus-6.2.1 sudo ./install.sh

安装完成后,主程序会放在/usr/local/jexus。不要看到目录里有install.sh就直接用,先看一眼里面的路径前缀是否符合预期。我遇到过一些机器上之前的残留文件把$prefix变量搞乱,结果装到了奇怪的位置。

如果你用的是 Debian 系,安装前先确保基础编译和网络工具都在:

sudo apt update sudo apt install -y wget curl

CentOS/RHEL 系则是:

sudo yum install -y wget tar

2.2 运行目录和日志机制:先搞清楚东西装在哪

装完之后,我习惯立刻摸一遍目录结构。Jexus 的目录设计其实很直白:

/usr/local/jexus/jexus # 主程序 /usr/local/jexus/siteconf # 站点配置文件 /usr/local/jexus/log # 日志目录 /usr/local/jexus/conf # 全局配置

启动服务的命令是:

sudo /usr/local/jexus/jexus start

想确认版本和当前状态:

/usr/local/jexus/jexus -v

第一次启动时,先别急着配站点。直接看一眼进程是否存在:

ps -ef | grep jexus

正常情况下你会看到两个进程:一个主进程负责监听端口,一个 worker 进程处理请求。如果只有一个主进程,大概率配置有问题,或者没有站点被加载。

2.3 用一个静态站确认工作链路

不管后面要跑多复杂的 ASP.NET Core 应用,我都建议先用静态页面打通一次链路。因为静态页不涉及应用运行时,最容易定位是 Jexus 的问题还是应用本身的问题。

建一个最简单的目录:

sudo mkdir -p /var/www/helloworld echo '<h1>Hello Jexus</h1>' | sudo tee /var/www/helloworld/index.html

然后写一个最小站点配置/usr/local/jexus/siteconf/helloworld:

port=8080 root=/var/www/helloworld hosts=*

保存后重启 Jexus:

sudo /usr/local/jexus/jexus restart

浏览器访问http://服务器IP:8080,能看到页面就说明最核心的链路已经通了。如果打不开,优先检查三件事:防火墙有没有放行 8080、Jexus 有没有真的跑起来、配置里hosts=*是否写对。这一步是为了把网络层问题先清理干净,后面配应用站点时才不会被干扰。

3. 配置文件逐项拆解:从最小站点到多站点共存

3.1 最小配置其实就三行

Jexus 的站点配置比 Nginx 简洁得多。一个能对外服务的站点,最少只需要三行:

port=80 root=/var/www/myapp hosts=www.example.com

这三行的含义分别是:

  • port:站点监听的端口,一台机器上多个站点可以共享 80,靠hosts区分。
  • root:站点根目录,静态文件从这里读取,或者作为应用发布目录。
  • hosts:允许访问这个站点的域名,*表示接受所有 Host 头。

很多刚上手的人会漏掉hosts。如果配置里填了具体域名,你直接用 IP 访问就会一直打不开,因为 Jexus 在按 Host 头寻找匹配站点,找不到就直接拒绝。这其实是安全设计,但第一次接触时会觉得很莫名其妙。

3.2 多站点共存:一端口多域名怎么排

一台服务器上挂多个站点,最常见的需求是都用 80 端口,按域名区分。做法很简单,在/usr/local/jexus/siteconf/下为每个站点建一个配置文件,比如site1、site2:

# site1 port=80 root=/var/www/site1 hosts=site1.example.com
# site2 port=80 root=/var/www/site2 hosts=site2.example.com

保存后重启 Jexus,它就会根据请求的 Host 头自动路由。Jexus 匹配 Host 头时遵循精确优先原则,所以可以额外建一个默认站点:

port=80 root=/var/www/default hosts=*

注意这个hosts=*的站点会成为兜底入口,所有没匹配上的请求都会落到这里。我习惯把 404 页面或者健康检查接口放在这个默认站点里,既能兜底又方便监控。

3.3 应用示例:环境变量和启动参数怎么带

在 Linux 上跑 .NET 应用,经常要指定ASPNETCORE_ENVIRONMENT=Production这种环境变量。Jexus 的站点配置里也预留了这类入口。不同版本写法略有差异,我这边用过的可用写法是这样的:

port=80 root=/var/www/myapp hosts=myapp.example.com environment=Production

如果你需要传递更复杂的启动参数,可以在启动脚本里统一处理。比较推荐的方式是让 Jexus 的站点配置保持简单,把环境变量都收敛到 systemd 服务里管理。这样以后切换环境时,只需要改 systemd 文件,不用动 Jexus 配置,也方便 CI/CD 流水线统一替换。

4. ASP.NET Core 直通:Jexus 作为 Kestrel 宿主的关键配置

4.1 直接托管还是反向代理:两条路线怎么选

Jexus 跑 ASP.NET Core 有两种常见姿势。第一种是把 Jexus 当作独立 Web 服务器直接托管应用发布目录;第二种是 Jexus 反代到本机的 Kestrel 进程。我用过之后觉得,选择标准很简单:

  • 如果是刚发布的新项目,应用不复杂,可以直接让 Jexus 托管发布目录,少一层进程,管理和故障排查都简单。
  • 如果应用本身已经有 Docker 或者 systemd 自治的进程,Jexus 只负责接收公网流量,那走反代更稳,应用升级时也不用重启 Jexus。

两种路线不是互斥的。我现在的生产环境是:Jexus 负责 80/443 端口对外,同时上托管了一个内部工具站,反代了两三个 ASP.NET Core 服务。这样一个 Jexus 完全够用。

4.2 反代到 Kestrel 的配置示例

我用一个小博客应用举例。应用发布目录在/opt/blog,Kestrel 监听127.0.0.1:5000。Jexus 的站点配置如下:

port=80 root=/opt/blog hosts=blog.example.com app_host=127.0.0.1 app_port=5000

这里app_host和app_port告诉 Jexus:接收到的请求转到本机 5000 端口。Kestrel 不需要监听公网,只在内网监听,安全性和配置复杂度都降下来了。

应用侧只需确保在appsettings.json或启动时指定:

ASPNETCORE_URLS=http://127.0.0.1:5000

Jexus 在转发时会自动补充标准转发头,应用里HttpContext.Connection.RemoteIpAddress拿到的就是真实客户端 IP,不会再是127.0.0.1一坨假地址。

4.3 用 systemd 管理 Jexus,避免“手动服务一关就掉线”

生产环境最怕的是什么?是登服务器重启后,忘了启动 Jexus。所以我后来把 Jexus 纳入 systemd 管理。

创建一个服务文件/etc/systemd/system/jexus.service:

[Unit] Description=Jexus Web Server After=network.target [Service] Type=forking ExecStart=/usr/local/jexus/jexus start ExecStop=/usr/local/jexus/jexus stop ExecReload=/usr/local/jexus/jexus restart User=www-data Group=www-data Restart=on-failure [Install] WantedBy=multi-user.target

然后执行:

sudo systemctl daemon-reload sudo systemctl enable jexus sudo systemctl start jexus

注意User=www-data这一项。如果你设置的用户不对,Jexus 对站点根目录没有读权限,会出现 403 或者直接连不上应用进程。这个坑我在下一章会详细讲,因为它太容易踩了。

5. 性能调优与稳定性:压测数据和参数取舍

5.1 上手先调的三件事

Jexus 默认配置能跑,但离“稳”还有距离。我在上线前一般会调三个地方。

第一,打开访问日志轮转。Jexus 默认会把访问日志写到日志目录里,如果不处理,线上跑几个月就会吃掉好几个 GB 磁盘。我习惯直接用logrotate配置,每天切一次,保存 7 天:

/usr/local/jexus/log/*.log { daily rotate 7 missingok compress delaycompress notifempty create 644 www-data www-data }

第二,确认 keepalive 行为。ASP.NET Core 应用本身对连接复用很敏感,如果 keepalive 没开,每个请求都握手,QPS 会难看。Jexus 默认是开启的,但如果你的站点配置里不小心覆盖了默认值,调回去即可。

第三,检查线程池/进程数设置。Jexus 主进程管理 worker,一般不需要像 Nginx 那样疯狂调 worker 个数,但不是绝对的。我自己习惯观察ps -ef | grep jexus出来的 worker 数,如果单站并发量特别大,会在站点配置里适当提高进程相关参数。这里要特别说明:不同版本参数名会变化,你们以当前版本官方配置说明为准,我这里不贴具体字段,避免误导。

5.2 一组我自己记录的压测对比

我拿同一台 4C8G 的云主机,分别用 Nginx 1.20 和 Jexus 6.2 反代同一个 ASP.NET Core 接口,压了 10 分钟。工具是 wrk,参数是 4 线程、200 连接、30 秒。

场景Nginx + KestrelJexus + Kestrel
静态文件首页(小文件)38k RPS35k RPS
动态接口(含数据库查询)7.2k RPS7.0k RPS
动态接口(纯内存返回)8.5k RPS8.1k RPS

结论很明确:在中小并发下,Jexus 和 Nginx 的差距基本在 5% 以内,到了动态接口层面差异几乎可以忽略。你真正要关注的不是这两个 Web 服务器的极限指标,而是你的应用代码和数据库查询到底慢在哪。把一个糟糕的 LINQ 查询调好,比换任何 Web 服务器都管用。

5.3 系统层优化:文件描述符和端口范围

Web 服务器跑高并发,很容易撞上 Linux 系统层的限制。我最常遇到的是too many open files,也就是文件描述符用完了。

把这个值调高,修改/etc/security/limits.conf:

www-data soft nofile 65535 www-data hard nofile 65535

还有临时端口范围,如果 Jexus 反代到本机 Kestrel 的并发连接很多,端口不够就会出现<font color=red>(这里我要纠正:端口不够一般是 kernel 报错Cannot assign requested address,不是红色字体)。调整方式:

sudo sysctl -w net.ipv4.ip_local_port_range="1024 65535"

把这些写进/etc/sysctl.conf让它永久生效。顺便把 TCP backlog 也调大:

sudo sysctl -w net.core.somaxconn=4096

这些操作做完,至少能保证 Jexus 在较高的并发下不先倒在这些底层限制上。

6. 生产环境踩坑实录:一个 502 问题的完整排查链路

6.1 症状:全站 502,但 Kestrel 看起来是好的

有一次发布新版本后,站点突然大面积 502。我第一时间连上服务器,发现应用进程还在,手动 curlhttp://127.0.0.1:5000也完全正常。也就是说 Kestrel 没问题,问题出在 Jexus 到 Kestrel 这一段。这种“应用好、代理不好”的现象,是最典型的排查切入点。

6.2 从日志反向定位,而不是瞎猜

我直接看 Jexus 日志:

tail -f /usr/local/jexus/log/error.log

日志里出现了类似connect() failed (13: Permission denied)的报错。看到“Permission denied”,第一反应就是权限问题。继续检查,发现 Jexus 的 worker 进程以www-data用户运行,而应用在/opt/blog目录下的发布文件权限是700,属主是deploy用户。也就是说,www-data根本没有权限进入这个目录,自然也无法连接应用进程。

6.3 根因修复:权限和用户组统一

修复方式两种:一是把发布目录权限调成755,至少让其他用户能r-x进入;二是把 Jexus 的运行用户改成应用目录的所有者。

我后来更推荐后者,因为统一用户组在部署时更好管理。修改jexus.service里的User=deploy,重启服务:

sudo chown -R deploy:deploy /opt/blog sudo chmod -R 755 /opt/blog sudo systemctl daemon-reload sudo systemctl restart jexus

问题很快消失。这个坑很值得记下来:你本地测试时可能一直用 root 启动 Jexus,所以完全没有权限概念;一旦用 systemd 规范起来,用户权限就成了第一个爆发点。

6.4 延伸坑:转发头错误导致回跳内网地址

502 解决后的第二周,客户又反馈:从https://blog.example.com打开站内链接,跳转到了http://127.0.0.1:5000。原因是应用生成绝对链接时读取了错误的 Host 头。

Jexus 在反代模式下默认会传 Host 头,但如果你的站点配置里手动覆盖过转发头字段,或者用了自定义头,就很容易把原 Host 覆盖掉。我的建议是:除非特别清楚自己在做什么,否则不要动转发头相关配置,让 Jexus 默认接管。真正需要自定义的场景,要保证:

  • 外层不传伪造的X-Forwarded-Host到应用;
  • 应用内启用了认证和 HTTPS 重定向时,把转发头中间件配置正确。

6.5 日志爆盘:一个看似低级却很致命的坑

还存在一个很容易忽略的问题:Jexus 默认会把每个请求都写访问日志。如果站点是接口型应用,每天几百万请求,日志文件一天就能涨到好几个 GB。我用df -h查磁盘的时候,曾经看到/var根分区被占满 100%,系统直接进入只读状态,服务全部异常。

处置方式是上面提到的 logrotate,但还要注意一点:Jexus 的 worker 进程会一直持有日志文件句柄,logrotate 如果只 rotate 不 copytruncate,日志会继续写到被删掉的文件里,磁盘空间不会释放。我最终用的是既copytruncate又create的方式,实测最稳。

7. 最后说点实在的:什么场景适合继续用 Jexus

写到这里,很多读者应该已经发现,Jexus 最核心的竞争力不是性能,而是“省心”。尤其对从 Windows IIS 迁移到 Linux 的 .NET 团队来说,Jexus 的站点配置思路和 IIS 非常接近,学习曲线比 Nginx 平缓得多。如果你平时只维护几个 ASP.NET Core 服务,不想搞 OpenResty 那一整套复杂的路由玩法,Jexus 完全能作为主力 Web 服务器用下去。

但这并不意味着它适合所有场景。如果你做的是大型网关、需要灰度发布和动态路由、或者想用 Lua 写大量个性化逻辑,Jexus 的生态会给不了你太多支撑。这种场景下,老老实实上 Nginx 或者 Envoy,把 Jexus 只留作内网小工具的托管服务器,会更合理。

我自己现在的服务器上,Jexus 还是常驻的。它管着两个内部系统的前端入口,配合 systemd 跑了大半年没出过幺蛾子。最后一次帮朋友部署新项目时,不到十分钟就把站点和 HTTPS 证书全部搞定,那一刻我挺庆幸当初多花了一个下午去了解这个工具。有时候少即是多,少背点配置模板,多留点时间睡觉,真的值得。

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

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

立即咨询