Files

26 KiB
Raw Permalink Blame History

服务器部署记录:nginx 欢迎页(lzwlab.cn

说明:服务器凭据见 Authentication.md,本文档中密码一律以 $PASS 代替。 执行方式:本地通过 Paramiko 以 SSH 密码方式登录远程执行(远程用户 ubuntu,sudo 组免密)。

0. 环境信息

项目
服务器 IP 1.15.225.173
登录用户 ubuntusudo 组,sudo -n 可免密)
系统 Ubuntu 24.04.4 LTSkernel 6.8.0-124-generic,腾讯云 VM
域名 lzwlab.cn / www.lzwlab.cnDNS 已解析到 1.15.225.173
防火墙 ufw inactive(无需额外放行)
磁盘 50G,已用 5.3G

1. 连接与初始状态检查

# 本地连接(密码 $PASS,取自 Authentication.md
ssh ubuntu@1.15.225.173

# 登录后检查系统
whoami && id && hostname && uname -a
cat /etc/os-release | head -3

# 检查 sudo 免密
sudo -n true && echo sudo-ok

# 检查 nginx(结果:未安装)
command -v nginx && nginx -v

# 检查 80/443 端口占用(结果:空闲)
ss -tlnp | grep -E ":(80|443)" || echo no-80-443

# 检查防火墙(结果:inactive)
sudo -n ufw status

# 检查域名解析(结果:均解析到 1.15.225.173
getent hosts lzwlab.cn www.lzwlab.cn

本机验证 DNS(结果:Address: 1.15.225.173):

nslookup lzwlab.cn
nslookup www.lzwlab.cn

2. 安装 nginx

sudo apt-get update
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y nginx

结果:安装 nginx 1.24.0-2ubuntu7.17nginx + nginx-common)。 安装完成后 systemd 已自动启动 nginx 并注册开机自启(默认站点为 /var/www/html 的欢迎页)。

3. 创建站点内容

本地创建 index.html(欢迎页,中文标题“欢迎访问 lzwlab.cn”,1219 字节), 通过 SFTP 上传到服务器 /tmp,再用 root 安装到站点目录:

# 本地:上传文件
# Paramiko SFTP: index.html -> /tmp/lzwlab-index.html

# 远程:
sudo mkdir -p /var/www/lzwlab.cn
sudo install -m 644 -o root -g root /tmp/lzwlab-index.html /var/www/lzwlab.cn/index.html

页面内容要点(详见仓库内 index.html):

<h1>🎉 欢迎访问 lzwlab.cn</h1>
<span class="badge">Powered by Nginx</span>

后续变更:2026-08-26 按需求移除了页面中的 <p>网站搭建成功!这是部署在个人云服务器上的第一个页面。</p> 提示信息(详见 CHANGELOG)。

4. 配置 nginx 站点

创建站点配置 /etc/nginx/sites-available/lzwlab.cn(内容同仓库内 lzwlab.nginx.conf):

server {
    listen 80;
    listen [::]:80;
    server_name lzwlab.cn www.lzwlab.cn;

    root /var/www/lzwlab.cn;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

启用站点并移除默认站点:

# 上传配置
# Paramiko SFTP: lzwlab.nginx.conf -> /tmp/lzwlab.conf

# 远程:
sudo install -m 644 -o root -g root /tmp/lzwlab.conf /etc/nginx/sites-available/lzwlab.cn
sudo ln -sf /etc/nginx/sites-available/lzwlab.cn /etc/nginx/sites-enabled/lzwlab.cn
sudo rm -f /etc/nginx/sites-enabled/default
sudo rm -f /tmp/lzwlab-index.html /tmp/lzwlab.conf

# 校验配置语法
sudo nginx -t
# 结果:syntax is ok / test is successful

5. 启动 / 加载配置

sudo systemctl enable nginx
sudo systemctl start nginx
# 注意:apt 安装时 nginx 已启动并加载了默认配置,
# 所以修改配置后必须 reload 才会生效:
sudo nginx -t
sudo systemctl reload nginx

检查监听状态(结果:0.0.0.0:80 与 [::]:80 均已监听):

sudo systemctl is-active nginx   # active
ss -tlnp | grep :80

6. 验证

服务器本机验证:

curl -s -o /dev/null -w "status=%{http_code} size=%{size_download}\n" http://127.0.0.1/ -H "Host: lzwlab.cn"
# status=200 size=1219
curl -s http://127.0.0.1/ -H "Host: lzwlab.cn" | grep -o "<title>.*</title>"
# <title>欢迎访问 lzwlab.cn</title>

本地(公网域名)验证:

curl -s -o /dev/null -w "status=%{http_code} size=%{size_download}\n" http://lzwlab.cn/
# status=200 size=1219
curl -s http://lzwlab.cn/ | grep -o "<title>.*</title>"
# <title>欢迎访问 lzwlab.cn</title>
curl -s -o /dev/null -w "status=%{http_code}\n" http://www.lzwlab.cn/
# status=200

部署完成:http://lzwlab.cnhttp://www.lzwlab.cn 均已正常展示欢迎页。

7. 部署 HTTPS 证书并配置 443 自动跳转

7.1 证书信息

本地证书目录 lzwlab.cn_nginx/

文件 用途
lzwlab.cn.key RSA 私钥(1700 字节)
lzwlab.cn_bundle.crt / .pem 证书 + 证书链(两者内容相同)
lzwlab.cn.csr 证书签名请求(部署用不到)

证书校验(本机 openssl):

openssl x509 -in lzwlab.cn_nginx/lzwlab.cn_bundle.crt -noout -subject -issuer -dates
# subject=CN=lzwlab.cn
# issuer=TrustAsia DV TLS RSA CA 2024
# 有效期:2026-08-24 ~ 2026-11-22
openssl rsa -in lzwlab.cn_nginx/lzwlab.cn.key -check -noout   # RSA key ok

SAN 包含:DNS:lzwlab.cnDNS:www.lzwlab.cn

7.2 上传证书与配置到服务器

# 本地:SFTP 上传到 /tmp
#   lzwlab.cn_nginx/lzwlab.cn.key         -> /tmp/lzwlab.cn.key
#   lzwlab.cn_nginx/lzwlab.cn_bundle.crt  -> /tmp/lzwlab.cn_bundle.crt
#   lzwlab.nginx.conf                     -> /tmp/lzwlab.conf

# 远程:安装证书/私钥并更新站点配置
sudo mkdir -p /etc/nginx/ssl
sudo install -m 600 -o root -g root /tmp/lzwlab.cn.key /etc/nginx/ssl/lzwlab.cn.key
sudo install -m 644 -o root -g root /tmp/lzwlab.cn_bundle.crt /etc/nginx/ssl/lzwlab.cn_bundle.crt
sudo cp /etc/nginx/sites-available/lzwlab.cn /etc/nginx/sites-available/lzwlab.cn.bak
sudo install -m 644 -o root -g root /tmp/lzwlab.conf /etc/nginx/sites-available/lzwlab.cn
sudo rm -f /tmp/lzwlab.cn.key /tmp/lzwlab.cn_bundle.crt /tmp/lzwlab.conf

私钥权限 600、root:root;证书 644。旧配置备份为 lzwlab.cn.bak

7.3 更新后的站点配置(/etc/nginx/sites-available/lzwlab.cn

server {
    listen 80;
    listen [::]:80;
    server_name lzwlab.cn www.lzwlab.cn;
    return 301 https://$host$request_uri;      # HTTP 全部 301 跳转到 HTTPS
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name lzwlab.cn www.lzwlab.cn;

    ssl_certificate     /etc/nginx/ssl/lzwlab.cn_bundle.crt;
    ssl_certificate_key /etc/nginx/ssl/lzwlab.cn.key;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;

    root /var/www/lzwlab.cn;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

7.4 校验并加载

sudo nginx -t                    # syntax is ok / test is successful
sudo systemctl reload nginx

7.5 验证结果

服务器本机:

curl -s -o /dev/null -w "http status=%{http_code} redirect=%{redirect_url}\n" http://127.0.0.1/ -H "Host: lzwlab.cn"
# status=301 redirect=https://lzwlab.cn/
curl -s https://127.0.0.1/ -k -H "Host: lzwlab.cn" | grep -o "<title>.*</title>"
# <title>欢迎访问 lzwlab.cn</title>

本地公网:

curl -s -o /dev/null -w "status=%{http_code} -> %{redirect_url}\n" http://lzwlab.cn/
# status=301 -> https://lzwlab.cn/
curl -s -o /dev/null -w "status=%{http_code} ssl_verify=%{ssl_verify_result}\n" https://lzwlab.cn/
# status=200 ssl_verify=0(证书链受信任)
curl -s -o /dev/null -w "status=%{http_code}\n" https://www.lzwlab.cn/
# status=200
echo | openssl s_client -connect lzwlab.cn:443 -servername lzwlab.cn -alpn h2,http/1.1 2>/dev/null | grep "ALPN"
# ALPN protocol: h2HTTP/2 已启用)

https://lzwlab.cnhttps://www.lzwlab.cn 已启用 HTTPS;http 访问会自动 301 跳转到 https。

8. 部署“文件快传”工具(/transfer/)

8.1 方案

  • 后端:Flask 3.0.2 + gunicorn 20.1apt 安装),监听 127.0.0.1:8090systemd 服务 lzwlab-transfer.service
  • 前端:单页 HTML(内嵌 CSS/JS),PC 与手机自适应,登录后可用。
  • 存储:上传文件保存在 /var/lib/lzwlab-transfer/files/,元数据在 SQLite /var/lib/lzwlab-transfer/files.db
  • 配置:/etc/lzwlab-transfer/config.jsonroot:lzwlab 640),登录密码只保存 scrypt 哈希,明文经部署环境变量传入。
  • 清理:lzwlab-transfer-cleanup.timer 每 10 分钟执行 cleanup.py 删除过期文件;访问列表/手动清理时也会触发检查。
  • nginx/transfer/ 反向代理到 8090proxy_request_buffering off + proxy_buffering off 保证大文件上传下载流式传输,client_max_body_size 5120m

8.2 部署命令(本地运维机)

# 依赖脚本见仓库 deploy_transfer.py;密码经环境变量传入,不会写入仓库
LZWLAB_APP_PASSWORD='$PASS' python deploy_transfer.py --username admin

脚本会依次完成:apt 安装依赖 → 创建系统用户 lzwlab 与目录 → 上传/安装应用、配置、systemd 单元 → 启动服务与清理定时器 → 校验并 reload nginx。重复执行幂等。

8.3 验证结果

systemctl is-active lzwlab-transfer.service lzwlab-transfer-cleanup.timer   # active active
curl -s -o /dev/null -w "%{http_code}\n" https://lzwlab.cn/transfer/        # 200(登录页)
curl -s -o /dev/null -w "%{http_code} -> %{redirect_url}\n" http://lzwlab.cn/transfer/  # 301 -> https

公网 API 实测:登录成功返回会话 Cookie;未登录访问 /api/files 返回 401;上传 25 字节测试文件后列表可查,下载返回 200 且支持 Range(206);删除后列表为空;将测试文件过期时间强制置为过去并启动清理服务后,日志输出“清理完成:过期文件 1 个”,列表随即清空。

8.4 踩坑

  • deploy_transfer.py 在 Windows 本地生成远端路径时不能使用 os.path.join(会把 / 变成 \),SQLite 路径必须手工拼接 Linux 路径,否则服务报 unable to open database file
  • gunicorn 手动排障时不要在 SSH 会话里前台运行(会话断开会留下占端口的孤儿进程),应直接通过 systemctl 管理;排障后可 sudo pkill -u lzwlab -f gunicorn && sudo systemctl restart lzwlab-transfer.service
  • Windows 下 curl 的 -F 'file=@...;filename=中文名' 对非 ASCII 文件名支持不佳,测试时用 ASCII 文件名,浏览器上传不受影响。

9. 部署 RustDesk 自建服务器(Docker

9.1 方案

  • 组件:RustDesk Server OSS 版两个容器——hbbs(ID/注册服务器)与 hbbr(中继服务器),镜像 rustdesk/rustdesk-server:latest
  • 网络:network_mode: host(官方推荐,无需 -p 映射),数据卷 ./data 挂载到容器 /root(密钥、SQLite 库)。
  • 端口(防火墙需放行):
    • hbbs21115/TCPNAT 类型测试)、21116/TCP+UDPUDP 用于 ID 注册与心跳,TCP 用于打洞与连接服务)、21118/TCP(网页客户端);
    • hbbr21117/TCP(中继服务)、21119/TCP(网页客户端);
    • 21114/TCP 网页控制台仅 Pro 版需要,OSS 版不监听。
  • 参考文档:https://rustdesk.com/docs/zh-cn/self-host/rustdesk-server-oss/docker/ ;客户端配置:https://rustdesk.com/docs/zh-cn/self-host/client-configuration/

9.2 安装 Docker 并配置镜像加速

sudo apt-get update
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y docker.io docker-compose-v2
sudo systemctl enable --now docker
docker --version   # Docker version 29.1.3apt docker.io 自带守护进程服务)

踩坑:腾讯云访问 Docker Hub 被拒(registry-1.docker.io connection refused),必须配置镜像加速:

echo '{"registry-mirrors":["https://docker.m.daocloud.io"]}' | sudo tee /etc/docker/daemon.json
sudo systemctl restart docker

9.3 部署 hbbs/hbbr

Compose 文件为仓库内 rustdesk/docker-compose.yml

services:
  hbbs:
    container_name: hbbs
    image: rustdesk/rustdesk-server:latest
    command: hbbs
    volumes:
      - ./data:/root
    network_mode: "host"
    depends_on:
      - hbbr
    restart: unless-stopped
  hbbr:
    container_name: hbbr
    image: rustdesk/rustdesk-server:latest
    command: hbbr
    volumes:
      - ./data:/root
    network_mode: "host"
    restart: unless-stopped

部署命令:

# 本地:SFTP 上传 rustdesk/docker-compose.yml 到 /tmp
sudo mkdir -p /opt/rustdesk
sudo install -m 644 -o root -g root /tmp/rustdesk-compose.yml /opt/rustdesk/compose.yml
cd /opt/rustdesk && sudo docker compose up -d

验证结果:

sudo docker ps                       # hbbs、hbbr 均 Up
sudo ss -tlnup | grep -E '2111[4-9]'
# tcp 21115/21116、udp 21116、tcp 21118 归 hbbstcp 21117/21119 归 hbbr
sudo docker logs --tail 5 hbbs
# Key: UB1fNGcVYA9NTIEA7ra3Nm0R6o2m64lNQE24L7CIAkY=
# Listening on tcp/udp :21116 / Listening on tcp :21115 / Listening on websocket :21118

9.4 客户端配置

RustDesk 客户端(设置 → 网络,点击“解锁”后可编辑)填写:

项目
ID 服务器 lzwlab.cnhbbs,端口 21116
中继服务器 留空自动推导;或填 lzwlab.cnhbbr,端口 21117
Key UB1fNGcVYA9NTIEA7ra3Nm0R6o2m64lNQE24L7CIAkY=
API 服务器 留空(仅 Pro 版需要)
  • Key 由 hbbs 首次启动自动生成,保存在服务器 /opt/rustdesk/data/id_ed25519.pub(容器内 /root/id_ed25519.pub),重建容器会复用;Key 需分发给每个客户端。
  • 连通性:本地已验证 21115-21119 TCP 公网可达;hbbs 日志可见外部客户端注册记录。UDP 21116 用于 ID 注册与心跳,若腾讯云控制台防火墙未放行 UDP 21116,客户端将无法注册。

9.5 运维

# 更新镜像并重建
cd /opt/rustdesk && sudo docker compose pull && sudo docker compose up -d
# 查看日志
sudo docker logs -f hbbs
sudo docker logs -f hbbr
# 查看密钥
sudo cat /opt/rustdesk/data/id_ed25519.pub

10. 部署 Gitea Git 服务(/git/

10.1 方案

  • 组件:Gitea 1.27.2 官方镜像 docker.gitea.com/gitea:1.27.2Docker Compose,容器名 gitea),数据库 SQLite,数据卷 ./gitea:/data(持久化在服务器 /opt/gitea/gitea/)。
  • 入口:服务器没有 git.lzwlab.cn 子域名解析,按 Gitea 官方反向代理文档的 sub-path 方案部署在 https://lzwlab.cn/git/ROOT_URL=https://lzwlab.cn/git/)。
  • 端口:Web 仅监听 127.0.0.1:3000(不对公网直接暴露),由 nginx 将 /git/ 反代到 3000;SSH 端口与主机 sshd 冲突,容器内 22 映射到主机 2222。
  • 账号:管理员私有单实例——关闭开放注册(DISABLE_REGISTRATION=true)、锁定安装器(INSTALL_LOCK=true),管理员 admin(密码见 Authentication.md)。
  • 参考文档:https://docs.gitea.com/installation/install-with-docker/https://docs.gitea.com/administration/reverse-proxies/ (“Nginx with a sub-path”)。

10.2 部署命令

Compose 文件为仓库内 gitea/docker-compose.yml

# 本地:SFTP 上传 gitea/docker-compose.yml 到 /tmp
sudo mkdir -p /opt/gitea
sudo install -m 644 -o root -g root /tmp/gitea-compose.yml /opt/gitea/compose.yml
sudo rm -f /tmp/gitea-compose.yml
cd /opt/gitea && sudo docker compose up -d

创建管理员并设置站点名:

# 管理员(密码经变量传入,不写入仓库;首次启动完成后执行)
sudo docker exec -u git gitea gitea admin user create \
  --username admin --password '$PASS' --email admin@lzwlab.cn \
  --admin --must-change-password=false
# 站点名写入 app.ini 的 [DEFAULT] 段后重建容器
sudo sed -i '1s/.*/APP_NAME = lzwlab Git 服务/' /opt/gitea/gitea/gitea/conf/app.ini
cd /opt/gitea && sudo docker compose up -d

10.3 nginx 反代(/git/

按官方 “Nginx with a sub-path” 方案(保持 %2F 原样并剥离 /git 前缀),在 443 server 块新增:

location ~ ^/(git)($|/) {
    client_max_body_size 512M;
    rewrite ^ $request_uri;
    rewrite ^/(git($|/))?(.*) /$3 break;
    proxy_pass http://127.0.0.1:3000$uri;
    proxy_http_version 1.1;
    proxy_set_header Connection $http_connection;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_read_timeout 300s;
}

该配置已并入仓库内 lzwlab.nginx.conf,服务器 /etc/nginx/sites-available/lzwlab.cn 同步更新(改前自动备份 lzwlab.cn.bak-YYYYMMDD-HHMMSS),sudo nginx -t 通过后 reload。

10.4 验证结果

curl -s -o /dev/null -w "%{http_code}\n" https://lzwlab.cn/git/          # 200
curl -s https://lzwlab.cn/git/api/v1/version                            # {"version":"1.27.2"}
curl -s https://lzwlab.cn/git/ | grep -o '<title>.*</title>'            # <title>lzwlab Git 服务</title>
curl -s https://lzwlab.cn/ | grep -o '进入 Git 服务'                     # 进入 Git 服务(首页按钮已生效)
# git 智能 HTTP 协议(创建测试仓库 hello 后):
curl -s 'https://lzwlab.cn/git/admin/hello.git/info/refs?service=git-upload-pack'
# 返回 service=git-upload-pack 与 HEAD 引用;测试仓库与临时令牌验证后已删除

关键配置项(/opt/gitea/gitea/gitea/conf/app.ini):ROOT_URL=https://lzwlab.cn/git/DOMAIN/SSH_DOMAIN=lzwlab.cnSSH_PORT=2222SSH_LISTEN_PORT=22(容器内)、INSTALL_LOCK=trueDISABLE_REGISTRATION=trueAPP_NAME=lzwlab Git 服务

10.5 使用与运维

  • 克隆地址:
    • HTTPS(公网可用):https://lzwlab.cn/git/<用户名>/<仓库>.git
    • SSH(需先放行安全组,见 10.6):ssh://git@lzwlab.cn:2222/<用户名>/<仓库>.git
  • 运维命令:
sudo docker compose -f /opt/gitea/compose.yml ps                # 容器状态
sudo docker logs -f gitea                                       # 日志
cd /opt/gitea && sudo docker compose pull && sudo docker compose up -d   # 升级镜像
sudo docker exec -u git gitea gitea admin user list             # 用户列表
  • 备份:SQLite 库、仓库与配置全部在数据卷内,直接备份 /opt/gitea/gitea/ 即可。

10.6 踩坑

  • GITEA__APP_NAME 环境变量传入容器后未生效(站点名仍显示默认值),站点名需直接写入 app.ini[DEFAULT] 段(APP_NAME = ...)并重建容器。
  • 腾讯云安全组只放行 22/443/21115-21119,主机映射的 2222 端口公网不可达(本机探测 TIMEOUT,服务器本地可达):SSH 克隆需在腾讯云控制台为 TCP 2222 添加入站规则;HTTPS 克隆与网页访问不受影响。
  • Gitea 子路径部署必须同时满足两点:ROOT_URL/git/ 后缀,且 nginx 使用官方 rewrite 方案剥离前缀(直接 proxy_pass http://127.0.0.1:3000/; 会破坏前缀与 %2F 转义)。
  • 镜像使用官方新仓库 docker.gitea.com/gitea:1.27.2(腾讯云可直连,无需镜像加速);若改用 Docker Hub 的 gitea/gitea 则会走 daocloud 加速。

11. Komari Lite 服务器监控(已卸载)

该服务曾部署到 https://status.lzwlab.cn/,后按需求从服务器卸载,首页入口已删除。此前的部署配置与完整记录见 git 历史(commit bdffcee / 60b9ce1)。nginx server 级 return 的踩坑保留在下方“附:踩坑记录”第 6 条。

卸载命令:

cd /opt/komari && sudo docker compose down
sudo docker rmi ghcr.io/nuomiiiii/komari:latest ghcr.nju.edu.cn/nuomiiiii/komari:latest
sudo rm -f /etc/nginx/sites-enabled/status.lzwlab.cn /etc/nginx/sites-available/status.lzwlab.cn /etc/nginx/sites-available/status.lzwlab.cn-http /etc/nginx/sites-available/status.lzwlab.cn.bak-*
sudo certbot delete --cert-name status.lzwlab.cn --non-interactive
sudo rm -rf /var/www/certbot /opt/komari
sudo nginx -t && sudo systemctl reload nginx

12. 部署 Beszel 服务器监控(https://lzwlab.cn/beszel/

12.1 方案与要点

  • 组件:Beszel hub(基于 PocketBase 的 Web 面板,Docker 镜像 henrygd/beszel:latest)。每个被监控服务器再运行一个 agenthenrygd/beszel-agent),由用户在 Web 面板中添加系统时生成配置。
  • 入口:按需求将 Beszel 主页反代到主站子路径 https://lzwlab.cn/beszel/(首页欢迎页“📊 服务器状态”按钮)。
  • 端口:Beszel 默认端口 8090 与本机文件快传的 gunicorn127.0.0.1:8090)冲突,因此容器 8090 只映射到宿主机 127.0.0.1:8092
  • 子路径关键配置:hub 的 APP_URL 必须设为 https://lzwlab.cn/beszel,否则通知链接与 Agent 配置生成会指向错误地址。
  • 参考文档:https://www.beszel.dev/zh/guide/getting-startedhttps://www.beszel.dev/zh/guide/hub-installationhttps://www.beszel.dev/zh/guide/reverse-proxy

12.2 部署命令

Compose 文件为仓库内 beszel/docker-compose.yml,服务器安装到 /opt/beszel/。首次启动通过 .envroot:root 0600)设置 USER_EMAIL / USER_PASSWORD 自动创建第一个管理员:

# 本地:SFTP 上传 beszel/docker-compose.yml 与临时 .env 到 /tmp
# 远程:
sudo mkdir -p /opt/beszel
sudo install -m 600 -o root -g root /tmp/beszel.env /opt/beszel/.env
sudo install -m 644 -o root -g root /tmp/beszel-compose.yml /opt/beszel/docker-compose.yml
sudo rm -f /tmp/beszel.env /tmp/beszel-compose.yml
cd /opt/beszel && sudo docker compose up -d
# 验证管理员已创建后,清空 .env 中的明文密码(管理员账号已持久化在 beszel_data
sudo sh -c 'echo > /opt/beszel/.env' && sudo chmod 600 /opt/beszel/.env
cd /opt/beszel && sudo docker compose up -d

.env 内容(仅首次启动需要,密码记录在 Authentication.md):

USER_EMAIL=beszel@lzwlab.cn
USER_PASSWORD=$PASS

12.3 nginx 反代(/beszel/

按 Beszel 官方 “使用 Nginx 在子路径提供服务” 方案,在 lzwlab.nginx.conf 的 443 server 块新增(已并入仓库配置):

location = /beszel {
    return 301 /beszel/;
}
location /beszel/ {
    client_max_body_size 10M;
    proxy_read_timeout 360s;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    rewrite /beszel/(.*) /$1 break;
    proxy_pass http://127.0.0.1:8092;
}

改配置后执行 sudo nginx -t && sudo systemctl reload nginx

12.4 验证结果

sudo docker ps --filter name=beszel   # Up (healthy)127.0.0.1:8092->8090/tcp
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8092/    # 200(本机 hub
# 公网:
curl -s -o /dev/null -w "%{http_code} -> %{redirect_url}\n" https://lzwlab.cn/beszel
# 301 -> https://lzwlab.cn/beszel/
curl -s -o /dev/null -w "%{http_code}\n" https://lzwlab.cn/beszel/   # 200
curl -sL https://lzwlab.cn/beszel/ | grep -o '<title>.*</title>'      # <title>Beszel</title>
curl -s https://lzwlab.cn/ | grep -o '服务器状态'                       # 服务器状态(首页按钮已生效)

12.5 添加监控节点(Agent

  1. 打开 https://lzwlab.cn/beszel/,用 Authentication.md 中的管理员邮箱/密码登录。
  2. 点击右上角 Add System,填写系统名称,选择 WebSocket 或 SSH 连接方式,复制对话框生成的 Docker Compose 或二进制安装命令到目标服务器执行。
  3. 若目标服务器就是本机,且用 Docker 运行 agentagent 默认监听 45876,与本机其他服务无端口冲突;WebSocket 模式将 HUB_URL 设为 https://lzwlab.cn/beszelKEY/TOKEN 使用对话框生成的值。若想监控宿主机网络/容器,需给 agent 挂载 /var/run/docker.sock:ro 并使用 host 网络。
  4. 完成后回到对话框点击 Add System,系统状态变为绿色即接入成功。

12.6 运维

cd /opt/beszel && sudo docker compose ps                     # hub 容器状态
sudo docker logs -f beszel                                   # hub 日志
cd /opt/beszel && sudo docker compose pull && sudo docker compose up -d   # 升级 hub
# 备份:PocketBase 数据在数据卷内,直接备份 /opt/beszel/beszel_data/ 即可

12.7 踩坑

  • Beszel hub 默认端口 8090 与本机文件快传 gunicorn 冲突,必须改映射(本项目使用 127.0.0.1:8092:8090)。
  • 子路径部署必须设置 APP_URL=https://lzwlab.cn/beszel,并让 nginx 用 rewrite 去掉 /beszel 前缀;否则通知链接和 Agent 配置会指向错误地址。
  • USER_EMAIL / USER_PASSWORD 只在首次启动(尚无用户)时用于创建管理员;创建完成后应清空 .env,避免明文密码残留在服务器配置文件中。

附:踩坑记录

  1. 本地 Git Bash 调用 Windows Python 时,MSYS 会把以 / 开头的命令行参数(如 /tmp/...)转换成 Windows 路径, 导致 SFTP 上传远端路径错误(ENOENT)。解决:调用前加 MSYS_NO_PATHCONV=1,或把路径写进脚本内。
  2. apt 安装 nginx 会自动启动服务并加载默认站点配置;改完站点配置后 systemctl start 不会重新加载, 必须 systemctl reload nginx(或 restart),否则看到的仍是默认欢迎页。
  3. 该服务器上的 nginx 1.24(Ubuntu 构建)不识别独立指令 http2 on;nginx -t 报 unknown directive "http2" 启用 HTTP/2 应写成 listen 443 ssl http2;
  4. 腾讯云服务器访问 Docker Hub 被拒(pull 报 connection refused),通过 /etc/docker/daemon.json 配置 registry-mirrorsdocker.m.daocloud.io)后解决。
  5. remote.py--timeout 参数必须放在远程命令之前(python remote.py --timeout 500 "命令"); 放在命令之后会被 argparse 当作远程命令的一部分传给服务器执行。
  6. nginx 中 server 级的 return 在 rewrite 阶段(location 匹配之前)执行并直接终结请求:server { location /.well-known/acme-challenge/ {...} return 301 ...; } 的挑战路径照样会被 301,导致 certbot 签发失败(LE 跟随 301 到 HTTPS 后得到 404)。必须把重定向写进 location / { return 301 ...; } 内,让 location 先参与匹配。