← tmux-next EN · 中文 · GitHub

用 Caddy 部署,并加上登录认证

tmux-next 本身没有任何认证,只绑在 127.0.0.1 上。想从手机或外网访问, 需要在前面放一个反向代理来做两件事:HTTPS,和登录认证。这份指引用 Caddy——它能自动申请和续期证书,配置也短。

先想清楚风险。能访问到这个服务的人,就能在你的机器上执行命令—— 它给的是 shell。把访问权限当成 SSH 访问来对待:认证必须有,密码要强,只走 HTTPS。

1准备

2生成两样东西

一个是登录密码的哈希,一个是给 WebSocket 用的随机口令(下一步解释为什么需要它):

# 密码哈希——把它填进 Caddyfile,别存明文密码
caddy hash-password --plaintext '你的强密码'

# WebSocket 用的随机口令——生成一个,记下来
openssl rand -hex 24

第一条会输出一串 $2a$… 的 bcrypt 哈希;第二条会输出一串随机十六进制。 两个待会都要用。

3写 Caddyfile

下面是一份完整可用的配置。把三处占位符换成你自己的值:

tmux.example.com {
    # WebSocket:单独用 cookie 认证,原因见下一步
    @ws path /ws
    handle @ws {
        @noauth not header Cookie *tn_auth=你的随机口令*
        respond @noauth 403
        reverse_proxy 127.0.0.1:7682
    }

    # 其余所有请求:Basic Auth,通过后种下 cookie
    handle {
        basic_auth {
            你的用户名 $2a$…你的密码哈希
        }
        header +Set-Cookie "tn_auth=你的随机口令; Path=/; Secure; HttpOnly; SameSite=Strict"
        reverse_proxy 127.0.0.1:7682
    }
}

三处占位符:tmux.example.com 换成你的域名; 你的随机口令 两处都换成第 2 步 openssl 生成的那串(必须一致); 你的用户名$2a$… 换成登录名和第 2 步的密码哈希。

4为什么 WebSocket 要单独处理

这是最容易踩坑、也最容易留下安全漏洞的地方。终端的实时数据走 WebSocket (路径 /ws),而浏览器发起 WebSocket 握手时不会带 Basic Auth 头。 结果就是:如果你只用 Basic Auth 保护整站,页面看起来锁住了, 但 /ws 会因为收不到认证而连不上——或者更糟,你为了让它能连而放开了 /ws, 于是终端对任何人敞开。

上面的配置绕开了这个问题:普通请求通过 Basic Auth 后,响应里种一个 cookie; 之后浏览器发起 WebSocket 握手时带上这个 cookie, /ws 那段就凭 cookie 放行。所以那串随机口令必须保密—— 它等价于一把钥匙,SecureHttpOnly 标记确保它只走 HTTPS、 也不被脚本读到。

5启动并验证

caddy reload --config /path/to/Caddyfile

然后从命令行验证三个关键点(把域名换成你的):

# 页面要求认证 → 期望 401
curl -so /dev/null -w "%{http_code}\n" https://tmux.example.com/

# WebSocket 没带 cookie → 期望 403
curl -so /dev/null -w "%{http_code}\n" https://tmux.example.com/ws

# 带上正确 cookie → 期望 400(说明已穿过认证、到达服务)
curl -so /dev/null -w "%{http_code}\n" \
  -H "Cookie: tn_auth=你的随机口令" https://tmux.example.com/ws

401403 证明两道门都锁着;带 cookie 的请求返回 400 是正常的——那是服务在说「这不是一个合法的 WebSocket 握手」,但请求确实到了它, 说明认证放行了。三个都对,就可以在手机上打开域名登录了。

换机器 / 换域名?这份配置不依赖任何特定系统。 只要 tmux-next 在某处监听 127.0.0.1:7682、Caddy 能解析到你的域名, 照抄即可。开机自启(launchd / systemd)等更多细节见仓库的 deploy.md

6进阶:换成登录页 + 会过期的 token

上面的 Basic Auth 有两个已知的粗糙之处:登录框是浏览器画的原生弹窗, 没法自定义;那串 cookie 口令是静态的、永不过期,一旦泄漏就得手动改配置。 如果想要一个像样的登录页,加上会自动过期的 token,可以用 caddy-security 插件—— 它一并解决这两点。

它不在标准 Caddy 里,需要用 xcaddy 构建一个带它的版本:

xcaddy build --with github.com/greenpau/caddy-security

然后配置分三块。先生成一个签名密钥(JWT 用它签发和校验,泄漏等于伪造登录):

openssl rand -hex 32

① 全局块——定义用户库、登录门户、授权策略:

{
    # 这两行让插件的中间件排到正确的位置(照抄,与站点用不用 basicauth 无关)
    order authenticate before respond
    order authorize before basicauth

    security {
        # 用户库:密码用 bcrypt 哈希,不存明文
        local identity store localdb {
            realm local
            path /path/to/users.json
            user 你的用户名 {
                name 你的名字
                email you@example.com
                password "bcrypt:10:$2a$10$…你的密码哈希"
                roles authp/user
            }
        }

        # 登录门户:签发会过期的 JWT
        authentication portal myportal {
            crypto default token lifetime 28800   # token 有效期,秒(这里 8 小时)
            crypto key sign-verify 你的签名密钥
            enable identity store localdb
            # 让 JWT cookie 在 auth. 和 tmux. 两个子域间共享
            cookie domain example.com
        }

        # 授权策略:校验 JWT,未登录就跳到登录门户
        authorization policy tmuxpolicy {
            # 跨子域必须写绝对 URL,指向登录门户
            set auth url https://auth.example.com/auth/
            crypto key verify 你的签名密钥
            allow roles authp/user
        }
    }
}

② 登录门户的站点块——它托管那个自定义登录页:

auth.example.com {
    route /auth* {
        authenticate with myportal
    }
}

③ tmux-next 的站点块——用策略保护,未登录自动跳去登录页:

tmux.example.com {
    # 未登录访问任何路径(含 WebSocket)都要求先有合法 JWT
    authorize with tmuxpolicy
    reverse_proxy 127.0.0.1:7682
}

为什么这次 WebSocket 不用单独处理了?因为 JWT 存在 cookie 里, 浏览器发起 WebSocket 握手时带上 cookie——和第 4 步靠 cookie 放行是同一个道理, 只是这次 cookie 里装的是签过名、会过期的 JWT,而不是一个静态口令。authorize 对所有路径统一校验,/ws 也在内,不用再写一段。

两个密钥别弄混,也别泄漏。签名密钥(crypto key)泄漏等于别人能伪造任意登录; 密码哈希用 caddy hash-password 生成、以 bcrypt:10: 开头填进 passwordtoken lifetime 到期后需要重新登录——这正是它比静态 cookie 强的地方: 抓到一个 token 也只在有效期内能用。

caddy-security 还能接 GitHub / Google 登录(OAuth)、加两步验证(TOTP / Yubikey)。 考虑到这服务给的是 shell,2FA 很值得开。完整选项见 它的文档