LXC容器里安装3x-ui:官方脚本会怎么跑,端口转发、证书、BBR和IP限制要注意什么
在 Proxmox、LXD 或 Incus 的 LXC 容器里装 3x-ui,是个很常见的需求,尤其是一台宿主机上想开多个独立的小环境。但官方对这件事什么都没有写:我把仓库里的文档、install.sh、x-ui.sh 翻了一遍,没有任何关于 LXC、Proxmox、LXD、Incus 的内容,只有 Docker 有专门的处理。所以这篇的做法是:先读安装脚本,弄清楚它在 LXC 容器里会做什么,再讲容器环境下可能卡在哪,哪些是脚本源码能确认的,哪些是我没法验证的,分开写清楚。
先说明依据:脚本部分来自 MHSanaei/3x-ui 主分支的 install.sh 和 x-ui.sh(读到的最新提交是 2026-10-06);端口转发部分来自 LXD 和 Incus 官方文档里 proxy 设备和 unix-char 设备的页面。我没有在 LXC 容器里真的装过 3x-ui,容器相关的限制(权限、sysctl、iptables 这些)我只能按一般情况提示,没法逐项验证,文中会标明。Proxmox 网页界面里的具体操作我这里查不到官方文档,所以不写。
脚本在 LXC 里会怎么跑
先看一个关键事实:x-ui.sh 里只有一处环境判断,就是判断是不是 Docker:
- 看有没有
/.dockerenv文件,或者环境变量XUI_IN_DOCKER是不是true; - 是的话,主目录改成
/app,一部分命令的行为也会不同。
LXC 不会被当成特殊环境。所以在 LXC 容器里,脚本走的是和普通 Linux 服务器一样的路径:
- 要求用 root 运行;
- 读
/etc/os-release判断发行版,读uname -m判断架构(支持 amd64、386、arm64、armv7、armv6、armv5、s390x); - 用发行版自己的包管理器安装依赖:Debian 系装
cron curl tar tzdata socat ca-certificates openssl,Alpine 装dcron curl tar tzdata socat ca-certificates openssl,其他系统也有对应的列表。这意味着很精简的容器模板也不用先手装 curl,只要容器里的包管理器能联网; - 下载程序,把服务文件放到
/etc/systemd/system(Alpine 用 OpenRC,脚本里用rc-service启动)。
所以第一个条件是:容器里要有 systemd(或 Alpine 的 OpenRC),并且要能联网。常见的 Debian、Ubuntu 的 LXC 模板默认是带 systemd 的,但这一点我没有逐个模板验证。
第二个要弄清楚的:容器共用宿主机的内核
LXC 容器和宿主机共用同一个内核,这个特点会影响好几件事:
- 不能在容器里换内核。本站 XanMod内核搭配BBR3使用教程 里就提到,用
systemd-detect-virt查看,输出lxc的环境不适合装 XanMod。要用什么内核,得去宿主机上改; - BBR 能不能开,取决于宿主机内核。3x-ui 菜单里的 Enable BBR(见 x-ui、3x-ui和Xray怎么开BBR)做的是写 sysctl 配置再
sysctl -p,我读到的enable_bbr函数没有检查是不是在容器里。容器里能不能改net.*参数,取决于容器的权限设置,很多容器里这些参数是只读的,我没有实测,所以建议先在宿主机上确认内核支持并开好 BBR,容器里再用sysctl net.ipv4.tcp_congestion_control验证; - Fail2ban 和 IP 限制:见后面单独一节。
端口怎么通:这是 LXC 里最容易卡住的地方
3x-ui 装好之后,要让外网访问到面板和节点,需要这些端口通到容器里:
| 端口 | 用途 | 协议 |
|---|---|---|
| 面板端口 | 访问面板,装完后脚本会打印 | TCP |
| 各入站的端口 | 节点,按你建的入站来 | TCP,Hysteria2 这类是 UDP |
| 80 | 申请 Let’s Encrypt 证书时用 | TCP |
如果容器用的是桥接模式、有自己的公网 IP,那就按普通服务器处理,放行防火墙即可。如果容器在宿主机的 NAT 网络后面(比如 LXD 默认的 lxdbr0),就要在宿主机上做端口转发。
LXD / Incus 的做法:proxy 设备
LXD 官方文档里的 proxy 设备,就是干这个的。命令格式(来自文档):
1 | lxc config device add <容器名> <设备名> proxy listen=<类型>:<地址>:<端口>[-<端口>][,<端口>] connect=<类型>:<地址>:<端口> |
文档说明它支持 tcp <-> tcp 和 udp <-> udp 等类型。具体到 3x-ui,转发面板端口 2053 的写法大致是(端口换成你自己的):
1 | lxc config device add 容器名 xui-panel proxy listen=tcp:0.0.0.0:2053 connect=tcp:127.0.0.1:2053 |
几点说明,都来自文档:
- 节点用 UDP 的话(比如 Hysteria2),
listen和connect都写udp; - 端口可以写范围,格式是
<端口>[-<端口>][,<端口>],Hysteria2 的端口跳跃要转发一段端口时用得上,见 Hysteria2端口跳跃怎么配; - 文档还说有一个 NAT 模式(
nat=true),好处是能保留客户端的真实 IP;它只在宿主机同时是网关时可用(比如lxdbr0),LXD 文档要求这时目标实例的网卡要配静态 IP,listen地址要写宿主机上的具体 IP,不能用通配地址;Incus 文档写的是静态 IP 或 DHCP 动态地址都可以; - Incus 的命令把
lxc换成incus,语法一样。
默认的 proxy 模式(不带 nat=true)是起一个单独的代理连接,容器里的程序看到的客户端地址会变成转发进来的地址。对 3x-ui 来说,这会影响 客户端 IP 限制 这类依赖真实来源 IP 的功能,想保留真实 IP 就要考虑 NAT 模式。这是我根据文档说明推出的影响,没有实测。
Proxmox 的 LXC 具体怎么做端口转发(比如在宿主机上用 iptables 做 DNAT),我这里没有官方文档可依据,不写具体命令。
证书:脚本里和 NAT 有关的两个坑
安装时选 SSL 方式(见 手机搭建3x-ui 里的选项表),在 LXC 里要留意两点,都是读脚本得到的:
1. 80 端口。 选域名证书或 IP 证书,都要靠 80 端口做 HTTP-01 验证。脚本会问你用哪个端口做验证监听(默认 80,环境变量 XUI_ACME_HTTP_PORT 可以预设),并且明确提醒:如果不是 80,“Let’s Encrypt 仍然连接 80 端口,需要把外部的 80 端口转发到你选的端口”。所以容器在 NAT 后面时,要先把宿主机的 80 转发进容器,再装证书。
2. IP 地址探测。 脚本是靠访问外部的 IP 查询服务来获取"服务器公网 IP"的,我读到的列表有 api4.ipify.org、ipv4.icanhazip.com、v4.api.ipinfo.io/ip、ipv4.myexternalip.com/raw、4.ident.me、check-host.net/ip,依次尝试,取第一个成功的。这个 IP 用来拼出最后打印的访问地址,选 IP 证书时也用它。
问题在于:容器经过宿主机出网时,这些服务看到的是出口 IP,不一定是别人访问你时用的入口 IP。脚本自己的注释也承认,在多出口或路由不对称的情况下,查询服务可能返回一个中转地址,所以选 IP 证书时它会问你"这个是不是正确的入站公网 IPv4 地址",不对就让你手填。所以:
- 交互式安装时,认真看这一问,不对就填正确的公网 IP;
- 无人值守安装时(
XUI_NONINTERACTIVE=1),探测不到可以用环境变量XUI_SERVER_IP指定; - 最后打印的访问地址如果 IP 不对,别慌:脚本注释写明这个 IP 只用来拼出显示的访问地址,用你真实的入口 IP 加上打印出来的端口和访问路径访问即可。
Fail2ban 和 IP 限制:容器里可能用不了
3x-ui 的"限制客户端 IP 连接数"依赖 Fail2ban。脚本里的说明是:IP 限制离不开 Fail2ban,没有它的话,面板会把 limitIp 字段禁用并把已有的限制清零;所以安装时脚本会自动安装并配置 Fail2ban,并且这一步被设计成非致命的,失败也不会中断面板安装。想跳过可以设置 XUI_ENABLE_FAIL2BAN=false。
官方文档说明,它的封禁用的是 iptables,并且封禁范围不包括 SSH 和面板端口。在 Docker 里,官方要求给容器加 NET_ADMIN 权限,否则封禁只记录日志、不真正执行。LXC 容器里能不能执行 iptables,取决于容器的权限,我没有验证。所以在 LXC 里:
- 如果你需要 IP 限制,装完要去验证 Fail2ban 是不是真的在工作,见 3x-ui限制客户端IP连接数;
- 如果用不上这个功能,Fail2ban 装不上也不影响面板和节点。
服务起不来时怎么查
脚本生成的 x-ui.service 里带了一批 systemd 的安全加固选项,脚本里有一个函数专门检测"当前 systemd 版本太旧、会忽略一部分加固选项",并打印提示,同时说明"面板仍会启动"。这是对旧版 systemd 的处理。容器环境里 systemd 的沙盒选项能不能正常生效,还取决于容器的权限和 LXC 的配置,我没有验证;如果服务起不来,用这两条看原因,再对照 x-ui面板启动失败怎么办:
1 | systemctl status x-ui |
需要 TUN 的功能
3x-ui 的入站里有 TUN 类型,需要用的话,容器里要有 /dev/net/tun 这个设备。LXD 文档里的 unix-char 设备,就是把宿主机上的字符设备挂到容器 /dev 下的办法,命令格式是:
1 | lxc config device add <容器名> <设备名> unix-char path=<设备路径> |
至于具体到 TUN 要不要这样做、需要什么额外权限,我没有验证。大多数人只用普通的 VLESS、Hysteria2 这类节点,用不上 TUN,可以先不管。
装之前先自查
1 | systemd-detect-virt # 确认是 lxc,并且知道内核是宿主机的 |
装的过程和登录步骤,直接按 手机搭建3x-ui 里的流程来,那篇讲的是用手机操作,但安装脚本的选项和装完记录信息的部分是通用的。装好后的安全设置见 3x-ui面板设置怎么设。
和 Docker 部署比,该选哪个
两种都是"隔离环境",区别在于:Docker 有官方的镜像和 docker-compose.yml,3x-ui 对 Docker 有专门的适配(比如 Fail2ban 的权限要求、环境变量);LXC 官方没有任何适配,要自己处理端口和权限。如果你只是想要隔离,用官方支持的 Docker 更省心,部署延迟相关的问题见 3x-ui用Docker部署延迟暴涨,问题出在哪。如果你的宿主机本来就用 LXC 管理一堆环境,按上面的几点处理也可以。
小结
- 官方对 LXC 没有任何说明;脚本只特殊处理 Docker,LXC 里走的是普通 Linux 的路径,要求 root、能联网、有 systemd(Alpine 用 OpenRC);
- 容器和宿主机共用内核:不能换内核,BBR 取决于宿主机,容器里改
net.*参数不一定有权限; - 容器在 NAT 后面时要转发端口:LXD、Incus 用 proxy 设备,支持 TCP、UDP 和端口范围,要保留客户端真实 IP 得考虑
nat=true; - 证书要转发 80 端口;脚本探测到的"公网 IP"是出口 IP,可能不是入口 IP,选 IP 证书时要认真确认;
- Fail2ban 靠 iptables,容器里能不能用我没验证,用得上 IP 限制的话装完要自己验证;
- 本文依据脚本源码和 LXD、Incus 文档,我没有在 LXC 里实际安装,Proxmox 的具体操作没有写。