Sign inSign up

casualty01/web_ssh

By casualty01

•Updated 17 days ago

WebSSH: Self-hosted browser SSH client, Xshell-style.

Image
Networking
Developer tools
Web analytics
0

694

casualty01/web_ssh repository overview

⁠WebSSH

English⁠ · 中文⁠


⁠English

A self-hosted, browser-based SSH client — Xshell-style, in a single Go binary on a tiny Alpine image.

Manage SSH sessions, multi-tab / split-pane terminals, SFTP file transfer and port-forwarding tunnels, all from your browser.

Open source: https://github.com/casualty01/web_ssh⁠


⁠Features

  • Real SSH: the backend dials genuine SSH connections via golang.org/x/crypto/ssh and bridges them over WebSocket to an xterm.js terminal
  • Session manager: groups, search, rename; password and private-key (with passphrase) authentication
  • Multi-tab & split panes: single pane, left/right, top/bottom and a 4-pane grid; light / dark theme
  • Persistent shells: the remote shell keeps running for 30 minutes after the WebSocket drops; refresh the page to re-attach and replay the output
  • Tunnel manager: local -L, remote -R and dynamic SOCKS5 -D forwarding, with optional auto-start
  • SFTP file browser: streamed uploads (progress / speed / ETA / cancel), download, rename, create folders, recursive delete
  • SSH keepalive: per-session keepalive interval (default 30 s, 0 = off)
  • Unprivileged runtime: the entrypoint starts as root only to fix ownership of the data directory, then drops to the webssh user

⁠Supported Architectures

linux/amd64 · linux/arm64

⁠Image Tags

Images are built and published automatically by GitHub Actions whenever a v* Git tag is pushed. Tag rules:

TagDescriptionExample
<major>.<minor>.<patch>Full version1.1.3
<major>.<minor>Rolling minor tag, always points to the latest patch of that series1.1
sha-<commit>Short commit SHA, for exact rollbackssha-a718467

⁠Quick Start

⁠Option 1: docker run
docker run -d --name webssh \
  --restart unless-stopped \
  -p 8080:8080 \
  -e ADMIN_USER=admin \
  -e ADMIN_PASSWORD='use-a-strong-password' \
  -v webssh-data:/data \
  casualty01/web_ssh:latest

Open http://localhost:8080⁠ and sign in with ADMIN_USER / ADMIN_PASSWORD.

Create docker-compose.yml:

services:
  webssh:
    image: casualty01/web_ssh:latest
    container_name: webssh
    restart: unless-stopped
    ports:
      - "8080:8080"
    environment:
      - ADMIN_USER=admin
      - ADMIN_PASSWORD=use-a-strong-password
    volumes:
      - webssh-data:/data

volumes:
  webssh-data:

Start it:

docker compose up -d
⁠Persisting data in a host directory

To keep data in a specific host directory, just bind-mount it. No manual chown is needed — the entrypoint fixes the ownership of /data on every start:

-v /data/webssh-data:/data

⁠NAS Deployment

Menu names vary between system versions; the steps below follow recent releases. Key points for every platform: container port 8080, mount a volume at /data, and change the default password.

⁠Synology (DSM 7.2+, Container Manager)

Option 1: GUI

  1. Open File Station and create a folder webssh-data under the shared folder docker for persistent data
  2. Open Container Manager → Registry, search for casualty01/web_ssh and download the latest tag
  3. Go to Image, select casualty01/web_ssh and click Run
  4. Port settings: local port 8080 (change it if it is taken), container port 8080
  5. Volume settings: add the folder docker/webssh-data and set the mount path to /data
  6. Environment: add ADMIN_USER and ADMIN_PASSWORD (use your own values)
  7. Enable auto-restart, finish creating the container, then open http://NAS-IP:8080

Option 2: Project (Compose, recommended)

  1. Container Manager → Project → Create
  2. Set the project name to webssh and pick the path docker/webssh (created automatically)
  3. Choose "Create docker-compose.yml" as the source and paste the Compose file from Quick Start, changing the volumes entry to:
    volumes:
      - /volume1/docker/webssh-data:/data
  1. Finish the wizard and start the project. /volume1/docker/webssh-data must match the folder you created in File Station; adjust it if your storage pool is not volume1

Older DSM (7.1 and below): install the Docker package from Package Center, download the image from "Registry", then launch it from "Image". The settings are the same as above.

Tips

  • If the registry search finds nothing or downloads slowly, SSH into the NAS and run sudo docker pull casualty01/web_ssh:latest, or configure a registry mirror / proxy for Docker
  • To connect WebSSH to the NAS itself: enable the SSH service under Control Panel → Terminal & SNMP, then use the NAS LAN IP and port 22 in the session
  • For remote access, set up an HTTPS domain via Control Panel → Login Portal → Advanced → Reverse Proxy, and add WebSocket support in the custom headers (Create → WebSocket adds it automatically); otherwise the terminal cannot connect
⁠fnOS (飞牛)

Option 1: Compose project (recommended)

  1. In File Manager, create a folder such as docker/webssh, with a data subfolder for persistence
  2. Open the Docker app → Compose (projects) → New project
  3. Set the project name to webssh, choose the docker/webssh folder, and paste the Compose file from Quick Start, changing the volumes entry to:
    volumes:
      - ./data:/data
  1. Save and start the project, then open http://NAS-IP:8080

The relative path ./data stores data under the project folder, so you don't need to know the absolute path of the storage volume.

Option 2: Create a container in the GUI

  1. Open the Docker app → Images → Pull image, and enter casualty01/web_ssh:latest
  2. When the pull finishes, create a container from that image
  3. Port: host port 8080 → container port 8080
  4. Storage: mount a host folder (e.g. docker/webssh/data) at /data in the container
  5. Environment: add ADMIN_USER and ADMIN_PASSWORD
  6. Set the restart policy to "unless stopped" (or the equivalent), then create and start the container

Tips

  • To connect WebSSH to the NAS itself: enable SSH in the system settings, then use the NAS LAN IP and port 22 in the session
  • If port 8080 is taken, change only the host port in Compose (left of the colon), e.g. "18080:8080"
  • If the image pull fails, check Docker's registry-mirror settings and whether the NAS can reach Docker Hub
⁠General NAS Notes
  • Always mount /data: otherwise sessions, tunnels and a changed admin password are lost when the container is recreated or updated
  • Never expose the default admin / admin to the internet: for remote access use an HTTPS reverse proxy plus a VPN or IP allow-list
  • Updating the image: on Synology, rebuild/recreate the project (or re-download the image and recreate the container); on fnOS, pull the image again in the Compose project and recreate. As long as the /data folder is kept, no data is lost
  • Reaching the NAS from inside the container: use the NAS LAN IP, not localhost (which points at the container itself)

⁠Environment Variables

VariableDefaultDescription
ADMIN_USERadminBootstrap login username (only used until the password is changed via the UI/API)
ADMIN_PASSWORDadminBootstrap login password — change it in production
LISTEN_ADDR:8080HTTP listen address
DATA_DIR/dataData directory; configuration is stored as store.json inside it
WEB_DIR/app/webStatic web client directory (preset in the image, normally no change needed)

After logging in you can change the credentials from the 🔑 button at the bottom of the sidebar. The new salted-hash credentials are written to store.json and take precedence over the environment variables; changing them signs out every active login, including your own. Logins are valid for 24 hours.

⁠Ports & Volumes

ItemValueDescription
Port8080Web UI and WebSocket share it
Volume/dataHolds sessions, groups, tunnels and admin credentials. Always mount it, or data is lost when the container is removed

⁠Operations

Logs and running version

docker logs -f webssh
curl http://localhost:8080/api/version

/api/version needs no login and returns something like {"version":"1.1.3-20261009-120000"}, in the format <release number>-<UTC build time>. The version is also shown on the login screen and in the sidebar footer, so you can confirm that what is running is the image you just deployed.

Upgrade

# docker run
docker pull casualty01/web_ssh:latest
docker rm -f webssh
# then re-run the docker run command above (the data volume is kept)

# Compose: after changing the image tag if needed
docker compose pull
docker compose up -d

Backup: simply back up the /data volume / directory (e.g. tar czf webssh-data.tgz -C /path/to/data .). It contains plaintext SSH credentials, so keep it safe.


Credentials are POSTed to the login endpoint, so always put TLS in front (Caddy / Nginx / Traefik). WebSocket upgrades for /ws/ must also be forwarded.

Nginx example (request buffering is disabled for large SFTP uploads; otherwise the progress bar jumps straight to 100%):

server {
    listen 443 ssl;
    server_name webssh.example.com;

    # ssl_certificate     ...;
    # ssl_certificate_key ...;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_request_buffering off;
        client_max_body_size 0;
        proxy_read_timeout 3600s;
    }
}

Caddy: reverse_proxy 127.0.0.1:8080 is enough (HTTPS and WebSocket are handled automatically).


⁠FAQ

Q: How do I reach an SSH server on the host or my LAN from inside the container? Sessions use addresses as seen from the container. For sshd on the Docker host, on Linux add --add-host=host.docker.internal:host-gateway and use host.docker.internal as the session host. For LAN machines, just use their LAN IP, as long as the container network can route to it.

Q: I forgot the admin password I changed. What now? Stop the container and back up store.json. Then edit the top-level admin field (the stored username and password hash) — delete or empty it — and restart. With no password hash in admin, the app falls back to the ADMIN_USER / ADMIN_PASSWORD environment variables. Sessions, groups and tunnels in store.json are not affected.

Q: Is my terminal content still there after a page refresh? Yes. The remote shell keeps running for 30 minutes after the WebSocket drops; reconnecting re-attaches to the same shell and replays the output. If you don't reconnect within 30 minutes, the shell is reclaimed.

Q: Where does a local (-L) tunnel listen? Inside the container. To reach it from the host or outside, publish the corresponding port with an extra -p in docker run / Compose.


⁠Security Notes

⚠️ Read this before exposing WebSSH to the internet.

  • SSH host keys are not verified (InsecureIgnoreHostKey is used), which leaves you open to man-in-the-middle attacks on untrusted networks
  • Saved passwords / private keys are stored in plaintext in store.json; restrict access to the volume / directory
  • There is a single shared admin account, suitable for personal or small-team use
  • Change the default admin / admin immediately, put the service behind TLS, and ideally add a VPN or IP allow-list

⁠Source & License


⁠中文

自托管的浏览器 SSH 客户端 —— 类 Xshell 体验,单个 Go 二进制,Alpine 轻量镜像。

通过浏览器即可管理 SSH 会话、多标签/分屏终端、SFTP 文件传输和端口转发隧道。

开源地址:https://github.com/casualty01/web_ssh⁠


⁠特性

  • 真实 SSH:后端使用 golang.org/x/crypto/ssh 建立连接,经 WebSocket 桥接到 xterm.js 终端
  • 会话管理:分组、搜索、重命名;支持密码和私钥(含口令)认证
  • 多标签 + 分屏:单窗格、左右、上下、四宫格;明/暗主题
  • 终端保持:WebSocket 断开后远端 Shell 保留 30 分钟,刷新页面可重新附着并回放输出
  • 隧道管理:本地 -L、远程 -R、动态 SOCKS5 -D,可自动启动
  • SFTP 文件浏览器:流式上传(带进度/速度/剩余时间/取消)、下载、重命名、新建目录、递归删除
  • SSH 保活:每个会话可单独设置 Keepalive 间隔(默认 30 秒,0 为关闭)
  • 非特权运行:容器入口以 root 修正数据目录属主后,降权为 webssh 用户运行

⁠支持架构

linux/amd64 · linux/arm64

⁠镜像标签

镜像在推送 v* 格式的 Git tag 时由 GitHub Actions 自动构建并发布,标签规则如下:

标签说明示例
<major>.<minor>.<patch>完整版本号1.1.3
<major>.<minor>小版本滚动标签,自动指向该系列最新补丁版1.1
sha-<commit>对应提交的短 SHA,用于精确回溯sha-a718467

⁠快速开始

⁠方式一:docker run
docker run -d --name webssh \
  --restart unless-stopped \
  -p 8080:8080 \
  -e ADMIN_USER=admin \
  -e ADMIN_PASSWORD='请改成强密码' \
  -v webssh-data:/data \
  casualty01/web_ssh:latest

启动后访问 http://localhost:8080⁠,使用 ADMIN_USER / ADMIN_PASSWORD 登录。

⁠方式二:Docker Compose(推荐)

新建 docker-compose.yml:

services:
  webssh:
    image: casualty01/web_ssh:latest
    container_name: webssh
    restart: unless-stopped
    ports:
      - "8080:8080"
    environment:
      - ADMIN_USER=admin
      - ADMIN_PASSWORD=admin
    volumes:
      - webssh-data:/data

volumes:
  webssh-data:

启动:

docker compose up -d
⁠使用宿主机目录持久化

如果希望把数据放在宿主机指定目录,直接挂载即可,无需手动 chown(入口脚本每次启动会自动修正 /data 的属主):

-v /data/webssh-data:/data

⁠NAS 部署

界面名称因系统版本而异,以下以较新版本为准。通用要点:容器端口 8080,数据卷挂载到 /data,密码务必改掉。

⁠群晖 Synology(DSM 7.2+,Container Manager)

方式一:图形界面

  1. 打开 File Station,在共享文件夹 docker 下新建目录 webssh-data,用于持久化数据
  2. 打开 Container Manager → 注册表,搜索 casualty01/web_ssh,下载 latest 标签
  3. 进入 映像,选中 casualty01/web_ssh,点击 运行
  4. 端口设置:本地端口填 8080(若被占用可改为其他端口),容器端口 8080
  5. 存储空间设置:添加文件夹,选择 docker/webssh-data,装载路径填 /data
  6. 环境变量:添加 ADMIN_USER 和 ADMIN_PASSWORD(改成你自己的值)
  7. 勾选"启用自动重新启动",完成创建后访问 http://NAS-IP:8080

方式二:项目(Compose,推荐)

  1. Container Manager → 项目 → 新增
  2. 项目名称填 webssh,路径选择 docker/webssh(会自动创建)
  3. 来源选择"创建 docker-compose.yml",粘贴「快速开始」中的 Compose 内容,并把 volumes 改为:
    volumes:
      - /volume1/docker/webssh-data:/data
  1. 按向导完成后启动项目。其中 /volume1/docker/webssh-data 需要与 File Station 里创建的目录一致,存储池不是 volume1 时请对应修改

旧版 DSM(7.1 及以下):套件中心安装 Docker,在"注册表"下载镜像后,在"映像"中启动,设置项与上面相同。

使用提示

  • 如果注册表搜索不到或下载很慢,可以在 SSH 登录 NAS 后执行 sudo docker pull casualty01/web_ssh:latest,或为 Docker 配置可用的镜像加速/代理
  • 想用 WebSSH 连接 NAS 本机:先在 控制面板 → 终端机和 SNMP 中启用 SSH 服务,会话里主机填 NAS 的局域网 IP,端口 22
  • 需要外网访问时,建议用 控制面板 → 登录门户 → 高级 → 反向代理 配置 HTTPS 域名,并在自定义标头中添加 WebSocket 支持(点击"创建 → WebSocket"即可自动添加),否则终端无法连接
⁠飞牛 fnOS

方式一:Compose 项目(推荐)

  1. 在 文件管理 中新建目录,例如 docker/webssh,并在其中再建 data 子目录用于持久化
  2. 打开 Docker 应用 → Compose(项目)→ 新增项目
  3. 项目名称填 webssh,路径选择上面建好的 docker/webssh 目录,粘贴「快速开始」中的 Compose 内容,并把 volumes 改为:
    volumes:
      - ./data:/data
  1. 保存并启动项目,访问 http://NAS-IP:8080

这里使用相对路径 ./data,数据会保存在项目目录下的 data 中,无需关心存储卷的绝对路径。

方式二:图形界面创建容器

  1. 打开 Docker 应用 → 镜像 → 拉取镜像,输入 casualty01/web_ssh:latest
  2. 拉取完成后选择该镜像创建容器
  3. 端口:主机端口 8080 → 容器端口 8080
  4. 存储:挂载宿主机目录(如 docker/webssh/data)到容器路径 /data
  5. 环境变量:添加 ADMIN_USER、ADMIN_PASSWORD
  6. 重启策略选择"除非手动停止"(或同类选项),创建并启动

使用提示

  • 想用 WebSSH 连接 fnOS 本机:在系统设置中开启 SSH,会话里主机填 NAS 的局域网 IP,端口 22
  • 8080 端口被占用时,只改 Compose 里冒号左边的主机端口,例如 "18080:8080"
  • 如果镜像拉取失败,请检查 Docker 的镜像加速设置,或网络能否访问 Docker Hub
⁠NAS 通用注意事项
  • 一定要挂载 /data:否则重建/更新容器后,会话、隧道和修改过的管理员密码都会丢失
  • 不要把默认的 admin / admin 暴露到公网:需要远程访问时,使用 HTTPS 反向代理,并配合 VPN 或 IP 白名单
  • 更新镜像:群晖在"项目"中选择"构建/重新创建"(或在"映像"重新下载后重建容器);飞牛在 Compose 项目中重新拉取镜像后重建。只要 /data 目录保持不变,数据不会丢
  • 容器内访问 NAS 本机:使用 NAS 的局域网 IP,不要填 localhost(那指向容器自身)

⁠环境变量

变量默认值说明
ADMIN_USERadmin初始登录用户名(仅在通过界面/API 修改过密码之前生效)
ADMIN_PASSWORDadmin初始登录密码,生产环境务必修改
LISTEN_ADDR:8080HTTP 监听地址
DATA_DIR/data数据目录,配置保存为其中的 store.json
WEB_DIR/app/web静态前端目录(镜像内已设置,一般无需改动)

登录后可在侧边栏底部点击 🔑 修改账号密码。修改后新的加盐哈希凭据会写入 store.json,优先于环境变量,并会使所有已登录会话失效(包括当前会话)。登录有效期为 24 小时。

⁠端口与数据卷

项目值说明
端口8080Web 界面和 WebSocket 共用
数据卷/data保存会话、分组、隧道配置和管理员凭据,务必挂载,否则删除容器后数据丢失

⁠日常运维

查看日志与运行版本

docker logs -f webssh
curl http://localhost:8080/api/version

/api/version 无需登录,返回形如 {"version":"1.1.3-20261009-120000"},格式为 <发布版本号>-<UTC 构建时间>。版本号同时显示在登录页和侧边栏底部,可用来确认当前运行的确实是刚部署的镜像。

升级

# docker run 方式
docker pull casualty01/web_ssh:latest
docker rm -f webssh
# 然后用新的 tag 重新执行上面的 docker run 命令(数据卷保持不变)

# Compose 方式:修改 image 的 tag 后
docker compose pull
docker compose up -d

备份:直接备份 /data 数据卷/目录即可(例如 tar czf webssh-data.tgz -C /path/to/data .)。其中含明文保存的 SSH 凭据,请妥善保管。


⁠反向代理(强烈建议)

登录凭据通过 POST 提交,请务必在前面加一层 TLS(Caddy / Nginx / Traefik)。同时需要转发 /ws/ 的 WebSocket 升级请求。

Nginx 示例(已针对 SFTP 大文件上传关闭请求缓冲,否则进度条会瞬间跳到 100%):

server {
    listen 443 ssl;
    server_name webssh.example.com;

    # ssl_certificate     ...;
    # ssl_certificate_key ...;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_request_buffering off;
        client_max_body_size 0;
        proxy_read_timeout 3600s;
    }
}

Caddy:一行 reverse_proxy 127.0.0.1:8080 即可(自动处理 HTTPS 和 WebSocket)。


⁠常见问题

Q:容器内如何访问宿主机或内网的 SSH 服务器? 会话里填写的是「容器所能访问到的」地址。访问宿主机上的 sshd 时,Linux 可以加 --add-host=host.docker.internal:host-gateway,然后在会话中填 host.docker.internal;访问内网机器则直接填内网 IP,确保容器网络能路由到即可。

Q:忘记了修改后的管理员密码怎么办? 先停止容器并备份 store.json,然后编辑其中顶层的 admin 字段(即保存的账号和密码哈希),将其删除或清空,再重启容器。admin 中没有密码哈希时,程序会重新使用 ADMIN_USER / ADMIN_PASSWORD 环境变量登录。store.json 中的会话、分组和隧道配置不受影响。

Q:刷新页面后终端内容还在吗? 在。远端 Shell 在 WebSocket 断开后继续保留 30 分钟,重新连接会附着到同一个 Shell 并回放历史输出。超过 30 分钟未重连则 Shell 会被回收。

Q:隧道里的本地转发 -L 监听在哪里? 监听在容器内。需要从宿主机或外部访问时,请在 docker run / Compose 中额外映射对应端口(-p)。


⁠安全提示

⚠️ 暴露到公网之前请务必阅读。

  • 未校验 SSH 主机密钥(使用了 InsecureIgnoreHostKey),在不可信网络中存在中间人风险
  • 已保存的密码/私钥以明文存储在 store.json 中,请限制对数据卷/目录的访问权限
  • 仅有一个共享的管理员账号,适合个人或小团队使用
  • 立即修改默认的 admin / admin,并将服务置于 TLS 之后,最好再配合 VPN 或 IP 白名单

⁠源码与许可证

Tag summary

Content type

Image

Digest

sha256:6c851af80…

Size

6.8 MB

Last updated

17 days ago

docker pull casualty01/web_ssh