DSH Server Setup
Run DeepSeek Harness on a remote VPS with reverse proxy as a DSH plugin.
This is a production-tested setup for running DSH on an Ubuntu server (ARM64 or x64) with:
Features
- systemd service — auto-restart, logging, persistence
- Reverse proxy plugin — smanx/dsh-proxy runs inside DSH, configurable from Settings
- Basic Auth (optional) — protect your instance from unauthorized access
- File upload & preview — built-in in DSH 0.1.5+ (no plugin needed)
- PWA support — install-as-app via custom plugin
- Trusted host — remote access via domain name without token in URL
Architecture
Browser/Phone
│
▼
DSH (0.0.0.0:3080) ← dsh-proxy plugin handles external access
│
▼
DSH core (127.0.0.1:3079) ← internal port
│
▼
DeepSeek API / MCP servers / Filesystem
The proxy runs as a DSH plugin — no separate process needed. It starts and stops with dsh web.
Quick Start
1. Install prerequisites
# Node.js 24+ (via fnm recommended)
curl -fsSL https://fnm.vercel.app/install | bash
source ~/.bashrc
fnm install 24
# pnpm
npm install -g pnpm
# DSH
npm install -g @deepseek-ai/dsh@0.1.5-rc.1
2. Create a web profile
mkdir -p ~/.dsh/profiles/web
cd ~/.dsh/profiles/web
3. Add plugins
# Reverse proxy
pnpm add github:smanx/dsh-proxy
# Other recommended plugins
pnpm add dshmarket dsh-mnemon dsh-free-search dsh-config-manager dsh-mcp-sync
4. Configure the profile
~/.dsh/profiles/web/package.json:
{
"name": "dsh-profile-web",
"private": true,
"dependencies": {
"@smanx/dsh-proxy": "github:smanx/dsh-proxy",
"dshmarket": "^1.45.1"
},
"dsh": {
"profile": {
"bundles": [
"@deepseek-ai/dsh-base",
"@deepseek-ai/dsh-web-app",
"@smanx/dsh-proxy",
"dshmarket"
]
}
}
}
~/.dsh/profiles/web/cordis.patch.yml:
# dsh-proxy: reverse proxy for LAN/remote access
- id: dsh-proxy
name: "@smanx/dsh-proxy"
config:
listenPort: 3080
# username: admin # Uncomment to enable Basic Auth
# password: changeme # Uncomment to enable Basic Auth
5. Configure credentials
Create ~/.dsh/.credentials.yaml:
version: 1
refs:
OPENCODE_GO_API_KEY: sk-your-api-key-here
records: {}
6. Install the systemd service
sudo cp systemd/dsh.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable dsh
sudo systemctl start dsh
7. Verify
# Check DSH is running
curl -s http://127.0.0.1:3079/ | head -5
# Check proxy is accessible
curl -s http://YOUR_SERVER_IP:3080/ | head -5
# Check logs
journalctl -u dsh -f
Updating DSH
The update_all.sh script handles updates automatically via cron. To update manually:
# Update DSH
npm install -g @deepseek-ai/dsh@latest
# Restart service
sudo systemctl restart dsh
Configuration
Proxy Settings (via UI)
Go to Settings → LAN Proxy in the DSH web GUI to:
- Start/stop the proxy
- Change the listen port
- Set username and password for Basic Auth
- View connection status
Proxy Settings (via cordis.patch.yml)
- id: dsh-proxy
name: "@smanx/dsh-proxy"
config:
listenPort: 3080 # External port (0.0.0.0)
username: admin # Basic Auth username (empty = disabled)
password: changeme # Basic Auth password
HTTPS / Remote Access
The proxy does not handle HTTPS. For production, put a TLS terminator in front:
- Pangolin (recommended) — self-hosted identity-aware VPN + reverse proxy with WireGuard, dashboard, and access control
- Cloudflare Tunnel —
cloudflared tunnel --url http://127.0.0.1:3080 - Caddy — auto HTTPS with
reverse_proxy localhost:3080 - Nginx + Let's Encrypt — standard reverse proxy config
Pangolin Setup
Pangolin is a self-hosted tunnel that gives you HTTPS + authentication + WireGuard VPN without opening ports. It runs as Docker containers on the same VPS.
1. Install Pangolin:
git clone https://github.com/fosrl/pangolin.git ~/pangolin
cd ~/pangolin
bash install.sh
2. Configure (~/pangolin/config/config.yml):
domain: yourdomain.com
letsencrypt:
email: you@yourdomain.com
useLetsEncrypt: true
flask_secret: <random-secret> # Generate with: openssl rand -hex 32
jwt_secret: <random-secret> # Generate with: openssl rand -hex 32
3. Add DSH resource in Pangolin dashboard:
- Open
https://yourdomain.com→ login - Go to Resources → New Resource
- Set:
- Name:
dsh - Protocol:
HTTP - Target IP:
127.0.0.1 - Target Port:
3080
- Name:
- Create a Target (the Gerbil client) and generate a config
- On your VPS, add the Gerbil client:
sudo nano /etc/pangolin/client/config.yml
sudo systemctl restart pangolin-client
4. Access DSH:
https://dsh.yourdomain.com
Advantages over direct proxy:
- HTTPS with automatic Let's Encrypt certificates
- Built-in authentication (email-based or SSO)
- WireGuard VPN option for full network access
- Access control and audit logs
- No need to open additional ports
Reverse Proxy Features
The proxy (smanx/dsh-proxy, MIT license) provides:
crypto.randomUUID polyfill
DSH's frontend uses crypto.randomUUID() for RPC IDs, but this API is only available in secure contexts (HTTPS/localhost). When accessing via LAN IP or public URL, the polyfill injects a compatible implementation using getRandomValues().
Loopback trust patch
DSH checks location.hostname to determine if the browser is local. Non-loopback hosts get degraded behavior (memory-only mode, no settings). The proxy patches the client JS to treat proxied connections as loopback, enabling full functionality.
WebSocket support
The proxy forwards WebSocket connections for real-time DSH features (streaming, live updates).
Public path whitelist
/manifest.webmanifest, /favicon.svg, and /favicon.ico are served without auth so browsers can fetch PWA metadata without credentials.
DSH 0.1.5 Changes
Built-in features (no plugins needed)
- File upload — drag & drop or paste images directly in chat
- File preview — sidebar with syntax highlighting, PDF, images
- Sidebar — multi-tab, split view, fullscreen for files and deliverables
Removed features
- Telegram integration — both
dsh-telegramanddsh-telegram-bridgeare broken with DSH 0.1.5 - Cron scheduler —
dsh-cronHost-side doesn't start - Webhook — removed, was used with cron
Known issues
dsh-telegramv0.2.0 — Host-sideapply()never executesdsh-telegram-bridge— depends onapiProxywhich doesn't exist in DSH 0.1.5dsh-cron(@goodandready) — Host-side doesn't start, no logs generated
OpenCode Go Session Header
OpenCode Go requires an x-opencode-session header for per-conversation routing. DSH doesn't send this natively.
Workaround (in ~/.dsh/settings.yaml):
llm-pi-ai:
providers:
opencode-go:
headers:
x-opencode-session: "dsh-global"
Status: PR proposed at DSH #5495 adding sessionHeader config field for dynamic per-session IDs.
Troubleshooting
DSH won't start
- Check logs:
journalctl -u dsh -f - Verify Node.js is in PATH:
which node - Check DSH home exists:
ls ~/.dsh/ - Verify credentials:
cat ~/.dsh/.credentials.yaml
Proxy not accessible
- Check if the plugin loaded: Settings → Plugins → dsh-proxy
- Check port is open:
ss -tlnp | grep 3080 - Check firewall:
sudo ufw allow 3080/tcp - For remote access, add
--trusted-host yourdomain.comto run.sh
WebSocket not working
- The proxy must forward
UpgradeandConnectionheaders - If behind another proxy (nginx/Caddy), ensure WebSocket is enabled there too
Settings page shows "unavailable in this browser"
- The loopback patch may not be applied
- Check the proxy plugin is enabled in Settings → Plugins
- Clear browser cache and reload
Plugin not loading
- Check the plugin is in
bundlesarray inpackage.json - Check
cordis.patch.ymlsyntax - Check logs for errors:
journalctl -u dsh -f | grep -i error
Credentials not found
- Verify
~/.dsh/.credentials.yamlexists and has correct format - Check file permissions:
ls -la ~/.dsh/.credentials.yaml - Ensure the credential name matches what the plugin expects
Files
dsh-server-setup/
├── README.md # This file
├── PLUGINS.md # Plugin stack list
├── run.sh # DSH wrapper script
├── systemd/
│ └── dsh.service # Systemd unit file
└── dsh-proxy/ # Standalone proxy (alternative)
└── node/
├── index.js
├── proxy-core.js
└── package.json
Plugins
See PLUGINS.md for a full list of installed plugins with descriptions.
Credits
- Reverse proxy — smanx/dsh-proxy (MIT license). Also provides Go builds for standalone deployments.
- Remote access — Pangolin (self-hosted VPN + reverse proxy with WireGuard)
- DSH — DeepSeek Harness by DeepSeek AI
License
MIT
No comments yet. Be the first to write one.