Documentation forWeb Help Desk

WHD multi-instance install on Windows servers with an external database

Multiple Windows servers with one shared external database.

What you are building

This guide sets up WHD across three Windows servers that all share one external database. Microsoft SQL Server is used in the examples below.

Server

Address

Job

Daemon

10.XX.XX.80

Runs background work and owns schema migration

UI node 1

10.XX.XX.81

Serves web pages and API traffic only

UI node 2

10.XX.XX.129

Serves web pages and API traffic only

Database

10.XX.XX.235

Shared external SQL Server

Replace these addresses with your own throughout.

The one rule that matters most

Only one server may be the daemon node. If more than one server runs daemon work, you get duplicate background jobs, duplicate outbound email, and upgrade-time migration contention.

UI-only nodes must keep all of these settings:

whd.node.daemon-enabled=false
spring.liquibase.enabled=false
whd.license.cluster-secondary=true
whd.license.cluster-primary-url=https://10.XX.XX.80

Before you begin

You need:

  • three Windows servers that can reach each other and the database server;

  • the same WHD installer on all three nodes;

  • a shared external database already created;

  • a database user that can create tables in that database;

  • Administrator access on all three Windows servers;

  • access to copy conf\.whd.properties from the daemon node to the UI-only nodes.

A note about installer behavior

Different validated Windows builds have shown two startup patterns:

  1. the installer finishes and tells you to run whd_start.bat, or

  2. the installer auto-starts the services immediately.

The steps in this guide are written so they work in either case:

  • let the daemon node reach the setup wizard and complete it;

  • on UI-only nodes, stop the services before the node is allowed to stay on the shared database;

  • then apply the UI role, copy conf\.whd.properties, and start the node in the correct role.

Step 1 - Install the daemon node

Do all of this on the daemon node (10.XX.XX.80).

Confirm the server is clean

Open an elevated PowerShell session and run:

Get-Service WHD-PostgreSQL, WHD-Backend, WHD-Frontend, WHD-Caddy -ErrorAction SilentlyContinue
Test-Path 'C:\Program Files\WebHelpDesk'

What you should see:

  • no WHD services yet, or only stale services you intend to replace;

  • False from Test-Path on a clean machine.

If WHD is already installed, remove it first from Apps and Features or by using the existing uninstaller before continuing.

Install the package

Start-Process -Wait -FilePath 'C:\Share\WebHelpDesk-<version>-x64.exe' -Verb RunAs

Follow the installer prompts.

Confirm the services exist

Get-Service WHD-PostgreSQL, WHD-Backend, WHD-Frontend, WHD-Caddy

If the installer did not start the application automatically, start it now:

& 'C:\Program Files\WebHelpDesk\whd_start.bat'

Confirm the daemon node is reachable locally

Get-Service WHD-PostgreSQL, WHD-Backend, WHD-Frontend, WHD-Caddy
Invoke-WebRequest -UseBasicParsing -Uri 'http://127.0.0.1:8081/actuator/health'
Get-Content 'C:\Program Files\WebHelpDesk\conf\whd.env' | Select-String '^(WHD_HTTPS_PORT|WHD_DOMAIN)='

What you should see: all services present, backend health responding, and the current local hostname / port configuration.

Step 2 - Connect the daemon node to the external database

Open WHD in a browser

Browse to https://10.XX.XX.80 or https://10.XX.XX.80:<port> if your local WHD HTTPS port is not 443.

Enter the external database details

Use your real values. Example:

Field

Value

Database type

Microsoft SQL Server

Host

10.XX.XX.235

Port

1433

Database name

your existing empty database

Username / password

a user who can create tables

Confirm what the wizard saved

Get-Content 'C:\Program Files\WebHelpDesk\conf\.whd.properties' |
  Select-String '^whd\.db\.(embedded|host|port|name|username|subprotocol)='
Invoke-WebRequest -UseBasicParsing -Uri 'http://127.0.0.1:8081/actuator/health'

What matters most is:

whd.db.embedded=NO

That tells the next step this node is using the shared external database and should not depend on the local embedded PostgreSQL service.

Step 3 - Finish setting up the daemon node

Apply the daemon role

powershell.exe -ExecutionPolicy Bypass -File "C:\Program Files\WebHelpDesk\scripts\whd-set-node-role.ps1" -Role daemon

What you should see in the script output:

  • Database: external (shared server)

  • whd.node.daemon-enabled=true

  • spring.liquibase.enabled=true

  • whd.license.cluster-secondary=false

  • embedded PostgreSQL stopped and disabled

Restart and verify

Restart-Service WHD-Backend -Force
Start-Service WHD-Frontend, WHD-Caddy
Start-Sleep -Seconds 90
Invoke-WebRequest -UseBasicParsing -Uri 'http://127.0.0.1:8081/actuator/health'
Get-Service WHD-PostgreSQL
Get-Content 'C:\Program Files\WebHelpDesk\conf\application.properties' |
  Select-String '^(whd\.node\.daemon-enabled|spring\.liquibase\.enabled|whd\.license\.cluster-)='

What you should see:

  • backend health returns UP;

  • WHD-PostgreSQL is Stopped and typically Disabled;

  • the daemon-related properties are present.

Step 4 - Install each UI-only node

Do this on UI node 1 (10.XX.XX.81), then repeat on UI node 2 (10.XX.XX.129).

Install the package

Start-Process -Wait -FilePath 'C:\Share\WebHelpDesk-<version>-x64.exe' -Verb RunAs

Stop the WHD services before keeping the node in the cluster

Run this immediately after install if the services auto-started. If they are already stopped, the command is still safe:

Stop-Service WHD-Caddy, WHD-Frontend, WHD-Backend, WHD-PostgreSQL -Force -ErrorAction SilentlyContinue
Get-Service WHD-PostgreSQL, WHD-Backend, WHD-Frontend, WHD-Caddy

Apply the UI-only role

powershell.exe -ExecutionPolicy Bypass -File "C:\Program Files\WebHelpDesk\scripts\whd-set-node-role.ps1" -Role ui -PrimaryUrl https://10.XX.XX.80

Copy the shared database configuration from the daemon node

If the daemon node is accessible through the default admin share:

Copy-Item -LiteralPath '\\10.XX.XX.80\c$\Program Files\WebHelpDesk\conf\.whd.properties' `
  -Destination 'C:\Program Files\WebHelpDesk\conf\.whd.properties' -Force
Get-Content 'C:\Program Files\WebHelpDesk\conf\.whd.properties' |
  Select-String '^whd\.db\.(embedded|host|port|name|username|subprotocol)='

If you cannot use the admin share, copy the same file by your normal secure file-transfer method.

4.5 - Confirm the UI-only properties are correct

Get-Content 'C:\Program Files\WebHelpDesk\conf\application.properties' |
  Select-String '^(whd\.node\.daemon-enabled|spring\.liquibase\.enabled|whd\.license\.cluster-)='
Get-Service WHD-PostgreSQL

What you should see:

  • whd.node.daemon-enabled=false

  • spring.liquibase.enabled=false

  • whd.license.cluster-secondary=true

  • WHD-PostgreSQL stopped / disabled

Start the UI-only node

Start-Service WHD-Backend
Start-Service WHD-Frontend, WHD-Caddy
Start-Sleep -Seconds 90
Invoke-WebRequest -UseBasicParsing -Uri 'http://127.0.0.1:8081/actuator/health'

Repeat Step 4 on the second UI-only node after the first one is healthy.

Step 5 - Check the finished system

Run this from any machine that can reach the three nodes:

$nodes = '10.XX.XX.80','10.XX.XX.81','10.XX.XX.129'
foreach ($node in $nodes) {
  $response = Invoke-WebRequest -SkipCertificateCheck -UseBasicParsing -Uri ("https://{0}/" -f $node)
  "{0}: {1}" -f $node, $response.StatusCode
}

All three should answer.

Then confirm they share the same data by creating a ticket on one node and reading it from the others.

Step 6 - Give users one web address (optional)

You may use any load balancer that supports:

  1. sticky sessions when the login flow requires them;

  2. keeping the daemon node out of the user-facing pool; and

  3. preserving the client-facing host and port when forwarding traffic.

The shipped cluster template is:

  • C:\Program Files\WebHelpDesk\conf\Caddyfile.cluster-lb

Step 7 - Upgrade an existing external-database cluster

Use this section when all nodes are already part of a working Windows multi-instance cluster and you want to move them to a newer installer build.

Upgrade the daemon first, then each UI-only node one at a time.

Record the current role settings before you change anything

Run this on every node:

hostname
Get-Content 'C:\Program Files\WebHelpDesk\conf\application.properties' |
  Select-String '^(whd\.node\.daemon-enabled|spring\.liquibase\.enabled|whd\.license\.cluster-)='
Get-Content 'C:\Program Files\WebHelpDesk\conf\.whd.properties' |
  Select-String '^whd\.db\.(embedded|host|port|name|username|subprotocol)='
Get-Service WHD-PostgreSQL, WHD-Backend, WHD-Frontend, WHD-Caddy

What you should see:

  • daemon node: whd.node.daemon-enabled=true, spring.liquibase.enabled=true, whd.license.cluster-secondary=false

  • UI-only nodes: whd.node.daemon-enabled=false, spring.liquibase.enabled=false, whd.license.cluster-secondary=true

Place the new installer on each node

Example:

Copy-Item 'C:\Share\WebHelpDesk-<new-version>-x64.exe' '\\10.XX.XX.80\c$\Temp\WebHelpDesk-<new-version>-x64.exe'
Copy-Item 'C:\Share\WebHelpDesk-<new-version>-x64.exe' '\\10.XX.XX.81\c$\Temp\WebHelpDesk-<new-version>-x64.exe'
Copy-Item 'C:\Share\WebHelpDesk-<new-version>-x64.exe' '\\10.XX.XX.129\c$\Temp\WebHelpDesk-<new-version>-x64.exe'

Upgrade the daemon node first

Run on 10.XX.XX.80:

Start-Process -Wait -FilePath 'C:\Temp\WebHelpDesk-<new-version>-x64.exe' -Verb RunAs
powershell.exe -ExecutionPolicy Bypass -File "C:\Program Files\WebHelpDesk\scripts\whd-set-node-role.ps1" -Role daemon
Get-Service WHD-Backend, WHD-Frontend, WHD-Caddy | Where-Object Status -ne 'Running' | Start-Service
Restart-Service WHD-Backend -Force
Start-Service WHD-Frontend, WHD-Caddy -ErrorAction SilentlyContinue
Start-Sleep -Seconds 90
Invoke-WebRequest -UseBasicParsing -Uri 'http://127.0.0.1:8081/actuator/health'
Get-Service WHD-PostgreSQL
Get-Content 'C:\Program Files\WebHelpDesk\conf\application.properties' |
  Select-String '^(whd\.node\.daemon-enabled|spring\.liquibase\.enabled|whd\.license\.cluster-)='

Upgrade UI-only node 1

Run on 10.XX.XX.81:

Start-Process -Wait -FilePath 'C:\Temp\WebHelpDesk-<new-version>-x64.exe' -Verb RunAs
Stop-Service WHD-Caddy, WHD-Frontend, WHD-Backend, WHD-PostgreSQL -Force -ErrorAction SilentlyContinue
powershell.exe -ExecutionPolicy Bypass -File "C:\Program Files\WebHelpDesk\scripts\whd-set-node-role.ps1" -Role ui -PrimaryUrl https://10.XX.XX.80
if (-not (Test-Path 'C:\Program Files\WebHelpDesk\conf\.whd.properties')) {
  Copy-Item -LiteralPath '\\10.XX.XX.80\c$\Program Files\WebHelpDesk\conf\.whd.properties' `
    -Destination 'C:\Program Files\WebHelpDesk\conf\.whd.properties' -Force
}
Start-Service WHD-Backend
Start-Service WHD-Frontend, WHD-Caddy
Start-Sleep -Seconds 90
Invoke-WebRequest -UseBasicParsing -Uri 'http://127.0.0.1:8081/actuator/health'
Get-Content 'C:\Program Files\WebHelpDesk\conf\application.properties' |
  Select-String '^(whd\.node\.daemon-enabled|spring\.liquibase\.enabled|whd\.license\.cluster-)='
Get-Service WHD-PostgreSQL

Before you continue to the second UI-only node, confirm this upgraded node still shows:

  • whd.node.daemon-enabled=false

  • spring.liquibase.enabled=false

  • WHD-PostgreSQL stopped / disabled

Upgrade UI-only node 2

Run on 10.XX.XX.129 and repeat the same commands from Upgrade UI-only node 1.

Confirm the whole upgraded cluster is healthy

$nodes = '10.XX.XX.80','10.XX.XX.81','10.XX.XX.129'
foreach ($node in $nodes) {
  $response = Invoke-WebRequest -SkipCertificateCheck -UseBasicParsing -Uri ("https://{0}/" -f $node)
  "{0}: {1}" -f $node, $response.StatusCode
}

Then confirm each node still points at the same shared external database:

Get-Content 'C:\Program Files\WebHelpDesk\conf\.whd.properties' |
  Select-String '^whd\.db\.(embedded|host|port|name|username|subprotocol)='

If something goes wrong

What you see

What it means

What to do

The setup wizard appears again after upgrade

conf\.whd.properties was not preserved or not copied back

Restore or re-copy conf\.whd.properties, then restart services

A UI-only node starts WHD-PostgreSQL

The node is not using the cluster service layout

Re-run whd-set-node-role.ps1 -Role ui -PrimaryUrl https://10.XX.XX.80

A UI-only node comes back with daemon-enabled=true

The role was not re-applied after upgrade

Re-run the role script, then restart backend/frontend/Caddy

The backend does not start after upgrade on an external-DB node

Service dependencies were restored to the standalone layout

Re-run whd-set-node-role.ps1 with the correct role so WHD-Backend no longer depends on WHD-PostgreSQL

The load balancer redirects users to an individual node

The load balancer is not preserving the client-facing host and port

Follow WHD_MULTI_INSTANCE_CADDY_SETUP.md or apply the same rule in your own load balancer

Known limitations

  • Upgrade the daemon first, then the UI-only nodes one at a time.

  • Re-apply the node role after every cluster upgrade.

  • On builds that do not include the UI-node Liquibase fix, verify UI-only nodes still keep spring.liquibase.enabled=false before returning them to service.

  • The daemon node must stay out of the user-facing load-balancer pool.