dsh web http://127.0.0.1:3080 연결 안 되는 이유
dsh 실행 시 출력되는 http://127.0.0.1:3080은 루프백 주소로 외부 접속이 차단됩니다. 웹 UI를 안전하게 사용하려면 SSH 터널링을 설정해야 합니다. 포트 3080을 직접 외부에 노출하는 것이 왜 위험한지, 권한 탈취 위험성과 함께 올바른 접속 방법을 설명합니다.
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 키는 "수신 호스트; 지원되는 두 가지 값은 loopback과 all-interfaces입니다"라고 설명되어 있습니다. 설정을 변경하지 않으면 기본적으로 loopback이 적용됩니다. 포트 개념이 생소하다면 Linux에서 포트가 작동하는 방식을 참조하십시오. 이 문서에서는 모든 통신의 기반이 되는 주소와 포트 모델을 다룹니다.
Web UI가 localhost에만 바인딩되는 이유
dsh은 에이전트 하네스로, 모델을 감싸고 있는 프로그램입니다. 이 프로그램은 루프, 도구 호출, 그리고 해당 호출이 실행되는 권한을 직접 관리합니다. 브라우저 탭은 사용자가 선택한 작업 디렉터리에서 셸 명령을 실행하고 파일을 읽고 쓰며, 모델 API 키를 사용하는 프로세스의 제어 인터페이스입니다. 해당 페이지에 접근할 수 있는 사람은 누구나 dsh를 실행 중인 사용자의 권한으로 이 모든 작업을 수행할 수 있습니다. 네트워크만이 유일한 접근 경로는 아닙니다. 설치하는 플러그인 역시 동일한 프로세스 내에서 동일한 권한으로 실행되므로, dsh 플러그인을 설치하기 전에 검증하는 것은 서버가 어떤 포트에서 대기할지 결정하는 것만큼이나 신중해야 합니다.
따라서 3080 포트는 읽기 전용 대시보드가 아닙니다. 해당 페이지를 로드하는 것은 서버에서 명령을 실행할 수 있는 권한을 얻는 것과 같습니다.
Web UI를 열면 즉시 세션 목록으로 이동합니다. 개발자 프리뷰 버전에는 사용자 계정이나 원격 인증 기능이 포함되어 있지 않으므로 로그인 프롬프트가 없습니다. 루프백(loopback) 환경에서는 운영체제가 접근 제어 역할을 수행하며 로컬 프로세스만 접근할 수 있으므로 일관성이 유지됩니다. 하지만 공인 IP가 있는 VPS에서 동일한 서버를 0.0.0.0에 바인딩하면, 아무런 보호 장치 없이 전 세계 인터넷을 향해 해당 페이지가 노출됩니다. 자동화된 스캐너가 비표준 포트를 지속적으로 탐색하므로, 3080 포트를 공개하는 즉시 발견된다고 간주해야 합니다.
방화벽에서 3080 포트를 개방하지 마십시오. 또한 공인 VPS에서 웹 서버host을0.0.0.0로 설정하지 마십시오. 이 조합은 서버에 먼저 접속하는 누구에게나 명령 실행 권한을 넘겨주는 것과 같습니다.
동일한 논리가 서버에서 실행하는 모든 에이전트 런타임에 적용됩니다. 이것이 바로 VPS에서 코딩 에이전트를 안전하게 실행하는 방법이 동일한 규칙에서 시작되는 이유입니다. 에이전트의 제어 포트는 비공개로 유지해야 하며, 신뢰할 수 있는 수단을 통해서만 접근해야 합니다.
노트북에서 dsh 웹 UI를 어떻게 여나요?
세 가지 정석적인 방법이 있으며, 각 방법 모두 하네스를 루프백(loopback)에 바인딩된 상태로 유지합니다.
- SSH 터널을 사용합니다. 공개 인터페이스에서 새로 수신 대기하는 포트가 없으며, 이미 자격 증명을 보유하고 있습니다. 이 방법을 권장합니다.
- 사설 오버레이 네트워크를 구성합니다. 그러면 UI는 본인의 기기에서만 접근할 수 있고 외부에는 보이지 않게 됩니다.
- TLS(전송 계층 보안)를 종료하고 비밀번호를 요구한 뒤 요청을 전달하는 리버스 프록시를 사용합니다.
이 방법들의 차이점은 브라우저를 루프백으로 연결하는 방식에 있습니다. 어떤 경우에도 하네스를 루프백에서 이동시켜서는 안 됩니다.
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 자체에 연결되므로, 하네스는 루프백 인터페이스에 유지되며 dsh의 설정은 전혀 변경할 필요가 없습니다.
tailscale serve --bg localhost:3080
tailscale serve status이제 공개 인터페이스에 포트를 열지 않고도 HTTPS를 통해 tailnet 내 기기 이름으로 UI에 접근할 수 있습니다. 이를 위해서는 tailnet에 대한 HTTPS 인증서가 활성화되어 있어야 하며, 그렇지 않으면 serve가 제시할 인증서가 없게 됩니다. 다시 설정을 해제하려면 off 옵션을 사용하여 명령어를 반복하십시오.
tailscale serve --https=443 off반드시 serve을 사용하고, 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 아래의 기본 템플릿에서 자동으로 초기화됩니다. 이 디렉터리는 API 키와 모델 엔드포인트 설정이 저장되는 곳이기도 하므로, 파일을 수정하는 김에 dsh 키, 모델 및 엔드포인트 설정을 함께 읽어보는 것이 좋습니다. 모든 계층이 구성된 후 실제로 적용된 설정을 확인하려면 다음을 실행하십시오.
dsh --dump-config웹 서버 플러그인은 host와 port라는 두 가지 키만 노출합니다. port을(를) 0(으)로 설정하면 운영 체제에 사용 가능한 포트를 요청하게 되며, 이는 "0은 OS가 할당한 포트를 요청함"으로 문서화되어 있습니다. 이 방식은 포트 충돌을 방지하지만, 재시작할 때마다 번호가 바뀌므로 터널 설정에는 적합하지 않습니다.
왜 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가 개발자 프리뷰 단계이며 빠르게 반복 개선되고 있고, 호환성을 깨뜨리는 변경 사항이 발생할 것임을 명확히 밝히고 있습니다. 이러한 속도 때문에 망설여진다면, dsh와 Claude Code 및 Omnigent 비교 문서를 통해 동일한 곡선상의 서로 다른 지점에 있는 두 가지 하네스와 비교해 보십시오.
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버전 고정 후 설치가 거부되거나, 새로운 버전을 고정했음에도 npx이 계속 이전 빌드로 시작된다면 설치 및 버전 오류 해결 문서를 참조하여 npx 캐시를 삭제하고 Node가 어떤 npm을 사용하는지 확인하십시오.
프리뷰 릴리스 과정에서 플래그가 런처와 웹 애플리케이션 사이를 이동할 수 있습니다. --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 헤더를 전달하십시오. 이 헤더들이 없으면 페이지는 로드되지만 업데이트를 전혀 받을 수 없습니다.