Cómo acceder a la interfaz web de DSH en tu VPS
DSH escucha en 127.0.0.1:3080 y la URL no funciona desde el portátil. Compara 3 formas seguras de acceder y el método que debes evitar.
Por qué la interfaz web de DSH no se abre desde el portátil
La interfaz web de DSH se enlaza a la interfaz de loopback, por lo que la URL que mostró el terminal sólo funciona en la máquina que la mostró. npx @deepseek-ai/dsh web informa de una URL de inicio con el formato http://127.0.0.1:3080/?token=..., y 127.0.0.1 significa «el equipo que está leyendo esta dirección» para cualquier equipo que la lea. El portátil la lee, consulta su propia interfaz de loopback y no encuentra ningún proceso escuchando allí. Si la dirección es la parte confusa, por qué dsh muestra http://127.0.0.1:3080 inicialmente lo explica con más detalle. No cambie la interfaz a la que se enlaza DSH. Cree en su lugar una ruta autenticada desde el portátil hasta la dirección de loopback de la VPS.
El valor predeterminado de loopback es correcto y todos los métodos de esta página lo mantienen. Detrás del puerto 3080 hay un agente que ejecuta comandos de shell en el servidor, y la interfaz web no incluye una página de inicio de sesión. DSH autentica ahora el navegador mediante un token de inicio: cada inicio de proceso genera un token nuevo, la URL mostrada lo incluye y la primera visita lo intercambia por una cookie de sesión. Ese token es una credencial de tipo bearer incluida en una URL, por lo que loopback sigue siendo la capa más importante: para llegar al socket, ya debe tener una shell en el equipo.
A qué dirección se vincula DSH y cómo comprobar la configuración
Comprobado el 20 de octubre de 2026, el README de DeepSeek Harness indica que npx @deepseek-ai/dsh web «inicia la interfaz web en http://127.0.0.1:3080 de forma predeterminada». La referencia de CLI del mismo repositorio indica que el puerto predeterminado sigue siendo 3080, que --host «debe especificar una dirección local concreta, no un comodín» y que --host 0.0.0.0 termina con un error de uso. DSH es una versión preliminar para desarrolladores, y el README advierte en mayúsculas de que habrá cambios incompatibles. Confirme ambos valores en su propia instalación en lugar de confiar en cualquier página, incluida esta.
dsh --profile web --dump-config
ss -ltnp | grep 3080--dump-config muestra el árbol de configuración compuesto sin iniciar el agente, por lo que muestra el host y el puerto que prevalecen después de aplicar todas las capas de parches. ss muestra qué está escuchando realmente en este momento. Una línea correcta tiene este aspecto:
LISTEN 0 511 127.0.0.1:3080 0.0.0.0:* users:(("node",pid=8123,fd=24))Lea la dirección que aparece antes de los dos puntos y nada más. 127.0.0.1:3080 solo permite conexiones de loopback, que es lo que necesita. Cualquier otra dirección indica que DSH está sirviendo en la red a la que pertenece esa dirección, que puede ser la red pública. Si ss no muestra ninguna línea, DSH no está en ejecución y ningún túnel ayudará hasta que lo inicie. Este proceso se explica en instalar y ejecutar DeepSeek Harness en un VPS.
Las flags se aplican a una sola ejecución. Para conservar un valor, edite la capa de parches del perfil. dsh --profile <name> inicia el perfil en $DSH_HOME/profiles/<name>, y las capas se apilan en este orden: parches del bundle, el propio cordis.patch.yml del perfil, el $DSH_HOME/cordis.patch.yml del nivel de usuario y, después, cualquier superposición --patch. Los ajustes de escucha se encuentran en el plugin @deepseek-ai/dsh-host-webserver como host y port. Lea su propio árbol compuesto con --dump-config antes de editarlo, porque el perfil web se genera a partir de una plantilla incluida en el primer arranque y la versión preliminar cambia continuamente la estructura de esa plantilla.
La línea de inicio también indica de dónde procede la credencial. DSH muestra la URL raíz con el token del proceso en una consulta token, y solo GET /?token=... la acepta: no una ruta de API ni una cabecera Authorization. Esa petición establece una cookie firmada, exclusiva para el host y HttpOnly, SameSite=Strict, y redirige a una URL limpia. Cada inicio del proceso genera un token nuevo, y la cookie caduca después de 30 días de forma predeterminada; este valor lo establece cookieMaxAgeDays. Una petición sin credenciales válidas recibe un 401. Copie la URL completa, incluido el token, porque todos los métodos siguientes terminan abriéndola.
La segunda barrera: el límite de confianza de /api
Conseguir que los paquetes lleguen al puerto 3080 resuelve la mitad del problema. DSH realiza una segunda comprobación. Esto produce el fallo confuso en el que la página carga, aparece el diseño y después nada funciona.
El complemento @deepseek-ai/dsh-client-connection contiene una configuración trustedHosts. Su README define la regla: la cabecera Host debe ser loopback, la propia dirección de enlace del listener en cualquier puerto o coincidir con una entrada trustedHosts. Cuando el navegador envía una cabecera Origin, esta debe indicar la misma autoridad. La barrera se ejecuta antes de la autenticación en todas las solicitudes y rechaza cualquier solicitud /api que no la supere. El navegador informa del rechazo así:
transport failure for /api/host.describe: HTTP 403Por tanto, cualquier proxy que coloque delante de DSH debe declarar el nombre que el navegador escribe. La CLI acepta --trusted-host <authority> y la opción se puede repetir:
dsh web --trusted-host dsh.example.comLa barrera compara autoridades, es decir, host y puerto, y normaliza los puertos predeterminados según el esquema del listener. Una entrada sin puerto, como dsh.example.com, coincide con ese nombre en cualquier puerto, por lo que también cubre dsh.example.com:8443. Una entrada con puerto, como dsh.example.com:8443, coincide únicamente con ese host y ese puerto. Loopback no necesita ninguna entrada, así que localhost:3080 y 127.0.0.1:3080 pasan ambas. Un 403 cuya causa no puede explicar casi siempre se debe a una diferencia entre el nombre de la barra de direcciones y los nombres de la lista de confianza. Un 401 es otro problema: la barrera le permitió continuar, pero el navegador todavía no tiene una cookie de sesión.
La barrera comprueba el origen. No realiza la autenticación. El propio navegador impide que una página de otro sitio establezca una cabecera Host, por lo que esta comprobación impide que esa página controle su agente en determinados casos. Cualquier cliente que no sea un navegador puede escribir esa cabecera libremente. Trate trustedHosts como una configuración de compatibilidad para proxies, nunca como un control de seguridad.
Opción 1: un reenvío local de SSH
Use esta opción primero, porque no añade nada al servidor.
ssh -N -L 127.0.0.1:3080:127.0.0.1:3080 you@your-vps-L abre un listener en su portátil y reenvía cada conexión a 127.0.0.1:3080 tal como se resuelve en el VPS. -N significa «no ejecutar ningún comando remoto», por lo que la sesión transporta el reenvío y nada más. Escribir el lado local como 127.0.0.1:3080 en lugar de un simple 3080 es intencionado: fija el listener del lado del portátil en loopback, de modo que un ajuste GatewayPorts en la configuración del cliente SSH no pueda volver a publicar el agente en la red a la que está conectado.
Abra ahora en el navegador del portátil la URL de lanzamiento que DSH mostró, incluido el token. Como el reenvío escucha en 127.0.0.1:3080, el http://127.0.0.1:3080/?token=... mostrado funciona sin cambios. El http://localhost:3080 sin más no basta en una sesión nueva del navegador: sin la cookie, DSH responde con un 401. Después de la primera visita, la cookie permite el acceso, pero está limitada al host. Siga usando el nombre que abrió primero, porque una cookie establecida para 127.0.0.1 no se envía a localhost. Aquí hay dos ventajas inmediatas. La cabecera Host indica una autoridad de loopback, por lo que el control /api se supera sin configuración adicional. Además, los navegadores consideran seguros los orígenes de loopback como http://127.0.0.1 y http://localhost. Esto es importante porque la aplicación web de DSH llama a crypto.randomUUID() durante el arranque, y los navegadores sólo exponen esa función mediante HTTPS o en un origen de loopback.
Añada -f para que ssh pase a ejecutarse en segundo plano cuando el reenvío esté activo:
ssh -f -N -L 127.0.0.1:3080:127.0.0.1:3080 you@your-vpsLa contrapartida real. No se abre ningún listener en una dirección pública y no cambia ninguna regla del firewall, por lo que este método aumenta la exposición lo mínimo posible. Todo el control de acceso depende de la configuración de SSH. Por eso SSH con autenticación exclusiva mediante clave y el acceso mediante contraseña deshabilitado es un requisito, no un complemento opcional. El coste es que el túnel pertenece a una única sesión del cliente. Se cierra cuando el portátil entra en suspensión, debe reiniciarlo manualmente y no le permite acceder desde un teléfono.
Opción 2: Tailscale serve en el VPS
Tailscale crea una red privada entre tus propios dispositivos. Al instalarlo en el VPS, esa máquina obtiene una dirección 100.x.y.z a la que sólo pueden acceder los dispositivos de tu tailnet, según las políticas de control de acceso. Compartir la máquina extiende ese acceso a los dispositivos de un usuario externo.
Unirse a esa red no es suficiente. Aquí es donde suelen producirse los problemas. DSH no escucha en la dirección 100.x.y.z porque escucha en loopback. Un navegador que acceda a http://100.x.y.z:3080 recibe un rechazo de conexión porque no hay ningún socket asociado a esa dirección. --host sí acepta esa dirección concreta, pero un bind que no sea de loopback sirve HTTP sin cifrar si no configuras TLS. Además, DSH no muestra ninguna advertencia, lo que vuelve a plantear el problema del contexto seguro descrito en la Opción 1.
tailscale serve es la pieza que conecta ambos elementos. Se ejecuta en el VPS, acepta tráfico de tu red privada y lo envía mediante proxy a una dirección 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 muestra la URL con el formato https://<machine>.<tailnet>.ts.net/. Inicia DSH con ese nombre como confiable. De lo contrario, /api devuelve 403, exactamente como se describió arriba:
dsh web --public-url https://your-vps.your-tailnet.ts.net/ --trusted-host your-vps.your-tailnet.ts.net--public-url cambia únicamente lo que muestra DSH. Por tanto, la URL de inicio utiliza el nombre de la tailnet y se abre directamente en tu teléfono. Por sí solo, esto no concede ningún acceso, por lo que --trusted-host sigue siendo necesario. La URL mostrada contiene una credencial activa. Pégala sólo en tus propios dispositivos.
Esto también elimina el problema del contexto seguro. Tailscale termina HTTPS real con un certificado emitido para ese nombre, por lo que crypto.randomUUID() está disponible y la interfaz se inicia en el navegador del teléfono. Nada llega a Internet pública porque serve publica únicamente dentro de tu red privada.
No sustituyas tailscale funnel aquí. Pertenece a la misma familia de comandos, pero está orientado a Internet pública. Usarlo pondría un agente con acceso al shell en un nombre de host que cualquiera puede resolver. Lee cómo serve mantiene el tráfico dentro de tu tailnet mientras funnel lo publica antes de usar cualquiera de los dos. Para la versión de toda esta configuración adaptada al teléfono, consulta cómo acceder desde el teléfono a un agente autoalojado.
La contrapartida es una dependencia. Todos los dispositivos que necesiten la interfaz deben unirse a la red, y un servidor de coordinación que no administras tú decide quién forma parte de ella. Si esto no es aceptable, un servidor de control Headscale autoalojado utiliza el mismo protocolo en hardware que controlas.
Opción 3: un reverse proxy TLS con autenticación
Use esta opción cuando el navegador no pueda unirse a una red privada, por ejemplo, desde una máquina que usted no administra. Ahora publicará un nombre de host en Internet, por lo que la autenticación debe ser real y debe proceder del proxy. El token de lanzamiento de DSH es una credencial de tipo bearer incluida en una URL y compartida por todas las personas que la tengan, por lo que no cuenta como esa capa.
TLS significa seguridad de la capa de transporte, el cifrado que protege https://. Apunte nginx al puerto de loopback y coloque una contraseña delante. El bloque map pertenece al contexto http, así que colóquelo en un archivo independiente bajo /etc/nginx/conf.d/:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}A continuación, el sitio:
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_cookie_flags ~ secure;
proxy_read_timeout 3600s;
proxy_buffering off;
}
}Cree el archivo de contraseñas, compruebe la configuración y vuelva a cargarla:
sudo apt install -y apache2-utils
sudo htpasswd -c /etc/nginx/dsh.htpasswd you
sudo nginx -t && sudo systemctl reload nginxnginx -t debería mostrar syntax is ok y test is successful. Si muestra un error, nginx seguirá sirviendo la configuración anterior, por lo que todavía no se ha interrumpido nada. Después, inicie DSH con el nombre público como nombre de confianza, porque proxy_set_header Host $host reenvía dsh.example.com y el fence lo rechaza de lo contrario. --public-url hace que la URL de lanzamiento mostrada use ese nombre, para que pueda abrirla directamente:
dsh web --public-url https://dsh.example.com/ --trusted-host dsh.example.comTres directivas son esenciales. Las cabeceras Upgrade y Connection mantienen abierta la conexión de larga duración de la interfaz. Sin ellas, nginx la cierra y la interfaz se reconecta en un bucle mientras muestra un estado obsoleto. proxy_read_timeout 3600s impide que nginx interrumpa una ejecución del agente que tarde más de los 60 segundos predeterminados. proxy_cookie_flags ~ secure marca la cookie de sesión de DSH Secure para la conexión HTTPS. La guía de despliegue público de DSH pide que el proxy lo haga, porque un backend HTTP simple emite la cookie sin esa marca. DSH también puede servir HTTPS directamente con --tls-cert y --tls-key, pero nunca obtiene ni renueva certificados y no añade ninguna contraseña, por lo que aquí no sustituye al proxy. El razonamiento del resto se explica en una configuración de reverse proxy de nginx explicada directiva por directiva, y la elección del proxy se analiza en nginx comparado con Caddy y Traefik.
Tenga claro qué aporta basic auth. Detiene los escaneos automatizados y es mucho mejor que dejar un puerto abierto. También es una única contraseña compartida delante de un shell, sin segundo factor ni posibilidad de revocar el acceso de una sola persona. Cualquiera que la obtenga y tenga una URL de lanzamiento o una cookie de sesión puede ejecutar comandos como el usuario con el que se ejecuta DSH. Si varias personas necesitan acceso, coloque delante un proxy de identidad real y lea las reglas de seguridad para ejecutar un agente de programación en un VPS antes de ampliar más la exposición.
Qué ocurre si se enlaza a 0.0.0.0 y se abre el puerto
El atajo obvio es volver a enlazar y abrir el firewall. DSH bloquea la parte comodín. --host rechaza 0.0.0.0, y el plugin @deepseek-ai/dsh-host-webserver rechaza la dirección no especificada en todas sus formas al cargar la configuración, incluidas 0.0.0.0, :: y las formas asignadas a IPv4. Lo que host sí acepta es un literal de dirección IPv4 o IPv6 concreto, por lo que indicar la IP pública es una configuración que DSH acepta. Existen guías de la comunidad que eliminan la comprobación del comodín del paquete instalado. Incluyen advertencias, y las advertencias son correctas. La misma capa de plugins resulta mucho más útil si se configura en el sentido contrario: los límites de gasto y las reglas de permisos de las herramientas restringen lo que el agente puede hacer en lugar de ampliar dónde escucha.
Esto es lo que proporciona realmente un enlace público. El puerto 3080 responde en la dirección IP pública mediante HTTP sin cifrar, y DSH no muestra ninguna advertencia al respecto: su salida de inicio sólo contiene la línea de URL y el aviso de transferencia al navegador. No hay ninguna página de inicio de sesión. El token de inicio es el único control, y atraviesa Internet en texto claro dentro de la URL, igual que la cookie de sesión después. La barrera /api inspecciona una cabecera Host que cualquier cliente que no sea un navegador escribe por sí mismo, y además admite la dirección enlazada en cualquier puerto, por lo que incluir la dirección en trustedHosts no cambia nada para un atacante que tenga curl. La propia guía de despliegue de DSH indica que ni la URL anunciada ni la barrera protegen el puerto de escucha. Detrás de ese puerto hay un agente que ejecuta comandos de shell como su usuario, junto con las credenciales de $DSH_HOME, que es donde DSH guarda los perfiles y las claves de API. Un token o una cookie interceptados permiten la ejecución remota de código en su servidor y generan cargos en su cuenta del proveedor. Los escáneres recorren continuamente todo el espacio de direcciones, por lo que una IP no publicada no es un control. Todos los secretos que el agente puede leer salen junto con el shell.
Todos los métodos anteriores existen para que nunca tenga que hacer esto. El reenvío SSH es la opción predeterminada adecuada para una persona en un único portátil. Pase a tailscale serve cuando intervenga un teléfono o cuando quiera un certificado real, y mantenga el reverse proxy público para los casos en que un navegador que no administra deba acceder a la interfaz, con autenticación real delante.
FAQ
¿Por qué http://127.0.0.1:3080 no se abre desde mi portátil?
Porque 127.0.0.1 significa «la máquina que lee esta dirección». DSH la muestra en el VPS, donde es correcta. Tu portátil lee la misma cadena y consulta su propia interfaz de loopback, donde no hay ningún proceso escuchando. Comprueba el lado del servidor con ss -ltnp | grep 3080 en el VPS. Una línea que muestre 127.0.0.1:3080 significa que DSH se está ejecutando y está enlazado al loopback de forma intencionada. Lo que necesitas es un túnel o un proxy, no una dirección de enlace diferente, y después la URL completa de inicio con su token.
¿Puedo ejecutar dsh web con --host 0.0.0.0?
No. La referencia de la CLI del repositorio, comprobada el 20 de octubre de 2026, indica que --host debe especificar una dirección local concreta, no un comodín, y que --host 0.0.0.0 termina con un error de uso. El complemento del servidor web rechaza la misma dirección al cargar su configuración. Se acepta una dirección concreta, como tu IP pública, pero el servicio usa HTTP sin cifrar, por lo que el token de inicio que protege un shell atraviesa la red en texto claro. Los parches de la comunidad eliminan la comprobación del comodín. Si aplicas uno, el firewall y la seguridad del transporte que DSH no proporciona pasan a ser tu responsabilidad.
¿Por qué todas las llamadas a /api devuelven HTTP 403 detrás de mi proxy inverso?
La barrera de confianza de /api rechaza cualquier solicitud cuyo encabezado Host no sea una dirección de loopback, la dirección de enlace del listener o una dirección incluida en trustedHosts. Detrás de un proxy, ese encabezado contiene tu nombre público, por lo que la barrera lo rechaza y el navegador registra transport failure for /api/host.describe: HTTP 403. Inicia DSH con --trusted-host <your name> y haz que coincida con el nombre de la barra de direcciones. Una entrada sin puerto coincide con ese nombre en cualquier puerto, mientras que una entrada con puerto debe coincidir exactamente con el host y el puerto. Si las llamadas devuelven 401, la barrera ha permitido el acceso y el navegador aún no tiene una sesión, así que abre la URL de inicio mostrada con su token.
¿Por qué la interfaz de DSH se carga, pero nunca termina de iniciarse mediante HTTP sin cifrar?
La aplicación web llama a crypto.randomUUID() durante el arranque, y los navegadores solo exponen esa función en un contexto seguro: HTTPS o un origen de loopback como http://localhost. Si se sirve mediante HTTP sin cifrar desde una dirección IP independiente, la función no está definida, por lo que las llamadas que dependen de ella generan errores y la interfaz nunca se completa. Servirla mediante HTTPS con tailscale serve o mediante un proxy inverso TLS elimina el problema. También lo evita acceder a ella a través de un reenvío SSH en http://localhost.
¿Qué método debe usar un único desarrollador?
El reenvío local de SSH. No añade nada al servidor, no cambia ninguna regla del firewall y reutiliza la clave SSH en la que ya confías. Ejecuta ssh -f -N -L 127.0.0.1:3080:127.0.0.1:3080 you@your-vps y abre la URL de inicio que DSH mostró, http://127.0.0.1:3080/?token=..., sin alterar su token. Cambia a tailscale serve cuando quieras usar la interfaz desde un teléfono o necesites un acceso que siga disponible aunque el portátil entre en suspensión.