SSD Nodes Learn Hosting plans →
가이드 Matt Connor작성자 Matt Connor · 업데이트됨 2026-09-09

VPS에서 DSH 웹 UI 접속이 안 될 때 해결 방법

DSH 웹 UI가 127.0.0.1:3080에 바인딩되어 노트북에서 열리지 않는 문제를 해결합니다. 서버 보안을 유지하면서 안전하게 웹 UI에 접근할 수 있는 세 가지 방법과 절대 피해야 할 위험한 설정을 단계별로 안내합니다.

노트북에서 DSH 웹 UI가 열리지 않는 이유

DSH 웹 UI는 루프백 인터페이스에 바인딩되므로, 터미널에 출력된 URL은 해당 URL을 출력한 장비에서만 작동합니다. npx @deepseek-ai/dsh webhttp://127.0.0.1:3080을 보고하며, 127.0.0.1는 어떤 컴퓨터가 이 주소를 읽든 "이 주소를 읽고 있는 컴퓨터 자신"을 의미합니다. 노트북이 이 주소를 읽으면 자신의 루프백 인터페이스를 확인하게 되는데, 그곳에는 아무것도 리스닝 중인 서비스가 없습니다. 주소 자체가 혼란스럽다면 DSH가 왜 http://127.0.0.1:3080를 출력하는지에 대한 설명을 자세히 확인하십시오. DSH가 바인딩하는 대상을 변경하지 마십시오. 대신 노트북에서 VPS의 루프백 주소로 연결되는 인증된 경로를 구축하십시오.

루프백 기본값은 올바른 설정이며, 이 페이지의 모든 방법은 이를 유지합니다. 포트 3080 뒤에서 실행되는 것은 서버에서 셸 명령을 수행하는 에이전트이며, 웹 UI에는 별도의 로그인 페이지가 포함되어 있지 않습니다. 여기서 루프백은 DSH가 가진 유일한 접근 제어 수단입니다. 소켓에 도달하려면 이미 해당 장비에 대한 셸 접근 권한을 가지고 있어야 합니다.

DSH가 바인딩하는 대상과 확인 방법

2026년 8월 17일 확인된 DeepSeek Harness README에 따르면 npx @deepseek-ai/dsh web는 "기본적으로 http://127.0.0.1:3080에서 제공되는 웹 UI를 시작합니다". 같은 저장소의 CLI 참조 문서는 --port <num>을 기본값이 3080인 오버라이드 설정으로, --host <addr>를 "의도적으로 0.0.0.0을 거부하는" 오버라이드 설정으로 명시합니다. DSH는 개발자 프리뷰 버전이며, README는 호환성을 깨뜨리는 변경 사항이 발생할 것임을 대문자로 경고합니다. 이 페이지를 포함한 어떤 문서도 맹신하지 말고, 본인의 설치 환경에서 두 값을 직접 확인하십시오.

dsh --profile web --dump-config
ss -ltnp | grep 3080

--dump-config은 에이전트를 부팅하지 않고 구성된 설정 트리를 출력하므로, 모든 패치 레이어를 거쳐 최종적으로 적용된 호스트와 포트를 보여줍니다. ss는 현재 실제로 리스닝 중인 항목을 보여줍니다. 정상적인 상태의 출력 결과는 다음과 같습니다.

LISTEN 0  511  127.0.0.1:3080  0.0.0.0:*  users:(("node",pid=8123,fd=24))

콜론 앞의 주소만 확인하십시오. 127.0.0.1:3080은 루프백 전용이며, 이는 권장되는 설정입니다. 0.0.0.0:3080는 공용 인터페이스를 포함하여 서버의 모든 인터페이스를 의미합니다. ss를 실행했을 때 아무런 줄도 출력되지 않는다면 DSH가 실행 중이지 않은 것이며, 실행되기 전까지는 어떤 터널링도 도움이 되지 않습니다. 해당 단계까지 진행하는 방법은 VPS에서 DeepSeek Harness 설치 및 실행하기에서 다룹니다.

플래그는 1회 실행에만 적용됩니다. 값을 영구적으로 적용하려면 프로필 패치 레이어를 수정하십시오. dsh --profile <name>$DSH_HOME/profiles/<name>에 있는 프로필을 부팅하며, 레이어는 번들 패치, 프로필 자체의 cordis.patch.yml, 홈 디렉터리 수준의 $DSH_HOME/cordis.patch.yml, 그리고 --patch 오버레이 순서로 쌓입니다. 리스닝 설정은 @deepseek-ai/dsh-host-webserver 플러그인 하위의 hostport에 위치합니다. 웹 프로필은 첫 부팅 시 배포된 템플릿을 기반으로 스스로를 작성하며 프리뷰 버전에서는 해당 템플릿의 구조가 계속 변경되므로, 수정하기 전에 --dump-config를 사용하여 본인의 구성된 트리를 먼저 읽어보십시오.

두 번째 관문: /api 신뢰 울타리

3080 포트로 패킷을 전달하는 것은 문제의 절반에 불과합니다. DSH에는 두 번째 검사 단계가 존재하며, 이 단계에서 페이지 레이아웃은 정상적으로 로드되지만 이후 아무런 동작도 하지 않는 혼란스러운 오류가 발생합니다.

@deepseek-ai/dsh-client-connection 플러그인에는 trustedHosts 설정이 포함되어 있으며, 이에 대한 설명은 "루프백 외에 이 배포가 서비스하는 권한(Authorities): 정확한 host:port, 또는 포트 번호를 제외하고 모든 포트와 일치하는 host"로 정의되어 있습니다. 이 울타리는 Host 헤더가 루프백이 아니거나 목록에 명시되지 않은 모든 /api 요청을 거부합니다. 브라우저는 다음과 같이 거부 응답을 보고합니다.

transport failure for /api/host.describe: HTTP 403

따라서 DSH 앞에 배치하는 모든 프록시는 브라우저에 입력되는 도메인 이름을 선언해야 합니다. CLI에서는 --trusted-host <authority> 옵션을 사용하며, 이 옵션은 반복해서 지정할 수 있습니다.

dsh web --trusted-host dsh.example.com --trusted-host dsh.example.com:8443

이 울타리는 Host 헤더를 단순 문자열로 비교하며, 위 예시에서 두 가지 표기법이 모두 나타나는 이유가 바로 여기에 있습니다. dsh.example.comdsh.example.com:8443는 이 시스템에서 서로 다른 권한으로 간주되며, localhost:3080127.0.0.1:3080 또한 마찬가지입니다. 원인을 알 수 없는 403 오류는 대부분 URL 표시줄에 입력한 주소와 신뢰 목록 간의 철자 불일치로 인해 발생합니다.

이 울타리는 오리진(origin) 검사이며 인증이 아닙니다. 브라우저 자체의 정책에 따라 브라우저는 Host 헤더를 임의로 설정할 수 없으므로, 이 검사는 다른 사이트의 페이지가 사용자의 에이전트를 조작하는 것을 방지합니다. 브라우저가 아닌 클라이언트는 해당 헤더를 자유롭게 작성할 수 있습니다. trustedHosts은 프록시 호환성을 위한 설정으로 간주해야 하며, 보안 제어 수단으로 사용해서는 안 됩니다.

옵션 1: SSH 로컬 포워딩

서버에 아무것도 추가하지 않으므로 가장 먼저 고려해야 할 방법입니다.

ssh -N -L 127.0.0.1:3080:127.0.0.1:3080 you@your-vps

-L는 노트북에서 리스너를 열고 각 연결을 VPS에서 해석된 127.0.0.1:3080으로 전달합니다. -N은 "원격 명령을 실행하지 않음"을 의미하므로, 세션은 포워딩 기능만 수행합니다. 로컬 측을 단순히 3080이 아닌 127.0.0.1:3080로 작성한 것은 의도적입니다. 이는 노트북 측 리스너를 루프백으로 고정하여, SSH 클라이언트 설정의 GatewayPorts 옵션이 에이전트를 현재 연결된 네트워크로 의도치 않게 노출하는 것을 방지합니다.

이제 브라우저에서 http://localhost:3080를 엽니다. 여기서는 두 가지 기능이 자동으로 작동합니다. Host 헤더는 루프백 권한으로 간주되므로, /api 보안 정책을 별도 설정 없이 통과합니다. 또한 브라우저는 http://localhost을 안전한 컨텍스트로 취급합니다. 이는 DSH 웹 앱이 부팅 중에 crypto.randomUUID()를 호출하는데, 브라우저가 해당 기능을 HTTPS 또는 루프백 오리진에서만 허용하기 때문에 중요합니다.

-f을 추가하여 포워딩이 설정되면 ssh이 자동으로 백그라운드로 전환되도록 합니다.

ssh -f -N -L 127.0.0.1:3080:127.0.0.1:3080 you@your-vps

이 방법의 솔직한 장단점입니다. 공용 주소에서 대기하는 새로운 서비스가 없고 방화벽 규칙 변경도 필요 없으므로, 노출 위험을 최소화할 수 있습니다. 전체 접근 제어는 SSH 설정에 의존하므로, 비밀번호 로그인을 비활성화한 키 기반 SSH는 선택 사항이 아닌 필수 전제 조건입니다. 단점은 터널이 단일 클라이언트 세션에 종속된다는 점입니다. 노트북이 절전 모드로 들어가면 터널이 끊기며, 수동으로 다시 시작해야 하고, 모바일 기기에서는 사용할 수 없습니다.

옵션 2: VPS에서 Tailscale serve 사용하기

Tailscale은 사용자의 기기들 사이에 사설 네트워크를 구축합니다. VPS에 이를 설치하면 해당 장비에 사용자의 기기만 접근할 수 있는 100.x.y.z 주소가 할당됩니다.

네트워크에 참여하는 것만으로는 부족하며, 많은 사용자가 이 지점에서 어려움을 겪습니다. DSH는 루프백(loopback) 주소에서 대기 중이므로 100.x.y.z 주소에서는 수신하지 않습니다. http://100.x.y.z:3080로 접속을 시도하면 해당 주소에 바인딩된 소켓이 없기 때문에 연결이 거부됩니다.

tailscale serve는 이 둘을 연결하는 핵심 요소입니다. VPS에서 실행되어 사설 네트워크로부터 트래픽을 받아 로컬 주소로 프록시합니다.

curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
sudo tailscale serve --bg --https=443 localhost:3080
tailscale serve status

tailscale serve statushttps://<machine>.<tailnet>.ts.net/ 형식의 URL을 출력합니다. 해당 이름을 신뢰하도록 설정하고 DSH를 시작하십시오. 그렇지 않으면 앞서 설명한 대로 /api에서 403 오류가 발생합니다.

dsh web --trusted-host your-vps.your-tailnet.ts.net

이 방식은 보안 컨텍스트 문제도 해결합니다. Tailscale은 해당 이름으로 발급된 인증서를 사용하여 실제 HTTPS를 종료하므로 crypto.randomUUID()를 사용할 수 있고, 스마트폰 브라우저에서도 UI가 정상적으로 로드됩니다. serve은 사설 네트워크 내부로만 공개되므로 외부 인터넷으로는 트래픽이 노출되지 않습니다.

여기서 tailscale funnel을 대신 사용하지 마십시오. 이는 공개 인터넷을 대상으로 하는 동일한 명령어 계열이며, 누구나 해석할 수 있는 호스트네임에 셸 접근 권한이 있는 에이전트를 노출하게 됩니다. 명령어를 입력하기 전에 serve가 트래픽을 tailnet 내부에 유지하는 방식과 funnel이 이를 공개하는 방식의 차이를 먼저 읽어보시기 바랍니다. 스마트폰 환경에서의 전체 설정 방법은 스마트폰에서 자가 호스팅 에이전트에 접근하기를 참조하십시오.

이 방식의 단점은 의존성입니다. UI가 필요한 모든 기기가 네트워크에 참여해야 하며, 네트워크 참여 여부는 사용자가 직접 운영하지 않는 조정 서버(coordination server)가 결정합니다. 이러한 방식이 수용 불가능하다면, 자가 호스팅 Headscale 제어 서버를 통해 직접 소유한 하드웨어에서 동일한 프로토콜을 사용할 수 있습니다.

옵션 3: 인증된 TLS 리버스 프록시

브라우저가 사설 네트워크에 접속할 수 없는 경우(예: 관리 권한이 없는 장비)에 이 방식을 사용합니다. 이제 호스트 이름을 인터넷에 공개하는 것이므로, 인증은 실제적이어야 하며 프록시 단에서 이루어져야 합니다. DSH는 자체적인 인증 기능을 제공하지 않습니다.

TLS는 https://의 기반이 되는 암호화 기술인 전송 계층 보안을 의미합니다. nginx를 루프백 포트로 지정하고 그 앞에 비밀번호를 설정하십시오. map 블록은 http 컨텍스트에 속하므로, /etc/nginx/conf.d/ 아래에 별도의 파일로 생성하십시오.

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

그다음 사이트 설정입니다.

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;
    }
}

비밀번호 파일을 생성하고, 테스트한 뒤 리로드하십시오.

sudo apt install -y apache2-utils
sudo htpasswd -c /etc/nginx/dsh.htpasswd you
sudo nginx -t && sudo systemctl reload nginx

nginx -tsyntax is oktest is successful을 출력해야 합니다. 오류가 출력되면 nginx는 기존 설정을 유지하므로 아직 서비스에 문제가 생기지는 않습니다. 그다음 공용 이름을 신뢰하도록 설정하여 DSH를 시작하십시오. proxy_set_header Host $hostdsh.example.com을 전달하며, 그렇지 않으면 펜스(fence)가 이를 거부하기 때문입니다.

dsh web --trusted-host dsh.example.com

여기서 두 가지 지시어는 매우 중요합니다. UpgradeConnection 헤더는 UI의 장기 연결을 유지합니다. 이 헤더가 없으면 nginx가 연결을 끊어버려 인터페이스가 오래된 상태를 표시하며 반복적으로 재연결을 시도하게 됩니다. proxy_read_timeout 3600s은 기본값인 60초보다 긴 시간 동안 에이전트가 작업을 수행할 때 nginx가 연결을 차단하지 않도록 합니다. 나머지 설정에 대한 근거는 nginx 리버스 프록시 설정 지시어별 상세 설명에서 확인할 수 있으며, 프록시 선택에 관한 내용은 nginx와 Caddy 및 Traefik 비교를 참조하십시오.

기본 인증(basic auth)이 제공하는 보안 수준을 명확히 이해해야 합니다. 이는 무작위 스캐너의 접근을 차단하며, 포트를 완전히 개방하는 것보다는 훨씬 안전합니다. 하지만 이는 셸 앞에 하나의 공유 비밀번호를 두는 방식일 뿐이며, 2단계 인증이 없고 특정 사용자의 권한을 회수할 방법도 없습니다. 비밀번호를 알아낸 사람은 누구나 DSH가 실행 중인 사용자의 권한으로 명령을 실행할 수 있습니다. 여러 사람이 접근해야 한다면 대신 실제 신원 확인이 가능한 프록시를 앞에 두어야 하며, 범위를 더 넓히기 전에 VPS에서 코딩 에이전트를 실행하기 위한 안전 수칙을 읽어보시기 바랍니다.

0.0.0.0에 바인딩하고 포트를 개방하면 발생하는 일

가장 쉬운 해결책은 바인딩을 변경하고 방화벽을 여는 것입니다. 하지만 DSH는 전자를 차단합니다. --host <addr>는 의도적으로 0.0.0.0를 거부하며, @deepseek-ai/dsh-host-webserver 플러그인은 host 설정값이 127.0.0.1 또는 0.0.0.0만 허용한다고 명시합니다. 즉, 지원되는 값은 하나뿐이며 다른 하나는 CLI에서 거부됩니다. 이 검사 로직을 우회하는 커뮤니티 플러그인이 존재하지만, 해당 플러그인들은 경고와 함께 제공되며 그 경고는 정확합니다. 동일한 플러그인 계층은 오히려 사용 한도 및 도구 권한 규칙을 통해 에이전트가 수행할 수 있는 작업을 제한하는 방향으로 사용하는 것이 훨씬 유용합니다.

강제로 설정을 변경했을 때 얻게 되는 결과는 다음과 같습니다. 포트 3080이 공인 IP 주소에서 응답하게 됩니다. 로그인 페이지는 존재하지 않습니다. /api 펜스는 브라우저가 아닌 클라이언트가 스스로 작성할 수 있는 Host 헤더를 검사할 뿐이므로, trustedHosts에 주소를 기재하는 것은 curl을 가진 공격자에게 아무런 방어 효과가 없습니다. 결과적으로 사용자는 자신의 계정 권한으로 셸 명령을 실행하는 에이전트를 외부에 노출하게 되며, DSH가 프로필과 API 키를 저장하는 $DSH_HOME 경로의 자격 증명까지 함께 노출됩니다. 이는 서버에 대한 원격 코드 실행(RCE)을 허용하는 것이며, 그 비용은 고스란히 사용자의 청구서로 돌아옵니다. 스캐너는 전체 주소 공간을 지속적으로 탐색하므로, 주소를 공개하지 않는 것만으로는 통제 수단이 될 수 없습니다. 에이전트가 읽을 수 있는 모든 비밀 정보는 셸을 통해 외부로 유출됩니다.

위에 언급된 모든 방법은 사용자가 이러한 위험한 설정을 하지 않도록 하기 위해 존재합니다. 한 명의 사용자가 노트북 한 대를 사용할 때는 SSH 포워딩이 올바른 기본값입니다. 모바일 기기를 사용하거나 정식 인증서가 필요한 경우에는 tailscale serve로 전환하십시오. 관리하지 않는 브라우저에서 UI에 접근해야 하는 경우에는 반드시 그 앞에 적절한 인증 체계를 갖춘 공개 리버스 프록시를 사용해야 합니다.

FAQ

왜 제 노트북에서 http://127.0.0.1:3080가 열리지 않습니까?

127.0.0.1은 "이 주소를 읽는 기기"를 의미하기 때문입니다. DSH는 VPS에서 이 주소를 출력하며, 그곳에서는 올바른 주소입니다. 노트북에서 같은 문자열을 읽으면 자신의 루프백 인터페이스를 바라보게 되는데, 그곳에는 아무것도 실행 중이지 않습니다. VPS에서 ss -ltnp | grep 3080 명령어로 서버 상태를 확인하십시오. 127.0.0.1:3080이 포함된 줄이 보인다면 DSH가 의도적으로 루프백에 바인딩되어 실행 중이라는 뜻입니다. 필요한 것은 바인딩 주소 변경이 아니라 터널이나 프록시입니다.

dsh web을 --host 0.0.0.0으로 실행할 수 있습니까?

아니요. 2026년 8월 17일에 확인된 저장소 CLI 참조 문서에 따르면, --host <addr>0.0.0.0을 의도적으로 거부하는 재정의 옵션입니다. DSH는 셸 명령을 실행하지만 로그인 페이지를 제공하지 않으므로, 모든 인터페이스에 바인딩하면 공인 IP 주소에 인증되지 않은 셸을 노출하는 결과가 됩니다. 커뮤니티 패치를 적용하면 이 검사를 제거할 수 있습니다. 패치를 적용할 경우, DSH가 제공하지 않는 방화벽 설정과 인증 책임은 사용자에게 있습니다.

리버스 프록시 뒤에서 모든 /api 호출이 HTTP 403을 반환하는 이유는 무엇입니까?

/api 신뢰 장벽은 Host 헤더가 루프백 주소가 아니거나 trustedHosts에 나열되지 않은 모든 요청을 거부합니다. 프록시 뒤에서는 해당 헤더에 공인 도메인 이름이 담기므로 장벽이 이를 거부하고, 브라우저 로그에는 transport failure for /api/host.describe: HTTP 403가 기록됩니다. --trusted-host <your name> 옵션으로 DSH를 시작하십시오. 비교는 문자열을 그대로 대조하므로, URL에 포트 번호가 포함되어 있다면 포트 번호까지 정확히 일치시켜야 합니다.

일반 HTTP 환경에서 DSH UI가 로드되지만 시작이 완료되지 않는 이유는 무엇입니까?

웹 앱은 부팅 중에 crypto.randomUUID()을 호출하는데, 브라우저는 HTTPS와 같은 보안 컨텍스트나 http://localhost 같은 루프백 출처에서만 이 기능을 노출합니다. 일반 IP 주소에서 일반 HTTP로 제공하면 해당 함수가 정의되지 않아 이를 사용하는 호출에서 오류가 발생하고 인터페이스가 나타나지 않습니다. tailscale serve을 사용하거나 TLS 리버스 프록시를 통해 HTTPS로 제공하면 문제가 해결되며, http://localhost로 SSH 포워딩을 통해 접속해도 해결됩니다.

1인 개발자는 어떤 방법을 사용해야 합니까?

SSH 로컬 포워딩을 사용하십시오. 서버에 아무것도 추가하지 않고, 방화벽 규칙을 변경할 필요도 없으며, 이미 신뢰하는 SSH 키를 재사용할 수 있습니다. ssh -f -N -L 127.0.0.1:3080:127.0.0.1:3080 you@your-vps을 실행한 뒤 http://localhost:3080을 여십시오. 휴대폰에서 UI를 확인하고 싶거나 노트북이 절전 모드로 들어가도 접속을 유지해야 한다면 tailscale serve로 전환하십시오.