Cách truy cập DSH Web UI trên VPS từ laptop
DSH bind tại 127.0.0.1:3080 nên URL in trên VPS không mở từ laptop. Xem 3 cách truy cập an toàn và cách không nên dùng để tránh lộ shell.
Vì sao web UI của DSH không mở được từ laptop
Web UI của DSH bind vào loopback interface, nên URL mà terminal in ra chỉ hoạt động trên máy đã in URL đó. npx @deepseek-ai/dsh web báo cáo http://127.0.0.1:3080, còn 127.0.0.1 có nghĩa là “máy tính đang đọc địa chỉ này” đối với bất kỳ máy tính nào đọc nó. Laptop của bạn đọc địa chỉ đó, kiểm tra loopback interface của chính nó và không tìm thấy tiến trình nào đang listening ở đó. Nếu phần gây nhầm lẫn là chính địa chỉ này, phần giải thích vì sao dsh in http://127.0.0.1:3080 ngay từ đầu sẽ trình bày chi tiết hơn. Không thay đổi địa chỉ mà DSH bind vào. Hãy tạo một đường truy cập có xác thực từ laptop đến địa chỉ loopback của VPS.
Giá trị loopback mặc định là đúng và mọi phương thức trên trang này đều giữ nguyên giá trị đó. Phía sau port 3080 là một agent chạy shell command trên server của bạn, còn web UI không có trang login. Ở đây, loopback là cơ chế kiểm soát truy cập duy nhất của DSH: để truy cập socket, bạn phải có shell trên máy đó trước.
DSH bind vào đâu và cách kiểm tra trên máy của bạn
Được kiểm tra vào ngày 17 August 2026, README của DeepSeek Harness cho biết npx @deepseek-ai/dsh web “khởi động Web UI, mặc định phục vụ tại http://127.0.0.1:3080”. Tài liệu CLI trong cùng repository ghi nhận --port <num> là tùy chọn override có giá trị mặc định là 3080, còn --host <addr> là tùy chọn override “cố ý từ chối 0.0.0.0”. DSH đang ở giai đoạn developer preview, và README viết cảnh báo bằng chữ in hoa rằng sẽ có các thay đổi gây mất tương thích. Hãy xác nhận cả hai giá trị trên chính bản cài đặt của bạn, thay vì tin vào bất kỳ trang nào, kể cả trang này.
dsh --profile web --dump-config
ss -ltnp | grep 3080--dump-config in cây cấu hình đã ghép mà không khởi động agent, nên nó hiển thị host và port còn hiệu lực sau mọi lớp patch. ss hiển thị những gì đang thực sự listening tại thời điểm hiện tại. Một dòng bình thường có dạng như sau:
LISTEN 0 511 127.0.0.1:3080 0.0.0.0:* users:(("node",pid=8123,fd=24))Đọc địa chỉ ở trước dấu hai chấm, không đọc phần nào khác. 127.0.0.1:3080 chỉ là loopback, đây là giá trị bạn cần. 0.0.0.0:3080 nghĩa là mọi interface trên máy, bao gồm cả interface public. Nếu ss không in ra dòng nào, DSH chưa chạy và không tunnel nào có thể giúp cho đến khi DSH chạy. Phần hướng dẫn để đạt đến trạng thái đó có tại cài đặt và chạy DeepSeek Harness trên VPS.
Các flag chỉ áp dụng cho một lần chạy. Để giữ cố định một giá trị, hãy chỉnh sửa lớp profile patch. dsh --profile <name> khởi động profile tại $DSH_HOME/profiles/<name>, còn các layer được áp dụng theo thứ tự: bundle patch, cordis.patch.yml của profile, $DSH_HOME/cordis.patch.yml ở cấp home, sau đó là overlay --patch nếu có. Các thiết lập listen nằm dưới plugin @deepseek-ai/dsh-host-webserver, tại host và port. Trước khi chỉnh sửa, hãy đọc cây cấu hình đã ghép của chính bạn bằng --dump-config, vì web profile tự tạo lại từ template được đóng gói trong lần boot đầu tiên và bản preview vẫn liên tục thay đổi cấu trúc của template đó.
Cổng kiểm tra thứ hai: hàng rào tin cậy cho /api
Đưa được packet đến port 3080 mới chỉ giải quyết một nửa vấn đề. DSH còn có một bước kiểm tra thứ hai. Bước này gây ra lỗi khó hiểu: trang tải được, giao diện hiển thị, nhưng sau đó không thao tác nào hoạt động.
Plugin @deepseek-ai/dsh-client-connection chứa setting trustedHosts, được mô tả là “Các authority mà deployment này phục vụ ngoài loopback: host:port chính xác hoặc host không kèm port để khớp với mọi port”. Hàng rào này từ chối mọi request /api có header Host không phải loopback và cũng không nằm trong danh sách đó. Browser hiển thị việc từ chối như sau:
transport failure for /api/host.describe: HTTP 403Vì vậy, mọi proxy đặt trước DSH phải khai báo hostname mà browser sử dụng. CLI nhận tham số --trusted-host <authority> và tham số này có thể lặp lại:
dsh web --trusted-host dsh.example.com --trusted-host dsh.example.com:8443Hàng rào so sánh header Host dưới dạng chuỗi thuần. Vì vậy, cả hai cách viết đều xuất hiện ở trên. Với nó, dsh.example.com và dsh.example.com:8443 là hai authority khác nhau, tương tự localhost:3080 và 127.0.0.1:3080. Lỗi 403 không giải thích được hầu như luôn do URL trên thanh địa chỉ khác cách viết với danh sách tin cậy.
Hàng rào này là kiểm tra origin. Nó không phải cơ chế xác thực. Browser tự cấm browser đặt header Host, nên bước kiểm tra này ngăn một trang trên site khác điều khiển agent của bạn. Mọi client không phải browser đều có thể tự ghi header đó. Hãy xem trustedHosts là setting tương thích cho proxy, không bao giờ xem đây là một cơ chế bảo mật.
Tùy chọn 1: SSH local forward
Hãy dùng cách này trước vì nó không thay đổi gì trên server.
ssh -N -L 127.0.0.1:3080:127.0.0.1:3080 you@your-vps-L mở một listener trên laptop của bạn và chuyển tiếp từng kết nối đến 127.0.0.1:3080 sau khi hostname này được resolve trên VPS. -N có nghĩa là “không chạy remote command”, nên session chỉ mang forward và không làm gì khác. Việc viết phía local là 127.0.0.1:3080 thay vì chỉ ghi 3080 là có chủ ý: nó cố định listener phía laptop trên loopback, để thiết lập GatewayPorts trong cấu hình SSH client không thể âm thầm public lại agent lên network mà bạn đang kết nối.
Bây giờ mở http://localhost:3080 trong browser. Ở đây có 2 thứ tự hoạt động mà không cần cấu hình thêm. Header Host là một loopback authority, nên fence /api được vượt qua mà không cần cấu hình nào. Browser cũng coi http://localhost là một secure context. Điều này quan trọng vì web app DSH gọi crypto.randomUUID() khi khởi động, còn browser chỉ cung cấp function đó qua HTTPS hoặc trên loopback origin.
Thêm -f để ssh tự chuyển xuống background sau khi forward hoạt động:
ssh -f -N -L 127.0.0.1:3080:127.0.0.1:3080 you@your-vpsĐánh đổi thực tế là như sau. Không có listener mới trên public address và không cần thay đổi firewall rule, nên mức tăng exposure là nhỏ nhất có thể. Toàn bộ access control nằm trong cấu hình SSH của bạn. Vì vậy, SSH chỉ dùng key và tắt đăng nhập bằng password là điều kiện bắt buộc, không phải tùy chọn bổ sung. Nhược điểm là tunnel chỉ thuộc về một client session. Tunnel sẽ dừng khi laptop sleep, bạn phải tự khởi động lại và không thể sử dụng tunnel này trên phone.
Tùy chọn 2: Tailscale serve trên VPS
Tailscale tạo một mạng riêng giữa các thiết bị của bạn. Cài Tailscale trên VPS sẽ cấp cho máy đó một địa chỉ 100.x.y.z mà chỉ các thiết bị của bạn mới định tuyến được đến.
Tham gia mạng đó là chưa đủ. Đây là chỗ nhiều người bị vướng. DSH không lắng nghe trên địa chỉ 100.x.y.z vì nó đang lắng nghe trên loopback. Khi browser truy cập http://100.x.y.z:3080, kết nối bị từ chối vì không có socket nào bind vào địa chỉ đó.
tailscale serve là thành phần kết nối hai phần này. Nó chạy trên VPS, nhận traffic từ mạng riêng của bạn rồi proxy đến một địa chỉ local:
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
sudo tailscale serve --bg --https=443 localhost:3080
tailscale serve statustailscale serve status in URL theo dạng https://<machine>.<tailnet>.ts.net/. Hãy khởi động DSH với hostname đó trong danh sách trusted. Nếu không, /api sẽ trả về 403 đúng như mô tả ở trên:
dsh web --trusted-host your-vps.your-tailnet.ts.netCách này cũng loại bỏ vấn đề secure context. Tailscale thực hiện TLS termination bằng HTTPS thật, với certificate được cấp cho hostname đó. Vì vậy, crypto.randomUUID() khả dụng và UI có thể khởi động trong browser trên điện thoại. Không có traffic nào đi vào public internet vì serve chỉ publish trong mạng riêng của bạn.
Không được thay bằng tailscale funnel ở đây. Đây là cùng nhóm command nhưng hướng đến public internet. Nó sẽ đưa một agent có quyền shell lên một hostname mà bất kỳ ai cũng có thể resolve. Hãy đọc cách serve giữ traffic trong tailnet còn funnel publish traffic ra ngoài trước khi chạy một trong hai command này. Để xem phiên bản phù hợp với điện thoại của toàn bộ thiết lập này, hãy xem cách truy cập agent tự host từ điện thoại.
Đổi lại, bạn phải chấp nhận thêm một dependency. Mọi thiết bị cần UI đều phải tham gia mạng, và một coordination server không do bạn vận hành sẽ quyết định thiết bị nào được phép tham gia. Nếu điều đó không phù hợp, control server Headscale tự host sử dụng cùng protocol trên phần cứng do bạn sở hữu.
Tùy chọn 3: reverse proxy TLS có xác thực
Dùng tùy chọn này khi trình duyệt không thể tham gia private network, chẳng hạn trên một máy bạn không quản trị. Lúc này bạn đang public một hostname ra Internet, nên cơ chế xác thực phải là xác thực thật và phải do proxy thực hiện. DSH không cung cấp cơ chế này.
TLS là viết tắt của transport layer security, lớp mã hóa đứng sau https://. Trỏ nginx đến cổng loopback và đặt password ở phía trước. Block map phải nằm trong context http, vì vậy hãy đặt nó trong một file riêng dưới /etc/nginx/conf.d/:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}Tiếp theo là cấu hình cho chính site:
server {
listen 443 ssl;
server_name dsh.example.com;
ssl_certificate /etc/letsencrypt/live/dsh.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/dsh.example.com/privkey.pem;
auth_basic "dsh";
auth_basic_user_file /etc/nginx/dsh.htpasswd;
location / {
proxy_pass http://127.0.0.1:3080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 3600s;
proxy_buffering off;
}
}Tạo password file, kiểm tra cấu hình rồi reload:
sudo apt install -y apache2-utils
sudo htpasswd -c /etc/nginx/dsh.htpasswd you
sudo nginx -t && sudo systemctl reload nginxnginx -t phải in ra syntax is ok và test is successful. Nếu lệnh in lỗi, nginx vẫn tiếp tục phục vụ cấu hình cũ, nên hiện tại chưa có gì bị hỏng. Sau đó khởi động DSH với public name trong danh sách trusted, vì proxy_set_header Host $host forward dsh.example.com và fence sẽ từ chối nếu không có thiết lập này:
dsh web --trusted-host dsh.example.comHai directive trong đó rất quan trọng. Các header Upgrade và Connection giữ connection dài hạn của UI luôn mở. Nếu thiếu chúng, nginx sẽ đóng connection, khiến giao diện reconnect liên tục và hiển thị state cũ. proxy_read_timeout 3600s ngăn nginx ngắt một agent run mất hơn 60 giây mặc định. Phần giải thích cơ sở của các thiết lập còn lại nằm trong giải thích cấu hình nginx reverse proxy theo từng directive, còn lựa chọn proxy được trình bày trong so sánh nginx với Caddy và Traefik.
Cần hiểu rõ basic auth mang lại gì. Nó chặn các scanner quét ngẫu nhiên và tốt hơn nhiều so với việc để một cổng mở. Tuy nhiên, đây vẫn chỉ là một password dùng chung đặt trước shell, không có second factor và không có cách revoke quyền của từng người. Bất kỳ ai biết password đều có thể chạy command với user mà DSH chạy dưới user đó. Nếu có nhiều người cần truy cập, hãy đặt một identity proxy thực thụ ở phía trước. Trước khi mở rộng thêm, hãy đọc các quy tắc an toàn khi chạy coding agent trên VPS.
Điều gì xảy ra nếu bind vào 0.0.0.0 và mở cổng
Cách tắt hiển nhiên là bind lại và mở firewall. DSH chặn phần đầu tiên. --host <addr> cố ý từ chối 0.0.0.0, còn plugin @deepseek-ai/dsh-host-webserver ghi rõ host chỉ chấp nhận 127.0.0.1 hoặc 0.0.0.0, nên chỉ có một giá trị được hỗ trợ và một giá trị khác bị CLI từ chối. Có các plugin cộng đồng loại bỏ kiểm tra này. Chúng đi kèm cảnh báo, và các cảnh báo đó là chính xác. Lớp plugin này hữu ích hơn nhiều khi dùng theo hướng ngược lại, trong đó giới hạn chi tiêu và các rule cấp quyền cho tool thu hẹp những gì agent được phép làm thay vì mở rộng địa chỉ mà agent lắng nghe.
Đây là những gì bạn thực sự có được khi ép cấu hình này. Cổng 3080 phản hồi trên địa chỉ IP public của bạn. Không có trang đăng nhập. Hàng rào /api kiểm tra một header Host mà bất kỳ client không phải browser nào cũng tự ghi vào, nên thêm địa chỉ của bạn vào trustedHosts không thay đổi gì đối với attacker đang nắm curl. Thứ bạn đã public là một agent có thể chạy shell command bằng user của bạn, cùng với các credential trong $DSH_HOME, nơi DSH lưu profile và API key. Đây là remote code execution trên server của bạn, kèm theo hóa đơn từ provider. Scanner liên tục quét toàn bộ không gian địa chỉ, nên IP không được liệt kê không phải là một biện pháp kiểm soát. Mọi secret mà agent có thể đọc đều có thể bị lấy đi cùng shell.
Mọi phương thức ở trên đều tồn tại để bạn không bao giờ phải làm việc này. SSH forward là lựa chọn mặc định phù hợp cho một người dùng trên một laptop. Chuyển sang tailscale serve khi có điện thoại tham gia hoặc khi bạn muốn dùng certificate thực sự. Chỉ dùng reverse proxy public khi một browser mà bạn không quản trị phải truy cập UI, và phải đặt authentication thực sự ở phía trước.
FAQ
Vì sao http://127.0.0.1:3080 không mở được từ laptop của tôi?
Vì 127.0.0.1 có nghĩa là “máy đang đọc địa chỉ này”. DSH in địa chỉ đó trên VPS, nên địa chỉ này đúng trong ngữ cảnh của VPS. Laptop của bạn đọc cùng chuỗi đó rồi truy cập loopback interface của chính nó, nơi không có tiến trình nào đang listening. Kiểm tra phía server bằng ss -ltnp | grep 3080 trên VPS. Nếu thấy dòng hiển thị 127.0.0.1:3080, nghĩa là DSH đang chạy và cố ý bind vào loopback. Bạn cần tunnel hoặc proxy, không phải một bind address khác.
Tôi có thể chạy dsh web với --host 0.0.0.0 không?
Không. Tài liệu CLI của repository, được kiểm tra vào ngày 17 August 2026, ghi rõ --host <addr> là tùy chọn override cố ý từ chối 0.0.0.0. Nguyên nhân là DSH chạy shell command và không có trang đăng nhập, nên bind vào mọi interface sẽ public một shell không có xác thực trên địa chỉ IP public của bạn. Các patch từ community đã loại bỏ kiểm tra này. Nếu áp dụng patch, bạn phải tự chịu trách nhiệm cấu hình firewall và cơ chế xác thực mà DSH không cung cấp.
Vì sao mọi lệnh gọi /api đều trả về HTTP 403 phía sau reverse proxy?
/api trust fence từ chối mọi request có header Host không phải là địa chỉ loopback và cũng không có trong trustedHosts. Phía sau proxy, header đó chứa public name của bạn, nên trust fence từ chối request và browser ghi log transport failure for /api/host.describe: HTTP 403. Khởi động DSH với --trusted-host <your name> và phải ghi đúng chính tả, gồm cả port nếu URL có port, vì phép so sánh là so khớp chuỗi literal.
Vì sao DSH UI tải được nhưng không bao giờ khởi động xong khi dùng HTTP thuần?
Web app gọi crypto.randomUUID() trong lúc khởi động. Browser chỉ cho phép function này trong secure context: HTTPS hoặc loopback origin như http://localhost. Khi được phục vụ qua HTTP thuần từ một bare IP address, function này là undefined. Vì vậy các lệnh gọi phụ thuộc vào nó bị lỗi và interface không bao giờ hiển thị đầy đủ. Phục vụ UI qua HTTPS với tailscale serve hoặc dùng TLS reverse proxy sẽ xử lý vấn đề này. Truy cập UI qua SSH forward trên http://localhost cũng có tác dụng.
Một developer nên dùng phương pháp nào?
SSH local forward. Cách này không thêm gì vào server, không thay đổi firewall rule và dùng lại SSH key mà bạn đã tin cậy. Chạy ssh -f -N -L 127.0.0.1:3080:127.0.0.1:3080 you@your-vps, sau đó mở http://localhost:3080. Chuyển sang tailscale serve khi bạn muốn mở UI trên phone hoặc muốn access tiếp tục hoạt động sau khi laptop chuyển sang sleep.