Deploy DeepSeek Harness on Ubuntu: Installation, Nginx Reverse Proxy, Credentials, and WeChat Integration

DeepSeek Harness can be deployed on an Ubuntu cloud server and exposed through Nginx while keeping the Harness web service bound to localhost. During deployment, the main issues are usually Node.js and pnpm versions, DSH profile configuration, credentials format, plugin compatibility, and reverse-proxy settings.

This guide documents a practical deployment process, including the errors encountered during installation, how they were diagnosed, and how the WeChat integration was changed from the incompatible deepseek-harness-weixin plugin to dsh-clawbot.

1. What This Deployment Is Trying to Achieve

The target architecture is straightforward.

Browser
   │
   ▼
Nginx :80
   │
   ▼
127.0.0.1:3080
   │
   ▼
DeepSeek Harness Web
   │
   ├── DeepSeek API
   │
   └── WeChat / ClawBot integration

The DSH web service listens only on localhost.

Nginx handles external HTTP access and forwards requests to DSH. This avoids exposing the DSH process directly to the public network.

The important components are:

Component Role
Ubuntu 24.04 Cloud server operating system
Node.js 22.x Runtime required by DSH
pnpm 10.x Package management
DeepSeek Harness Web interface and agent runtime
Nginx Reverse proxy
DSH profile web Web application configuration
DeepSeek API Key Model API authentication
dsh-clawbot WeChat integration

2. Install Node.js Before Installing DeepSeek Harness

The first prerequisite is a working Node.js environment.

If the server returns

Command 'node' not found, but can be installed with:

apt install nodejs

Node.js has not yet been installed or is not available in the current PATH.

After installation, verify the runtime.

node -v
npm -v
pnpm -v

The deployment used Node.js 22.23.2 and pnpm 10.33.4.

A typical environment check looks like this.

node -v
npm -v
pnpm -v

The important point is to establish the runtime first. DSH plugin compatibility depends heavily on the installed DSH and Node.js versions.


3. Install DeepSeek Harness Globally

After Node.js and pnpm are ready, install DSH globally.

The installed DSH version in this deployment was:

0.1.5-rc.1

Check the installation with:

dsh --version

Also verify that the executable can be located.

which dsh

On a global npm installation, the package and executable may be located under the global Node.js installation directory.


4. Create and Inspect the Web Profile

The DSH web profile is stored under:

/root/.dsh/profiles/web

The profile contains its own package.json.

A simplified profile configuration looks like this.

{
  "name": "dsh-profile-web",
  "private": true,
  "dsh": {
    "profile": {
      "bundles": [
        "@deepseek-ai/dsh-base",
        "@deepseek-ai/dsh-web-app"
      ],
      "patchReload": "live"
    }
  }
}

One of the most important troubleshooting lessons was that removing a plugin from dependencies does not necessarily remove it from the profile.

If a plugin remains in the bundles array, DSH can continue loading it.

For example, this configuration still loads the old WeChat plugin.

{
  "dsh": {
    "profile": {
      "bundles": [
        "@deepseek-ai/dsh-base",
        "@deepseek-ai/dsh-web-app",
        "deepseek-harness-weixin"
      ]
    }
  }
}

If the plugin has been removed, its entry must also be removed from bundles.


5. A Broken package.json Can Prevent DSH from Starting

After manually editing the profile, DSH initially failed with a JSON parsing error similar to:

SyntaxError: Unexpected token ']', ...
is not valid JSON

This indicates that the problem occurs before the application itself starts.

The first thing to check is the profile’s JSON syntax.

cd /root/.dsh/profiles/web
cat package.json

Then validate the JSON with Node.js.

node -e "JSON.parse(require('fs').readFileSync('package.json','utf8')); console.log('package.json OK')"

If the file contains an extra comma, unmatched bracket, or malformed object, DSH will fail before loading the application.

This is particularly easy to trigger when manually deleting a plugin entry.


6. The DSH Credentials File Must Use the Correct YAML Structure

Once the profile JSON was fixed, DSH progressed further but stopped while reading credentials.

The credentials file was:

/root/.dsh/.credentials.yaml

The relevant error indicated a problem with the version field.

The installed credentials package was checked directly.

node -e "console.log(require('/usr/lib/node_modules/@deepseek-ai/dsh/node_modules/@deepseek-ai/dsh-credentials-local/package.json').version)"

The installed version was:

0.1.5-rc.2

Its source showed that the document version is expected to be the numeric value:

version: 1

rather than:

version: "1"

This distinction matters because YAML treats the two values differently.

A valid basic structure is:

version: 1

records:
  client-connection/browser-session:
    kind: grant
    payload:
      version: 1
      secret: <REDACTED>

Do not paste browser-session secrets into public logs, tickets, articles, or chat messages.


7. Configure the DeepSeek API Key Without Printing It

The API key should be entered interactively so it does not appear in the shell history or command line.

Use:

read -rsp "Enter your new DeepSeek API Key: " NEW_KEY
echo

The terminal will appear to accept no input while typing.

That behavior is intentional because read -s disables character echo.

You can verify that a value was entered without displaying the key.

[ -n "$NEW_KEY" ] && echo "API Key entered" || echo "API Key is empty"

If the result is:

API Key entered

the variable contains a value.

The credentials structure can then contain a reference such as:

refs:
  DEEPSEEK_API_KEY: "<REDACTED>"

The actual key should never be included in documentation.


8. Why the Original WeChat Plugin Failed

The original deployment used the deepseek-harness-weixin plugin.

The DSH process eventually failed with:

The requested module '@deepseek-ai/dsh-agent-presets'
does not provide an export named 'resolveSessionPreset'

This error is important because it identifies a package API compatibility problem.

The installed @deepseek-ai/dsh-agent-presets package exported functions including:

AgentPresetSettingsSchema
AgentPresets
SHIPPED_PRESET_ROOT
agentPresetProjectionDefinition
discoverPresets
livePresetMounts
mountPreset
readPresetMetadata
renderPresetMetadata

It did not export:

resolveSessionPreset

The old WeChat plugin expected that function.

Therefore, the failure was not caused by Nginx, the server firewall, the API key, or the WeChat login itself.

The plugin and the installed DSH package expected different APIs.


9. Why Simply Upgrading DSH Was Not a Safe Fix

It may be tempting to solve the error by upgrading every DSH package to the latest development version.

That approach was not used here because the DSH package APIs were changing between release candidates.

The installed DSH environment was already using:

@deepseek-ai/dsh-agent-presets 0.1.5-rc.2

and that version still did not provide the function required by the old WeChat plugin.

Blindly upgrading DSH could therefore introduce another compatibility problem.

A better diagnostic sequence is:

Plugin error
    ↓
Identify missing export
    ↓
Inspect installed package
    ↓
Check whether export exists
    ↓
Determine version compatibility
    ↓
Replace or patch the plugin

This is more reliable than repeatedly changing package versions without identifying the actual API mismatch.


10. Switch the WeChat Integration to dsh-clawbot

The deployment eventually switched from the old WeChat plugin to dsh-clawbot.

The plugin can be added to the web profile with:

dsh plugin --profile web add github:tangwenhao616-netizen/dsh-clawbot

After installation, inspect the profile.

cd /root/.dsh/profiles/web
cat package.json

Then inspect installed packages.

pnpm list --depth 1

The purpose of this check is to confirm that the new plugin is actually present and that the old incompatible WeChat plugin is no longer part of the profile.


11. Start DSH Manually Before Creating a systemd Service

Before relying on systemd, start DSH manually.

dsh web --no-open --trusted-host <SERVER_IP>

The server IP should be replaced with the actual server address.

The deployment keeps the DSH service bound to localhost.

The web service listens on:

127.0.0.1:3080

This is preferable to directly exposing the application port.

If the process starts successfully, test it locally from the server.

curl http://127.0.0.1:3080

The exact response depends on the DSH web application, but the important point is that the TCP connection reaches DSH.


12. Configure Nginx as the Public Entry Point

Because DSH listens on localhost, Nginx is used as the public reverse proxy.

A basic configuration is:

server {
    listen 80;
    server_name YOUR_DOMAIN;

    location / {
        proxy_pass http://127.0.0.1:3080;
        proxy_http_version 1.1;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }
}

The WebSocket headers are important because the DSH web interface can use long-lived connections.

After modifying Nginx:

nginx -t

If the configuration test succeeds:

systemctl reload nginx

13. Fix HTTP 413 Errors in the Reverse Proxy

During client requests, DSH can return:

HTTP 413

When this occurs behind Nginx, the reverse proxy may be rejecting the request before it reaches DSH.

The Nginx configuration can be extended with:

client_max_body_size 100m;
proxy_request_buffering off;

For example:

server {
    listen 80;
    server_name YOUR_DOMAIN;

    client_max_body_size 100m;

    location / {
        proxy_pass http://127.0.0.1:3080;
        proxy_http_version 1.1;

        proxy_request_buffering off;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }
}

Then test and reload:

nginx -t
systemctl reload nginx

14. A 401 Response Does Not Automatically Mean the API Key Is Wrong

One of the tests against the DSH API returned:

401 Unauthorized

For example, an API endpoint such as:

/api/llm/listProviders

may require a valid browser session.

Therefore, a 401 response from the web API should not immediately be interpreted as a failed DeepSeek API Key.

There are several independent authentication layers in this deployment.

Browser session
       │
       ▼
DSH Web API
       │
       ▼
DeepSeek API credentials
       │
       ▼
DeepSeek model

A browser-session problem and a DeepSeek API-key problem can therefore produce completely different symptoms.


15. Run DSH with systemd

Once manual startup works, systemd can manage the service.

A service definition can use:

[Unit]
Description=DeepSeek Harness Web
After=network.target

[Service]
Type=simple
WorkingDirectory=/root/.dsh
ExecStart=/usr/bin/dsh web --no-open --trusted-host <SERVER_IP>
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

The exact path to dsh should be verified with:

which dsh

After creating or updating the service:

systemctl daemon-reload
systemctl enable dsh
systemctl restart dsh

Check its status with:

systemctl status dsh --no-pager

For detailed startup errors:

journalctl -u dsh -n 50 --no-pager

If the service repeatedly restarts, the journal is usually the first place to inspect.


16. Troubleshoot DSH Startup Failures in the Right Order

When DSH fails to start, checking components in sequence saves considerable time.

Use this order.

Step 1. Check Node.js

node -v

Step 2. Check DSH

dsh --version

Step 3. Check the profile JSON

cat /root/.dsh/profiles/web/package.json

Validate it:

node -e "JSON.parse(require('fs').readFileSync('/root/.dsh/profiles/web/package.json','utf8')); console.log('OK')"

Step 4. Check installed packages

cd /root/.dsh/profiles/web
pnpm list --depth 1

Step 5. Check credentials

cat /root/.dsh/.credentials.yaml

Do not expose secrets while doing this.

Step 6. Start DSH manually

dsh web --no-open --trusted-host <SERVER_IP>

Step 7. Check systemd logs

journalctl -u dsh -n 50 --no-pager

Step 8. Check Nginx

nginx -t

Then:

systemctl status nginx --no-pager

This separates application errors from reverse-proxy errors.


17. The Main Errors and Their Actual Causes

Error Likely cause in this deployment Resolution
node: command not found Node.js unavailable Install/configure Node.js
Unexpected token in package.json Invalid JSON Fix profile JSON
Credentials version error Incorrect YAML version type Use numeric version: 1
resolveSessionPreset missing WeChat plugin API mismatch Remove or replace incompatible plugin
DSH service restart loop Startup exception Read journalctl
HTTP 413 Nginx request-size/buffering restriction Adjust Nginx proxy settings
HTTP 401 Browser session authentication Check DSH web session
Old WeChat plugin keeps loading Plugin remains in bundles Remove it from profile

The key is to classify the failure before changing the configuration.


18. Recommended Deployment Structure

After the migration, the intended structure is:

/root/.dsh/
├── .credentials.yaml
└── profiles/
    └── web/
        └── package.json

The public request path is:

Internet
   │
   ▼
Nginx
   │
   ▼
127.0.0.1:3080
   │
   ▼
DSH Web
   │
   ├── DeepSeek credentials
   │
   └── dsh-clawbot

This keeps the DSH application itself behind the reverse proxy.


19. Deployment Checklist

Use the following checklist when deploying the same architecture on another Ubuntu server.

  • [ ] Confirm Ubuntu is available.
  • [ ] Install Node.js.
  • [ ] Verify node -v.
  • [ ] Verify npm and pnpm.
  • [ ] Install DeepSeek Harness.
  • [ ] Verify dsh --version.
  • [ ] Create or inspect the web profile.
  • [ ] Validate package.json.
  • [ ] Remove incompatible plugins from bundles.
  • [ ] Verify .credentials.yaml.
  • [ ] Use numeric version: 1.
  • [ ] Configure the DeepSeek API key without printing it.
  • [ ] Install dsh-clawbot if it is the selected WeChat integration.
  • [ ] Run DSH manually.
  • [ ] Confirm DSH listens on 127.0.0.1:3080.
  • [ ] Configure Nginx.
  • [ ] Enable WebSocket proxy headers.
  • [ ] Add the request-size settings if HTTP 413 occurs.
  • [ ] Run nginx -t.
  • [ ] Reload Nginx.
  • [ ] Create the systemd service.
  • [ ] Check systemctl status dsh.
  • [ ] Check journalctl -u dsh when startup fails.
  • [ ] Test the browser session separately from the DeepSeek API credentials.

20. Frequently Asked Questions

Why does DSH say that Node.js cannot be found?

Node.js is not installed or is not available in the current PATH. Install Node.js first and verify it with node -v.

Why does editing package.json cause DSH to fail immediately?

DSH cannot load the profile if package.json contains invalid JSON. Validate the file before starting DSH.

Why must credentials use version: 1?

The installed credentials package expects the document version to be the numeric value 1. A quoted "1" has a different YAML type.

Why did the old WeChat plugin fail?

The installed DSH preset package did not export resolveSessionPreset, while the old WeChat plugin attempted to import that function. The plugin and DSH versions were therefore incompatible.

Should DSH listen directly on the public IP?

The deployment keeps DSH on 127.0.0.1:3080 and uses Nginx as the external entry point.

What does HTTP 413 mean in this setup?

It indicates that the reverse-proxy layer rejected a request because of its configured request handling limits. Increasing client_max_body_size and disabling request buffering can address this case.

Does a 401 response mean the DeepSeek API key is invalid?

Not necessarily. DSH’s web API can require browser-session authentication, so the browser session should be checked separately from the DeepSeek API credentials.

How should a DSH service restart loop be diagnosed?

Start with:

systemctl status dsh --no-pager

Then inspect:

journalctl -u dsh -n 50 --no-pager

The first startup exception usually identifies whether the problem is the profile, credentials, plugin compatibility, or another runtime component.