dsh web http://127.0.0.1:3080 접속 안 되는 이유
dsh 실행 시 출력되는 http://127.0.0.1:3080은 웹 UI가 로컬 호스트에만 바인딩되어 외부 접근이 차단되었음을 의미합니다. 보안상 포트 3080을 직접 노출하는 것은 위험하므로 SSH 터널링을 통해 안전하게 접속하는 방법을 확인하십시오.
dsh web: http://127.0.0.1:3080의 의미
VPS에서 DeepSeek Harness 웹 프로필을 시작하면 두 줄이 출력된 뒤 대기 상태가 됩니다.
dsh web: http://127.0.0.1:3080
Ready.127.0.0.1은 루프백 주소입니다. 이는 기기가 자기 자신과 통신할 때 사용하는 주소입니다. 127.0.0.1에 바인딩된 소켓은 동일한 기기 내의 프로세스로부터 오는 연결만 허용하며, 외부에서의 연결은 차단합니다. 따라서 해당 줄은 웹 UI가 수신 대기 중인 위치와 접근이 허용된 대상을 동시에 알려줍니다. 오직 dsh가 실행 중인 기기에서만 접근할 수 있습니다.
이것이 바로 노트북의 브라우저에 해당 URL을 붙여넣어도 아무런 반응이 없는 이유입니다. 노트북의 127.0.0.1은 노트북 자신을 가리킵니다. 반면 Harness는 VPS의 127.0.0.1에서 수신 대기 중인데, 이는 서로 다른 루프백 스택을 가진 별개의 기기입니다. 설정이 잘못된 것은 아닙니다. 연결을 외부로 전달하는 과정이 필요합니다.
공식 README에는 기본 설정이 명확히 명시되어 있습니다: "명령어를 실행하면 웹 UI가 시작되며, 기본적으로 http://127.0.0.1:3080에서 서비스됩니다." 바인딩 주소는 웹 서버 호스트 플러그인인 @deepseek-ai/dsh-host-webserver에서 결정되며, 이 플러그인의 host 키는 "Listen host; 지원되는 두 가지 값은 loopback과 all-interfaces입니다"라고 설명되어 있습니다. 설정을 변경하지 않으면 기본값인 loopback이 적용됩니다. 포트 개념이 생소하다면, 이 모든 동작의 기반이 되는 주소와 포트 모델을 다룬 Linux에서의 포트 작동 방식을 확인하십시오.
웹 UI가 localhost에만 바인딩되는 이유
dsh은 에이전트 하네스입니다. 브라우저 탭은 사용자가 선택한 작업 디렉터리에서 셸 명령을 실행하고, 파일을 읽고 쓰며, 모델 API 키를 사용하는 프로세스를 위한 제어 인터페이스입니다. 해당 페이지를 로드할 수 있는 사람은 누구나 dsh를 실행 중인 사용자의 권한으로 이 모든 작업을 수행할 수 있습니다.
따라서 포트 3080은 읽기 전용 대시보드가 아닙니다. 해당 페이지를 로드하면 서버에서 명령을 실행할 수 있는 권한이 부여됩니다.
웹 UI를 열면 바로 세션 목록으로 이동합니다. 개발자 프리뷰 버전에는 사용자 계정이나 원격 인증 기능이 포함되어 있지 않으므로 로그인 프롬프트가 없습니다. 루프백 주소에서는 운영 체제가 접근 제어 역할을 수행하며 로컬 프로세스만 접근할 수 있으므로 일관성이 유지됩니다. 동일한 서버를 공인 IP가 있는 VPS의 0.0.0.0에 바인딩하면, 아무런 보호 장치 없이 인터넷 전체에 해당 페이지가 노출됩니다. 자동화된 스캐너가 비표준 포트를 지속적으로 탐색하므로, 3080 포트를 공개하는 것은 발견되는 것과 다름없다고 간주해야 합니다.
방화벽에서 3080 포트를 열지 마십시오. 또한 공용 VPS에서 웹 서버host을0.0.0.0로 설정하지 마십시오. 해당 조합은 서버에 먼저 접속하는 누구에게나 명령 실행 권한을 넘겨주는 것과 같습니다.
동일한 논리가 서버에서 실행하는 모든 에이전트 런타임에 적용됩니다. 이것이 바로 VPS에서 코딩 에이전트를 안전하게 실행하는 방법이 동일한 규칙에서 시작되는 이유입니다. 에이전트의 제어 포트는 비공개로 유지해야 하며, 신뢰할 수 있는 수단을 통해서만 접근해야 합니다.
노트북에서 dsh 웹 UI에 접속하려면 어떻게 해야 합니까?
세 가지 정석적인 방법이 있으며, 어떤 방법을 선택하든 harness는 loopback에 바인딩된 상태를 유지합니다.
- SSH 터널을 사용합니다. 공개 인터페이스에서 새로 수신 대기하는 포트가 없으며, 이미 인증 정보를 보유하고 있습니다. 이 방법을 권장합니다.
- 사설 오버레이 네트워크를 구성합니다. 이렇게 하면 본인의 기기에서만 UI에 접근할 수 있고 외부에는 노출되지 않습니다.
- 리버스 프록시를 사용합니다. TLS(전송 계층 보안)를 종료하고 비밀번호 인증을 거친 뒤에만 요청을 전달하도록 설정합니다.
이 방법들의 차이점은 브라우저를 loopback으로 연결하는 방식에 있습니다. 어떤 경우에도 harness를 loopback에서 해제해서는 안 됩니다.
SSH 터널로 접속하기
이 명령은 VPS가 아닌 로컬 노트북에서 실행합니다.
ssh -N -L 3080:127.0.0.1:3080 you@your-vps명령을 실행한 상태로 두고, 로컬 브라우저에서 http://127.0.0.1:3080에 접속합니다. 웹 UI가 로드됩니다.
-L 인자는 콜론으로 구분된 세 개의 필드를 가집니다. 첫 번째는 노트북에서 열 포트 번호입니다. 두 번째와 세 번째는 연결을 전달할 대상의 주소와 포트입니다. 여기서 중요한 점은 중간 필드의 127.0.0.1가 트래픽이 이미 도착한 이후 VPS의 SSH 서버에 의해 해석된다는 것입니다. 즉, 로컬이 아닌 VPS의 루프백 주소를 의미합니다. 이것이 바로 dsh이 출력한 주소이며, 브라우저로 직접 접속할 때는 안 되던 연결이 터널을 통하면 작동하는 이유입니다.
-N은 SSH에게 원격 명령을 실행하지 말라고 지시하므로, 셸 없이 포트 포워딩 기능만 얻게 됩니다. 조용히 실패하는 대신 오류를 명확히 알 수 있는 백그라운드 터널을 만들려면 다음을 사용합니다.
ssh -N -f -o ExitOnForwardFailure=yes -o ServerAliveInterval=30 -L 3080:127.0.0.1:3080 you@your-vps-f은 인증 후 프로세스를 백그라운드로 보냅니다. ExitOnForwardFailure=yes는 겉보기보다 중요합니다. 이 옵션이 없으면 포워딩 설정에 실패해도 SSH는 성공적으로 연결된 것처럼 동작하므로, 경고 없이 터널이 죽은 상태로 세션만 유지됩니다. ServerAliveInterval=30은 30초마다 keepalive 패킷을 보내 카페나 호텔 라우터의 NAT(네트워크 주소 변환) 타임아웃으로 인해 유휴 터널이 끊기지 않게 합니다.
확인해야 할 사항
VPS에서 실제로 어떤 포트가 리스닝 중인지 확인합니다.
ss -ltnp | grep 3080정상적인 결과라면 루프백 주소가 표시됩니다.
LISTEN 0 511 127.0.0.1:3080 0.0.0.0:* users:(("node",pid=1042,fd=21))만약 로컬 주소 열에 0.0.0.0:3080이 표시된다면, 웹 UI가 공용 인터페이스를 포함한 모든 인터페이스에서 열려 있는 상태입니다. 즉시 중단하고 바인딩 설정을 수정하십시오. ss가 소켓은 출력하지만 users: 필드가 비어 있다면 sudo를 추가하여 실행하십시오. 다른 사용자가 소유한 소켓의 프로세스 이름은 권한 문제로 숨겨질 수 있기 때문입니다.
터널 시작이 거부될 때
SSH가 다음을 출력하고 종료되는 경우:
bind [127.0.0.1]:3080: Address already in use
channel_setup_fwd_listener_tcpip: cannot listen to port: 3080이는 서버가 아닌 노트북의 문제입니다. 로컬의 3080 포트를 이미 다른 프로세스가 사용 중이며, 보통 이전에 띄워둔 터널이 남아있는 경우입니다. 사용 가능한 다른 로컬 포트를 선택하십시오.
ssh -N -L 3081:127.0.0.1:3080 you@your-vps첫 번째 필드만 변경했으므로, 이제 브라우저로 http://127.0.0.1:3081에 접속하면 됩니다. 이때 터널은 여전히 3080 포트에서 대기 중입니다. 두 숫자가 반드시 일치할 필요는 없습니다.
터널은 생성되었으나 브라우저에서 연결 거부나 빈 응답이 발생한다면, 트래픽이 VPS까지 도달했으나 목적지에서 아무것도 찾지 못한 것입니다. dsh이 종료되었거나 다른 포트에 바인딩된 상태입니다. 서버에서 ss -ltnp | grep 3080로 확인하십시오.
한 가지 더 주의할 점이 있습니다. 포그라운드에서 실행한 npx @deepseek-ai/dsh web은 셸이 닫히면 함께 종료되므로, 로그아웃하는 순간 터널도 끊깁니다. 이를 방지하려면 tmux 내부에서 실행하거나 systemd 사용자 서비스로 등록해야 합니다. 이는 VPS에서 코딩 에이전트 계속 실행하기에서 다룬 문제와 동일합니다. SSH 설정을 다루는 김에 VPS의 SSH 보안 강화하기를 먼저 수행하는 것을 권장합니다. 터널을 사용하면 SSH 로그인이 에이전트로 향하는 유일한 통로가 되기 때문입니다.
사설 오버레이 네트워크를 통한 접근
오버레이 네트워크를 사용하면 VPS와 노트북이 오직 본인의 기기만 참여하는 사설 네트워크상의 주소를 갖게 됩니다. Tailscale이 일반적으로 선택되는 도구이며, serve 명령어가 이 경우에 정확히 부합합니다. tailscaled은 VPS에서 실행되어 localhost:3080 자체에 연결되므로, 하네스는 루프백(loopback) 상태를 유지하며 dsh의 설정은 전혀 변경할 필요가 없습니다.
tailscale serve --bg localhost:3080
tailscale serve status이제 공개 인터페이스에 포트를 열지 않고도 HTTPS를 통해 tailnet 내 기기 이름으로 UI에 접근할 수 있습니다. 이를 위해서는 tailnet에 대한 HTTPS 인증서가 활성화되어 있어야 하며, 그렇지 않으면 serve가 제시할 인증서가 없습니다. 다시 종료하려면 off와 함께 명령어를 반복하십시오.
tailscale serve --https=443 offserve을 사용하십시오. funnel은 절대 사용해서는 안 됩니다. Funnel은 동일한 대상을 공용 인터넷에 게시하므로, 인증되지 않은 에이전트 런타임이 열린 포트에 노출되는 상태로 돌아가게 됩니다. 두 명령어는 거의 동일해 보이지만 정반대의 작업을 수행하므로, 명령어를 입력하기 전에 Tailscale Serve와 Funnel의 차이점을 읽어보시기 바랍니다. 사설 네트워크로서의 Tailscale에서 설정 방법 전반을 다룹니다.
비밀번호를 확인하는 리버스 프록시를 통해 접근하기
이 방식은 실제로 포트를 인터넷에 공개하는 옵션이므로, 인증만이 외부인과 서버의 명령 실행 사이를 가로막는 유일한 방어선입니다. 여러 사람이 UI를 사용해야 하고 각자 터널을 생성하는 것이 비효율적일 때 이 방식을 선택하십시오.
harness는 127.0.0.1:3080에서 실행됩니다. nginx가 같은 서버에서 실행되므로 루프백 주소로 접근할 수 있으며, 인증서와 비밀번호 파일을 사용하여 443 포트에서 대기합니다.
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 Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 3600s;
proxy_buffering off;
}
}비밀번호 파일을 생성하고 설정을 다시 불러옵니다:
sudo apt install -y apache2-utils
sudo htpasswd -c /etc/nginx/dsh.htpasswd you
sudo nginx -t && sudo systemctl reload nginxnginx -t는 syntax is ok에 이어 test is successful을 출력해야 합니다. 파일에 오류가 있는 상태에서 설정을 다시 불러오면 실패하며 기존 설정이 유지되므로, 무작정 재시작하기보다 오류 메시지를 먼저 확인하십시오.
프록시 설정의 세 줄은 단순한 장식이 아닙니다. Upgrade 및 Connection 헤더는 WebSocket 핸드셰이크를 허용하며, 이 설정이 없으면 페이지는 로드되지만 업데이트되지 않습니다. proxy_read_timeout 3600s는 기본값인 60초를 대체합니다. 이 설정을 하지 않으면 긴 에이전트 작업이 중간에 끊겨 UI가 멈춘 것처럼 보일 수 있습니다. proxy_buffering off는 모델 출력이 완료될 때까지 기다리지 않고 도착하는 즉시 브라우저로 전송합니다. nginx 리버스 프록시 설정, 한 줄씩 알아보기에서 나머지 내용을 설명하며, nginx, Caddy, Traefik 중 선택하기에서는 자동 인증서를 사용하여 동일한 설정을 구성하는 방법을 다룹니다.
어떤 프록시를 선택하든 방화벽에서 3080 포트는 닫아두어, 인증된 경로를 통해서만 접근할 수 있도록 하십시오. ufw 방화벽 기초에서 관련 규칙을 다룹니다. TLS를 통한 기본 인증은 최소한의 보안 조치일 뿐 완벽한 보안 모델이 아닙니다. 비밀번호를 가진 사람은 누구나 서버의 셸을 획득할 수 있습니다. 가능하다면 터널 방식을 우선적으로 사용하십시오.
dsh 웹 서비스가 사용하는 포트는 어떻게 변경합니까?
--port은(는) 런처가 아닌 웹 애플리케이션에 속합니다. CLI 문서에서 직접 예시를 확인할 수 있습니다:
dsh --profile web --port 8080dsh web은(는) --profile web의 별칭이므로 dsh web --port 8080는 동일한 명령어입니다. 런처는 자신의 플래그만 해석하고 그 뒤에 오는 모든 내용을 부팅된 프로필로 전달합니다. 따라서 런처 플래그가 먼저 와야 하며, 런처가 인식하지 못하는 첫 번째 토큰부터 애플리케이션의 인자로 간주합니다. --port은(는) 반드시 프로필 뒤에 배치하십시오.
명령어 실행 시 출력되는 URL을 직접 확인하십시오. 해당 줄은 서버가 실제로 바인딩한 주소를 보고합니다. 그 후 터널의 마지막 필드를 해당 주소에 맞춰 업데이트하십시오:
ssh -N -L 3080:127.0.0.1:8080 you@your-vps영구적인 변경을 원한다면 명령줄 대신 프로필 설정에서 포트를 지정해야 합니다. web 및 headless 프로필은 처음 사용할 때 ~/.dsh 아래의 기본 템플릿에서 자동으로 초기화됩니다. 모든 계층이 결합된 후 실제로 적용된 설정을 확인하려면 다음 명령을 사용하십시오:
dsh --dump-config웹 서버 플러그인은 host와 port라는 두 가지 키를 제공합니다. port을(를) 0으로 설정하면 운영 체제에 사용 가능한 포트를 요청하게 되며, 이는 "0은 운영 체제가 할당한 포트를 요청함"으로 문서화되어 있습니다. 이 방식은 포트 충돌을 방지하지만, 재시작할 때마다 포트 번호가 변경되므로 터널 설정에는 적합하지 않습니다.
왜 dsh에서 address already in use 오류가 발생합니까?
다른 프로세스가 이미 해당 주소와 포트를 점유하고 있어 커널이 두 번째 bind 요청을 거부하기 때문입니다. 노드에서는 다음과 같이 보고합니다.
Error: listen EADDRINUSE: address already in use 127.0.0.1:3080설정을 변경하기 전에 점유 중인 프로세스를 찾으십시오.
sudo ss -ltnp | grep 3080users:(("node",pid=1042,fd=21)) 필드에는 프로세스 이름과 PID가 표시됩니다. 보통은 종료되었다고 생각한 이전 dsh가 여전히 분리된 tmux 창에서 실행 중인 경우가 많습니다. kill 1042 명령으로 해당 프로세스를 중지하거나, 새 인스턴스를 다른 포트에서 시작하십시오. 127.0.0.1:3080와 0.0.0.0:3080은 서로 충돌한다는 점에 유의하십시오. 모든 인터페이스에 바인딩하는 설정은 이미 루프백까지 포함하기 때문입니다.
버전을 고정하십시오. 개발자 프리미어 버전입니다
README는 이 점을 명확히 밝히고 있습니다. DeepSeek Harness는 현재 개발자 프리미어 단계로 빠르게 반복 개선되고 있으며, 호환성을 깨뜨리는 변경 사항이 발생할 것입니다.
npx @deepseek-ai/dsh web는 실행할 때마다 항상 최신 게시 버전을 불러옵니다. 일주일 동안 건드리지 않은 서버라도 다음 실행 시점에 다른 CLI가 시작되거나 플래그가 달라질 수 있습니다. 재시작이 곧 업그레이드가 되지 않도록 버전을 고정하십시오.
npx @deepseek-ai/dsh@0.1.0-rc.7 web2026년 8월 기준으로 게시된 패키지 버전은 0.1.0-rc.7입니다. 일반 npx 명령을 실행했을 때 어떤 버전이 설치되는지 먼저 확인하십시오.
npm view @deepseek-ai/dsh version프리미어 릴리스 과정에서 런처와 웹 애플리케이션 간에 플래그가 이동하기도 합니다. 만약 --port이 이 가이드의 설명대로 동작하지 않는다면, 추측하지 말고 애플리케이션에 직접 플래그 목록을 요청하십시오.
dsh --profile web --help설치 자체와 워크스페이스 설정, 모델 키에 관한 내용은 VPS에 DeepSeek Harness 설치하기를 참조하십시오. 접속 단계만 다루는 더 짧은 안내는 VPS에서 dsh 웹 UI 접속하기에서 추론 과정을 제외한 터널링 방법을 확인할 수 있습니다.
FAQ
왜 노트북 브라우저에서 http://127.0.0.1:3080에 접속할 수 없습니까?
127.0.0.1은 현재 사용 중인 기기를 의미하기 때문입니다. DeepSeek Harness Web UI는 VPS의 루프백 주소에 바인딩되어 있으므로, VPS 내부 프로세스만 연결할 수 있습니다. 노트북은 별도의 루프백을 가지고 있으며, 해당 기기의 3080 포트에서는 아무것도 수신 대기하지 않습니다. ssh -N -L 3080:127.0.0.1:3080 you@your-vps를 사용하여 SSH로 포트 포워딩을 수행한 다음, 로컬에서 http://127.0.0.1:3080에 접속하십시오. -L 인수의 중간 필드는 서버 측에서 해석되므로, 이를 통해 하네스(harness)로 연결할 수 있습니다.
공용 VPS에서 dsh Web UI를 0.0.0.0에 바인딩해도 안전합니까?
아니요, 안전하지 않습니다. Web UI는 dsh를 실행하는 사용자의 권한으로 셸 명령을 실행하고 파일을 수정하는 에이전트의 제어 인터페이스이며, 개발자 프리뷰 버전에는 로그인 화면이 전혀 없습니다. 공용 IP의 모든 인터페이스에 바인딩하면 3080 포트에 접근하는 누구나 서버에서 명령을 실행할 수 있게 됩니다. 바인딩은 127.0.0.1으로 유지하고, 방화벽에서 3080 포트를 닫은 뒤, SSH 터널, 사설 오버레이 네트워크 또는 비밀번호 인증이 필요한 리버스 프록시를 사용하십시오.
SSH 세션을 종료한 후에도 dsh Web UI가 계속 실행되게 하려면 어떻게 해야 합니까?
포그라운드에서 실행되는 npx @deepseek-ai/dsh web는 로그인 셸의 자식 프로세스이므로, 셸이 종료되면 함께 종료됩니다. tmux 세션 내에서 시작한 뒤 Ctrl-b d으로 분리하거나, lingering이 활성화된 systemd 사용자 서비스로 실행하십시오. 터널과 하네스는 독립적입니다. 하네스 자체가 로그인 세션보다 오래 지속되는 부모 프로세스를 가지고 있다면, SSH 터널은 하네스에 영향을 주지 않고 언제든지 끊거나 다시 연결할 수 있습니다.
nginx 뒤에서 긴 에이전트 작업을 수행할 때 왜 dsh Web UI가 중간에 멈춥니까?
nginx의 기본 proxy_read_timeout 설정이 60초이기 때문입니다. 1분 동안 데이터 전송이 없으면 연결을 끊어버리는데, 긴 에이전트 작업 단계에서는 이런 일이 쉽게 발생합니다. location 블록에 proxy_read_timeout 3600s;을 설정하십시오. proxy_buffering off;을 추가하여 출력이 발생하는 즉시 브라우저로 스트리밍되도록 하고, WebSocket 핸드셰이크가 성공하도록 proxy_http_version 1.1;을 사용하여 Upgrade 및 Connection 헤더를 전달하십시오. 이 헤더가 없으면 페이지는 로드되지만 업데이트를 전혀 받을 수 없습니다.