前言
在之前的 Homelab 搭建手记(4)开发工具配置 中,我已经把 VS Code、Codex 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 本身可以正常显示:
text1http://<SERVER_IP>:<PORT>
登录、编辑文件和使用终端通常没有问题,但 Codex 扩展的 WebView 可能无法工作,浏览器控制台会出现与 crypto.subtle 或 Service Worker 有关的错误。code-server 官方 FAQ 也说明,WebView 依赖 Service Worker,而 Service Worker 需要运行在 Secure Context 中。
可以在浏览器开发者工具中检查:
javascript12window.isSecureContext window.crypto.subtle
通过普通内网 IP 的 HTTP 页面访问时,结果可能是:
text12false undefined
这也是最初容易误判的地方:code-server 进程已经正常运行,不代表其中所有浏览器能力都可用。 如果只是看服务状态或登录页面,很难发现这条链路还缺少 HTTPS。
1.2 最终部署目标
本文使用的结构如下:
text123456789101112浏览器 │ │ HTTPS ▼ code.example.com │ ▼ Caddy :443 │ │ HTTP,仅本机回环 ▼ code-server 127.0.0.1:30080
职责划分为:
code-server提供编辑器、终端和扩展运行环境;code-server只监听127.0.0.1,不直接暴露应用端口;Caddy负责 TLS 终结和反向代理;AliDNSProvider 负责完成 ACME DNS-01 验证;- 浏览器只通过
https://code.example.com访问服务。
这里的 code.example.com、30080 都是示例值,需要按自己的域名和端口替换。本文不会展示实际使用的域名、IP、凭据或内部拓扑。
二、安装 code-server
2.1 使用自动化脚本安装
公开的 homelab-setup 中,17-code-server.sh 会从 code-server 官方 GitHub Release 获取与当前架构匹配的 Debian 安装包,并把下载结果保存在本地缓存目录中。脚本目前支持 amd64 和 arm64。
bash123456# 克隆公开仓库 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:
bash1bash init.sh --silent 05 16 17
其中 05 提供 Go 工具链,16 安装 Caddy 并加入 dns.providers.alidns,17 安装 code-server。Caddy 自定义构建当前要求 Go 1.25 或更高版本,脚本会在修改系统前检查这个条件。
自动化脚本有意只完成软件安装,不会:
- 收集域名或 AliDNS AccessKey;
- 修改用户现有的 code-server 配置;
- 自动启动 code-server 服务;
- 把站点配置写入 Caddyfile。
这些内容与每台主机的域名、端口和安全策略有关,保留为显式配置比隐藏在安装脚本中更稳妥。
2.2 手动安装方式
如果不使用脚本,也可以从 code-server Releases 下载对应架构的 Debian 安装包:
bash1sudo apt install ./code-server_<VERSION>_<ARCH>.deb
安装完成后检查版本:
bash1code-server --version
这里只确认程序已经安装,不急着直接运行。下一步先固定监听地址和认证方式,避免默认配置与最终服务状态不一致。
三、配置并启动 code-server
3.1 配置监听地址
code-server 的用户配置位于:
text1~/.config/code-server/config.yaml
编辑配置文件:
bash12mkdir -p ~/.config/code-server ${EDITOR:-nano} ~/.config/code-server/config.yaml
参考配置如下:
yaml12345bind-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:将界面语言设置为简体中文。
配置文件包含登录密码,建议限制权限:
bash1chmod 600 ~/.config/code-server/config.yaml
3.2 使用 systemd 管理服务
Debian 安装包提供了用户实例化的 systemd 服务,可以使用当前用户名启用:
bash1sudo systemctl enable --now code-server@$USER
检查运行状态和日志:
bash12systemctl status code-server@$USER --no-pager journalctl -u code-server@$USER -n 100 --no-pager
修改 config.yaml 后,需要重启正在运行的服务:
bash1sudo systemctl restart code-server@$USER
仅修改配置文件不会让旧进程自动切换端口,这一点也是后续出现 502 Bad Gateway 的常见原因。
3.3 先验证本地服务
在加入 Caddy 前,先确认 code-server 自身可用:
bash12ss -lntp | grep ':30080' curl --noproxy '*' -I http://127.0.0.1:30080
正常情况下可以看到 code-server 只监听 127.0.0.1:30080,HTTP 请求返回登录页跳转:
text12HTTP/1.1 302 Found Location: ./login
如果这一步失败,应该先检查 code-server 的配置与日志,而不是继续调整 Caddy。把应用层和代理层分开验证,可以避免在多个组件之间来回猜测。
四、为什么 HTTPS 需要 DNS-01
4.1 内网服务无法使用常规公网验证
本文希望使用公网可信证书,但 code-server 仍然只在内网访问。域名可以通过 DNS 解析到 RFC 1918 私网地址,例如:
text1code.example.com A <PRIVATE_IP>
这种情况下,公共证书颁发机构无法从公网访问该私网地址。依赖公网访问 80 端口的 HTTP-01,以及依赖公网访问 443 端口的 TLS-ALPN-01,都不适合这套纯内网架构。
DNS-01 验证的是域名 DNS 中临时创建的 TXT 记录:
text12345678910Caddy │ 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。安装后可以检查:
bash1caddy list-modules | grep '^dns.providers.alidns$'
没有输出时,需要使用 xcaddy 构建自定义二进制:
bash1234go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest xcaddy build \ --with github.com/caddy-dns/alidns
构建完成后,应先验证产物再替换系统版本:
bash1./caddy list-modules | grep '^dns.providers.alidns$'
本文使用的 16-caddy.sh 自动化模块会完成以下工作:
- 安装 Caddy 官方 Debian 包,保留发行版提供的用户、目录和 systemd 服务;
- 检查当前二进制或本地缓存是否已经包含 AliDNS Provider;
- 必要时通过
xcaddy构建自定义 Caddy; - 使用
dpkg-divert和update-alternatives管理官方与自定义二进制; - 验证模块存在后才报告安装完成。
这样既不直接破坏 Debian 软件包的管理关系,也能在重复执行时复用已经验证过的自定义构建。
5.2 安装模块
只安装 Caddy 时执行:
bash12cd homelab-setup bash init.sh --silent 05 16
安装后检查:
bash123caddy 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,更不能提交到仓库。创建单独的环境文件:
bash12sudo install -m 640 -o root -g caddy /dev/null /etc/caddy/alidns.env sudoedit /etc/caddy/alidns.env
使用 AliDNS Provider 当前支持的环境变量名称:
text12ALIYUN_ACCESS_KEY_ID=<ACCESS_KEY_ID> ALIYUN_ACCESS_KEY_SECRET=<ACCESS_KEY_SECRET>
建议为自动 DNS 验证创建独立的阿里云 RAM 身份,并限制到实际需要的 DNS 权限,不使用主账号 AccessKey。检查文件权限:
bash1sudo stat -c '%A %U:%G %n' /etc/caddy/alidns.env
预期为:
text1-rw-r----- root:caddy /etc/caddy/alidns.env
6.2 通过 systemd 注入凭据
不要直接修改软件包提供的 systemd unit,否则升级时可能被覆盖。使用 override:
bash1sudo systemctl edit caddy
写入:
ini1234[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 前可以检查合并结果:
bash12sudo systemctl daemon-reload systemctl cat caddy
如果凭据曾经完整出现在终端记录、日志或其他非安全位置,仅删除日志并不足够,应立即轮换对应 AccessKey。
6.3 配置 Caddyfile
编辑 /etc/caddy/Caddyfile:
caddy12345678910code.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:
text1Browser ──HTTPS──> Caddy ──HTTP──> 127.0.0.1:30080
因为 code-server 已经配置为 cert: false,所以 reverse_proxy 不应误写成 https://127.0.0.1:30080。回环接口上的这一跳不需要重复配置 TLS。
6.4 格式化、验证并启动
每次修改 Caddyfile 后固定执行:
bash12345sudo caddy fmt --overwrite /etc/caddy/Caddyfile sudo caddy validate \ --config /etc/caddy/Caddyfile \ --adapter caddyfile sudo systemctl restart caddy
首次申请证书需要创建 DNS TXT 记录,等待 DNS 传播可能需要一段时间。查看日志:
bash1sudo journalctl -u caddy -n 100 --no-pager
确认日志中的验证类型是 dns-01,并检查是否出现权限不足、TXT 传播超时或 ACME 限流。
七、分层验证完整链路
7.1 DNS 与本地 upstream
先确认域名解析到了预期的私网地址:
bash1dig +short code.example.com
再绕过代理环境变量,直接检查 code-server:
bash1curl --noproxy '*' -I http://127.0.0.1:30080
只要这一步不是正常的登录跳转,就不应该继续判断 TLS 或 Caddy。
7.2 HTTPS 与反向代理
检查完整入口:
bash1curl --noproxy '*' -Iv https://code.example.com
重点观察:
- 证书主机名与域名一致;
- 证书链能够通过校验;
- HTTP 响应来自 Caddy;
- 最终返回 code-server 登录页或对应跳转,而不是 502。
完成登录后,再在浏览器 Console 检查:
javascript12window.isSecureContext window.crypto.subtle
预期分别得到:
text12true SubtleCrypto
最后打开 Codex 扩展,确认其面板能够加载并正常交互。到这里,才算完成了本文的 code-server 部署目标。
八、实际遇到的问题
8.1 HTTPS 正常但出现 502
如果浏览器显示的证书有效,但响应为:
text12HTTP/2 502 server: Caddy
说明浏览器到 Caddy 的 TLS 链路已经正常,问题位于 Caddy 到 code-server 的 upstream:
text12Browser ──HTTPS──> Caddy 正常 Caddy ──HTTP──> code-server 异常
本次部署中的原因是修改了 code-server 的监听端口,却没有重启旧进程。按顺序检查:
bash1234sudo 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 的业务端口。若日志出现:
text1listen tcp 127.0.0.1:2019: bind: address already in use
通常意味着系统里已经存在另一个 Caddy 进程。检查:
bash12sudo ss -lntp | grep ':2019' ps aux | grep '[c]addy'
不要简单把 Admin API 改到 2020 来绕过,因为两个实例仍可能继续竞争 80 和 443。正确做法是找出手工启动或遗留的实例,只保留 systemd 管理的一份 Caddy。
Admin API 应只监听本机,不要暴露到 LAN 或公网。
8.3 代理变量干扰 curl 判断
如果系统配置了 http_proxy 或 https_proxy,直接执行 curl 可能经过代理,得到的结果不能准确表示本机链路。排查时显式加入:
bash12curl --noproxy '*' -I http://127.0.0.1:30080 curl --noproxy '*' -Iv https://code.example.com
这样才能分别观察本地 upstream 和完整 HTTPS 入口。日常环境中也可以合理配置 NO_PROXY,但不要为了排障临时清空或覆盖整套代理配置。
8.4 AccessKey 出现在 journal
如果发现 Caddy 启动日志打印了完整环境变量,应立即:
- 停止继续复制或分享相关日志;
- 检查 systemd 实际的
ExecStart是否包含--environ; - 使用 override 移除该参数;
- 在阿里云 RAM 控制台轮换已经暴露的 AccessKey;
- 重新验证 Caddy 能否完成 DNS-01。
把凭据移出 Caddyfile 只是第一步,还要检查凭据在进程启动、日志和故障排查路径中是否会被再次输出。
九、适用边界与总结
本文最终完成的是一条围绕 code-server 的部署闭环:
text1234567891011安装 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 等现代扩展能够正常工作”,这才是本次部署真正结束的标志。

