Homelab 搭建手记(7)部署 code-server 并配置 HTTPS 访问
创建于 2026-09-03
更新于 2026-09-03
科技
Homelab
Debian
code-server
Caddy
HTTPS
AliDNS
8742 字 · 约 30 分钟

前言

在之前的 Homelab 搭建手记(4)开发工具配置 中,我已经把 VS CodeCodex CLI 等开发工具整理到了 Debian 工作站上。不过桌面远程只是其中一种使用方式:当手边只有平板、轻薄本,或者不方便建立完整的远程桌面时,如果能直接在浏览器中打开开发环境,会更加灵活。

code-server 可以把接近 VS Code 的编辑体验放进浏览器,终端、代码和扩展仍运行在 Homelab 主机上。最开始我直接通过内网 IP 和端口访问,编辑与终端都能使用,看起来部署已经结束;直到在其中运行 Codex 扩展,才发现 WebView 无法正常拉起,并提示当前页面不是安全上下文。

所以本文的中心不是单独部署一个反向代理,而是完成一条真正可用的 code-server 部署链路:先安装并限制服务只监听本机,再通过 Caddy + AliDNS DNS-01 提供浏览器信任的 HTTPS 入口,最终验证 Codex 智能体所依赖的 WebView 和 Web Crypto 能力。相关安装逻辑已经整理到公开的 homelab-setup 项目中。

一、从“能够打开”到“能够正常开发”

1.1 普通 HTTP 下的问题

直接访问下面这样的地址时,code-server 本身可以正常显示:

text
1
http://<SERVER_IP>:<PORT>

登录、编辑文件和使用终端通常没有问题,但 Codex 扩展的 WebView 可能无法工作,浏览器控制台会出现与 crypto.subtleService Worker 有关的错误。code-server 官方 FAQ 也说明,WebView 依赖 Service Worker,而 Service Worker 需要运行在 Secure Context 中。

可以在浏览器开发者工具中检查:

javascript
1
2
window.isSecureContext window.crypto.subtle

通过普通内网 IP 的 HTTP 页面访问时,结果可能是:

text
1
2
false undefined

这也是最初容易误判的地方:code-server 进程已经正常运行,不代表其中所有浏览器能力都可用。 如果只是看服务状态或登录页面,很难发现这条链路还缺少 HTTPS。

1.2 最终部署目标

本文使用的结构如下:

text
1
2
3
4
5
6
7
8
9
10
11
12
浏览器 │ │ HTTPS ▼ code.example.com │ ▼ Caddy :443 │ │ HTTP,仅本机回环 ▼ code-server 127.0.0.1:30080

职责划分为:

  • code-server 提供编辑器、终端和扩展运行环境;
  • code-server 只监听 127.0.0.1,不直接暴露应用端口;
  • Caddy 负责 TLS 终结和反向代理;
  • AliDNS Provider 负责完成 ACME DNS-01 验证;
  • 浏览器只通过 https://code.example.com 访问服务。

这里的 code.example.com30080 都是示例值,需要按自己的域名和端口替换。本文不会展示实际使用的域名、IP、凭据或内部拓扑。

二、安装 code-server

2.1 使用自动化脚本安装

公开的 homelab-setup 中,17-code-server.sh 会从 code-server 官方 GitHub Release 获取与当前架构匹配的 Debian 安装包,并把下载结果保存在本地缓存目录中。脚本目前支持 amd64arm64

bash
1
2
3
4
5
6
# 克隆公开仓库 git clone https://github.com/DoraTiger/homelab-setup.git cd homelab-setup # 安装 code-server bash init.sh --silent 17

如果还需要构建带 AliDNS Provider 的 Caddy,可以按照模块顺序一次执行 Go、Caddy 和 code-server:

bash
1
bash init.sh --silent 05 16 17

其中 05 提供 Go 工具链,16 安装 Caddy 并加入 dns.providers.alidns17 安装 code-server。Caddy 自定义构建当前要求 Go 1.25 或更高版本,脚本会在修改系统前检查这个条件。

自动化脚本有意只完成软件安装,不会:

  • 收集域名或 AliDNS AccessKey;
  • 修改用户现有的 code-server 配置;
  • 自动启动 code-server 服务;
  • 把站点配置写入 Caddyfile。

这些内容与每台主机的域名、端口和安全策略有关,保留为显式配置比隐藏在安装脚本中更稳妥。

2.2 手动安装方式

如果不使用脚本,也可以从 code-server Releases 下载对应架构的 Debian 安装包:

bash
1
sudo apt install ./code-server_<VERSION>_<ARCH>.deb

安装完成后检查版本:

bash
1
code-server --version

这里只确认程序已经安装,不急着直接运行。下一步先固定监听地址和认证方式,避免默认配置与最终服务状态不一致。

三、配置并启动 code-server

3.1 配置监听地址

code-server 的用户配置位于:

text
1
~/.config/code-server/config.yaml

编辑配置文件:

bash
1
2
mkdir -p ~/.config/code-server ${EDITOR:-nano} ~/.config/code-server/config.yaml

参考配置如下:

yaml
1
2
3
4
5
bind-addr: 127.0.0.1:30080 auth: password password: <STRONG_PASSWORD> cert: false locale: zh-cn

各项含义为:

  • bind-addr:只监听本机回环地址,端口可以自行调整;
  • auth:保留 code-server 自身的密码认证;
  • password:替换为独立的强密码,不要提交到 Git;
  • cert: false:code-server 不直接处理 TLS,由 Caddy 统一负责;
  • locale:将界面语言设置为简体中文。

配置文件包含登录密码,建议限制权限:

bash
1
chmod 600 ~/.config/code-server/config.yaml

3.2 使用 systemd 管理服务

Debian 安装包提供了用户实例化的 systemd 服务,可以使用当前用户名启用:

bash
1
sudo systemctl enable --now code-server@$USER

检查运行状态和日志:

bash
1
2
systemctl status code-server@$USER --no-pager journalctl -u code-server@$USER -n 100 --no-pager

修改 config.yaml 后,需要重启正在运行的服务:

bash
1
sudo systemctl restart code-server@$USER

仅修改配置文件不会让旧进程自动切换端口,这一点也是后续出现 502 Bad Gateway 的常见原因。

3.3 先验证本地服务

在加入 Caddy 前,先确认 code-server 自身可用:

bash
1
2
ss -lntp | grep ':30080' curl --noproxy '*' -I http://127.0.0.1:30080

正常情况下可以看到 code-server 只监听 127.0.0.1:30080,HTTP 请求返回登录页跳转:

text
1
2
HTTP/1.1 302 Found Location: ./login

如果这一步失败,应该先检查 code-server 的配置与日志,而不是继续调整 Caddy。把应用层和代理层分开验证,可以避免在多个组件之间来回猜测。

四、为什么 HTTPS 需要 DNS-01

4.1 内网服务无法使用常规公网验证

本文希望使用公网可信证书,但 code-server 仍然只在内网访问。域名可以通过 DNS 解析到 RFC 1918 私网地址,例如:

text
1
code.example.com A <PRIVATE_IP>

这种情况下,公共证书颁发机构无法从公网访问该私网地址。依赖公网访问 80 端口的 HTTP-01,以及依赖公网访问 443 端口的 TLS-ALPN-01,都不适合这套纯内网架构。

DNS-01 验证的是域名 DNS 中临时创建的 TXT 记录:

text
1
2
3
4
5
6
7
8
9
10
Caddy │ AliDNS API ▼ _acme-challenge.code.example.com TXT <CHALLENGE_VALUE> │ ▼ 证书颁发机构查询 TXT 记录 │ ▼ 验证域名控制权并签发证书

根据 Caddy 官方文档,DNS Challenge 不要求开放入站端口,申请证书的服务器也不需要从公网可达,因此正好适合“公网域名 + 私网服务”的场景。

4.2 为什么选择 Caddy

在这里,Caddy 是 code-server 的 HTTPS 配套层,而不是部署目标本身。我选择它主要因为:

  • Automatic HTTPS 会管理证书申请和续期;
  • Caddyfile 中反向代理配置较短;
  • 可以通过 DNS Provider 扩展接入 AliDNS;
  • WebSocket 反向代理不需要额外堆叠大量配置;
  • 配置适合使用 fmt → validate → reload 的固定流程维护。

如果环境里已经稳定运行 Nginx、Traefik 或其他代理,没有必要为了 code-server 强行迁移。本文选择 Caddy,只是因为它在这台个人 Debian 工作站上用较低的配置成本补齐了 HTTPS。

五、安装带 AliDNS Provider 的 Caddy

5.1 标准二进制不包含 AliDNS 模块

Caddy 的 DNS Provider 属于扩展模块,标准发行版不一定包含 dns.providers.alidns。安装后可以检查:

bash
1
caddy list-modules | grep '^dns.providers.alidns$'

没有输出时,需要使用 xcaddy 构建自定义二进制:

bash
1
2
3
4
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest xcaddy build \ --with github.com/caddy-dns/alidns

构建完成后,应先验证产物再替换系统版本:

bash
1
./caddy list-modules | grep '^dns.providers.alidns$'

本文使用的 16-caddy.sh 自动化模块会完成以下工作:

  1. 安装 Caddy 官方 Debian 包,保留发行版提供的用户、目录和 systemd 服务;
  2. 检查当前二进制或本地缓存是否已经包含 AliDNS Provider;
  3. 必要时通过 xcaddy 构建自定义 Caddy;
  4. 使用 dpkg-divertupdate-alternatives 管理官方与自定义二进制;
  5. 验证模块存在后才报告安装完成。

这样既不直接破坏 Debian 软件包的管理关系,也能在重复执行时复用已经验证过的自定义构建。

5.2 安装模块

只安装 Caddy 时执行:

bash
1
2
cd homelab-setup bash init.sh --silent 05 16

安装后检查:

bash
1
2
3
caddy version caddy list-modules | grep '^dns.providers.alidns$' systemctl status caddy --no-pager

如果系统已有包含其他第三方模块的 Caddy,升级前应检查 caddy build-info。不能为了补一个 AliDNS 模块,静默覆盖掉用户已有的其他扩展。

六、配置 code-server 的 HTTPS 入口

6.1 保存 AliDNS 凭据

不要把 AccessKey 直接写进 Caddyfile,更不能提交到仓库。创建单独的环境文件:

bash
1
2
sudo install -m 640 -o root -g caddy /dev/null /etc/caddy/alidns.env sudoedit /etc/caddy/alidns.env

使用 AliDNS Provider 当前支持的环境变量名称:

text
1
2
ALIYUN_ACCESS_KEY_ID=<ACCESS_KEY_ID> ALIYUN_ACCESS_KEY_SECRET=<ACCESS_KEY_SECRET>

建议为自动 DNS 验证创建独立的阿里云 RAM 身份,并限制到实际需要的 DNS 权限,不使用主账号 AccessKey。检查文件权限:

bash
1
sudo stat -c '%A %U:%G %n' /etc/caddy/alidns.env

预期为:

text
1
-rw-r----- root:caddy /etc/caddy/alidns.env

6.2 通过 systemd 注入凭据

不要直接修改软件包提供的 systemd unit,否则升级时可能被覆盖。使用 override:

bash
1
sudo systemctl edit caddy

写入:

ini
1
2
3
4
[Service] EnvironmentFile=/etc/caddy/alidns.env ExecStart= ExecStart=/usr/bin/caddy run --config /etc/caddy/Caddyfile

这里空的 ExecStart= 用来清除原启动命令,再定义不带 --environ 的启动方式。Debian 的 Caddy unit 曾使用 caddy run --environ 输出运行环境;如果把 AccessKey 通过 EnvironmentFile 注入,同时保留该参数,敏感值可能进入 journal。

应用 override 前可以检查合并结果:

bash
1
2
sudo systemctl daemon-reload systemctl cat caddy

如果凭据曾经完整出现在终端记录、日志或其他非安全位置,仅删除日志并不足够,应立即轮换对应 AccessKey。

6.3 配置 Caddyfile

编辑 /etc/caddy/Caddyfile

caddy
1
2
3
4
5
6
7
8
9
10
code.example.com { tls { dns alidns { access_key_id {env.ALIYUN_ACCESS_KEY_ID} access_key_secret {env.ALIYUN_ACCESS_KEY_SECRET} } } reverse_proxy 127.0.0.1:30080 }

浏览器到 Caddy 使用 HTTPS,Caddy 到本机 code-server 使用 HTTP:

text
1
Browser ──HTTPS──> Caddy ──HTTP──> 127.0.0.1:30080

因为 code-server 已经配置为 cert: false,所以 reverse_proxy 不应误写成 https://127.0.0.1:30080。回环接口上的这一跳不需要重复配置 TLS。

6.4 格式化、验证并启动

每次修改 Caddyfile 后固定执行:

bash
1
2
3
4
5
sudo caddy fmt --overwrite /etc/caddy/Caddyfile sudo caddy validate \ --config /etc/caddy/Caddyfile \ --adapter caddyfile sudo systemctl restart caddy

首次申请证书需要创建 DNS TXT 记录,等待 DNS 传播可能需要一段时间。查看日志:

bash
1
sudo journalctl -u caddy -n 100 --no-pager

确认日志中的验证类型是 dns-01,并检查是否出现权限不足、TXT 传播超时或 ACME 限流。

七、分层验证完整链路

7.1 DNS 与本地 upstream

先确认域名解析到了预期的私网地址:

bash
1
dig +short code.example.com

再绕过代理环境变量,直接检查 code-server:

bash
1
curl --noproxy '*' -I http://127.0.0.1:30080

只要这一步不是正常的登录跳转,就不应该继续判断 TLS 或 Caddy。

7.2 HTTPS 与反向代理

检查完整入口:

bash
1
curl --noproxy '*' -Iv https://code.example.com

重点观察:

  • 证书主机名与域名一致;
  • 证书链能够通过校验;
  • HTTP 响应来自 Caddy;
  • 最终返回 code-server 登录页或对应跳转,而不是 502。

完成登录后,再在浏览器 Console 检查:

javascript
1
2
window.isSecureContext window.crypto.subtle

预期分别得到:

text
1
2
true SubtleCrypto

最后打开 Codex 扩展,确认其面板能够加载并正常交互。到这里,才算完成了本文的 code-server 部署目标。

八、实际遇到的问题

8.1 HTTPS 正常但出现 502

如果浏览器显示的证书有效,但响应为:

text
1
2
HTTP/2 502 server: Caddy

说明浏览器到 Caddy 的 TLS 链路已经正常,问题位于 Caddy 到 code-server 的 upstream:

text
1
2
Browser ──HTTPS──> Caddy 正常 Caddy ──HTTP──> code-server 异常

本次部署中的原因是修改了 code-server 的监听端口,却没有重启旧进程。按顺序检查:

bash
1
2
3
4
sudo systemctl restart code-server@$USER ss -lntp | grep ':30080' curl --noproxy '*' -I http://127.0.0.1:30080 sudo journalctl -u caddy --since '5 minutes ago' --no-pager

同时确认 Caddyfile 中的端口一致,且 upstream 协议没有误写成 HTTPS。

8.2 Caddy 的 2019 端口冲突

Caddy 默认在 127.0.0.1:2019 提供 Admin API,它不是 code-server 的业务端口。若日志出现:

text
1
listen tcp 127.0.0.1:2019: bind: address already in use

通常意味着系统里已经存在另一个 Caddy 进程。检查:

bash
1
2
sudo ss -lntp | grep ':2019' ps aux | grep '[c]addy'

不要简单把 Admin API 改到 2020 来绕过,因为两个实例仍可能继续竞争 80 和 443。正确做法是找出手工启动或遗留的实例,只保留 systemd 管理的一份 Caddy。

Admin API 应只监听本机,不要暴露到 LAN 或公网。

8.3 代理变量干扰 curl 判断

如果系统配置了 http_proxyhttps_proxy,直接执行 curl 可能经过代理,得到的结果不能准确表示本机链路。排查时显式加入:

bash
1
2
curl --noproxy '*' -I http://127.0.0.1:30080 curl --noproxy '*' -Iv https://code.example.com

这样才能分别观察本地 upstream 和完整 HTTPS 入口。日常环境中也可以合理配置 NO_PROXY,但不要为了排障临时清空或覆盖整套代理配置。

8.4 AccessKey 出现在 journal

如果发现 Caddy 启动日志打印了完整环境变量,应立即:

  1. 停止继续复制或分享相关日志;
  2. 检查 systemd 实际的 ExecStart 是否包含 --environ
  3. 使用 override 移除该参数;
  4. 在阿里云 RAM 控制台轮换已经暴露的 AccessKey;
  5. 重新验证 Caddy 能否完成 DNS-01。

把凭据移出 Caddyfile 只是第一步,还要检查凭据在进程启动、日志和故障排查路径中是否会被再次输出。

九、适用边界与总结

本文最终完成的是一条围绕 code-server 的部署闭环:

text
1
2
3
4
5
6
7
8
9
10
11
安装 code-server ↓ 仅监听 localhost 并启用认证 ↓ 验证本地 HTTP upstream ↓ Caddy + AliDNS DNS-01 获取可信证书 ↓ 通过 HTTPS 反向代理 code-server ↓ 验证 Secure Context、WebView 与 Codex 扩展

这套方案适合拥有公开域名、DNS 托管在 AliDNS、服务实际只在内网访问的个人工作站或 Homelab。DNS-01 解决的是证书验证问题,不会自动赋予外网访问能力;域名解析、路由和防火墙仍决定哪些客户端能够连接到服务。

同时,HTTPS 和 code-server 密码也不是完整的公网暴露防护。如果需要从互联网直接访问,仍应结合 VPN、身份代理、访问控制、速率限制和网络边界设计。code-server 官方也明确不建议在缺少认证与加密的情况下直接暴露服务。

对我而言,Caddy 在这里的价值不是替代 code-server,而是补上浏览器安全上下文这一块拼图。完成 HTTPS 后,浏览器中的编辑器才从“页面能够打开”变成“Codex 等现代扩展能够正常工作”,这才是本次部署真正结束的标志。

参考

本文作者: 有次元袋的 tiger
本文链接: https://www.superheaoz.top/2026/09/22299/
版权声明: 本站点所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 我的个人天地
手机扫码阅读