Documentation forWeb Help Desk

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

Multiple macOS servers with one shared external database.

What you are building

This guide sets up WHD across three macOS servers that all share one external database – Microsoft SQL Server in this example.

Server

Address

Job

Daemon

10.XX.XX.80

Does all background work – email, reports, reminders

UI node 1

10.XX.XX.81

Serves web pages to users only

UI node 2

10.XX.XX.129

Serves web pages to users only

Database

10.XX.XX.235

Microsoft SQL Server – shared by all three

Replace these addresses with your own throughout.

The one rule that matters most

Only one server may do the background work. That is the daemon node. If two servers do it, you will get duplicate emails and duplicate tickets.

macOS does not currently ship the same node-role helper script that Linux and Windows do, so the main thing to get right here is applying the correct role properties on each server and keeping the local bundled PostgreSQL service unloaded on every external-database node.

Why an external database is easier

Because the database lives on its own server, you do not need to open a local bundled database to the network, copy database files between servers, or convert each node separately. Every server is configured the same way and points at the same place.

Before you begin

You need:

  • three macOS 13+ servers that can reach each other and the database server;

  • the same WHD package on all three – the installer file must be identical, or the saved database password and app artifacts may not match across the cluster;

  • a database already created on your MySQL/PostgreSQL/Microsoft SQL Server instance;

  • a database user with permission to create tables in that database;

  • administrator access on all three servers;

  • SSH or another secure way to copy conf/.whd.properties from the daemon node to the UI-only nodes.

Steps 1 through 6 cover a fresh installation. Step 7 covers an upgrade of an existing external-database multi-instance cluster.

Step 1 - Install the daemon node

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

Confirm the server is clean

sudo launchctl list | grep com.solarwinds.whd
test -d /opt/webhelpdesk && echo "Existing install found" || echo "Clean machine"

What you should see: no WHD services from the first command, and Clean machine from the second. If WHD is already installed, remove it first with:

sudo /opt/webhelpdesk/uninstall.sh

Confirm the database server can be reached

nc -z -w 5 10.XX.XX.235 1433 && echo "Database reachable" || echo "BLOCKED"

What you should see: Database reachable.

If you see BLOCKED, the database server is not accepting connections from this server. Fix that before going any further.

Install the package

sudo installer -pkg /tmp/WebHelpDesk-<version>-macos.pkg -target /

What you should see: the installer completes without errors. This copies files, runs the bundled installer scripts, and starts the local standalone stack on first install.

Confirm it started

sudo launchctl list | grep com.solarwinds.whd
curl -s http://127.0.0.1:8081/actuator/health; echo
grep -E '^WHD_HTTPS_PORT|^WHD_DOMAIN' /opt/webhelpdesk/conf/whd.env

What you should see: four loaded services, backend health JSON, then your address and web port. Make a note of WHD_HTTPS_PORT – you will need it later if your node is not using the default 8443.

Step 2 - Connect the daemon node to the external database

Open WHD in a browser

Go to https://10.XX.XX.80:8443 (use the port from Step 1.4 if it is not 8443).

Your browser will warn you the certificate is not trusted. That is expected – the installer created a temporary certificate. Continue past the warning.

Enter your database details

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

Let the wizard finish

The wizard tests the connection, then creates all the tables. On a new database this takes several minutes. Do not reload the page.

Confirm what was saved

sudo grep -E '^whd\.db\.(embedded|host|port|name|username|subprotocol)' /opt/webhelpdesk/conf/.whd.properties
curl -s http://127.0.0.1:8081/actuator/health; echo

What you should see:

whd.db.embedded=NO
whd.db.host=10.XX.XX.235
whd.db.name=whd
whd.db.port=1433
whd.db.subprotocol=mssql
whd.db.username=whd_user

whd.db.embedded=NO is the important line. It tells the next step that this server no longer needs its own built-in database.

Step 3 - Finish setting up the daemon node

Apply the daemon role

tmp="/tmp/whd-application.properties.$$"
sudo awk '!/^(whd\.node\.daemon-enabled|spring\.liquibase\.enabled|whd\.license\.cluster-secondary|whd\.license\.cluster-primary-url)=/' \
  /opt/webhelpdesk/conf/application.properties > "$tmp"
cat >>"$tmp" <<'EOF'
whd.node.daemon-enabled=true
spring.liquibase.enabled=true
whd.license.cluster-secondary=false
EOF
sudo install -o whd -g whd -m 644 "$tmp" /opt/webhelpdesk/conf/application.properties
rm -f "$tmp"

sudo launchctl unload -w /Library/LaunchDaemons/com.solarwinds.whd.pgsql.plist
sudo launchctl bootout system/com.solarwinds.whd.backend 2>/dev/null || true
sudo launchctl bootout system/com.solarwinds.whd.frontend 2>/dev/null || true
sudo launchctl bootout system/com.solarwinds.whd.caddy 2>/dev/null || true
sudo launchctl bootstrap system /Library/LaunchDaemons/com.solarwinds.whd.backend.plist
sleep 10
sudo launchctl bootstrap system /Library/LaunchDaemons/com.solarwinds.whd.frontend.plist
sleep 3
sudo launchctl bootstrap system /Library/LaunchDaemons/com.solarwinds.whd.caddy.plist

What you should see:

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

Turning off the built-in database on the daemon node is correct.

Restart and check

sleep 90
curl -s http://127.0.0.1:8081/actuator/health; echo
grep -E 'daemon-enabled|liquibase|cluster-' /opt/webhelpdesk/conf/application.properties
sudo launchctl list | grep com.solarwinds.whd.pgsql || echo "inactive"

What you should see: health UP, then the three settings above, then inactive for the built-in database.

If it says OUT_OF_SERVICE, wait another minute and try again. WHD reports this while it is still finishing its start-up checks. Against an external SQL Server this regularly takes longer than 60 seconds. It is only a problem if it still says that after about five minutes.

The daemon node is now ready.

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).

Confirm the server is clean and can reach the database

test -d /opt/webhelpdesk && echo "Existing install found" || echo "Clean machine"
nc -z -w 5 10.XX.XX.235 1433 && echo "Database reachable" || echo "BLOCKED"

Install the package

sudo installer -pkg /tmp/WebHelpDesk-<version>-macos.pkg -target /

Use exactly the same installer file as the daemon node. If the versions differ, the database settings copied below may no longer match what this node expects.

Stop the local stack and apply the UI-only role

sudo /opt/webhelpdesk/stop.sh
tmp="/tmp/whd-application.properties.$$"
sudo awk '!/^(whd\.node\.daemon-enabled|spring\.liquibase\.enabled|whd\.license\.cluster-secondary|whd\.license\.cluster-primary-url)=/' \
  /opt/webhelpdesk/conf/application.properties > "$tmp"
cat >>"$tmp" <<'EOF'
whd.node.daemon-enabled=false
spring.liquibase.enabled=false
whd.license.cluster-secondary=true
whd.license.cluster-primary-url=https://10.XX.XX.80:8443
EOF
sudo install -o whd -g whd -m 644 "$tmp" /opt/webhelpdesk/conf/application.properties
rm -f "$tmp"
sudo launchctl unload -w /Library/LaunchDaemons/com.solarwinds.whd.pgsql.plist

What you should see:

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

That is correct. The services must remain stopped until the shared database settings are copied in. Starting the node now would bring it up with whatever local state it currently has, which is not what you want for the shared external-database flow.

Copy the database settings from the daemon node

scp root@10.XX.XX.80:/opt/webhelpdesk/conf/.whd.properties /tmp/.whd.properties
sudo install -o whd -g whd -m 600 /tmp/.whd.properties /opt/webhelpdesk/conf/.whd.properties
sudo grep -E '^whd\.db\.(embedded|host|name)' /opt/webhelpdesk/conf/.whd.properties

No changes are needed to this file. Because the database is external, the settings are already correct for every server.

You will be asked to confirm an SSH fingerprint the first time – answer yes, then enter the daemon node’s root password. Answering no cancels the copy without an obvious error, and the UI-only node will then fail to start because it is still pointing at its own empty database.

Start the UI-only node

sudo launchctl bootstrap system /Library/LaunchDaemons/com.solarwinds.whd.backend.plist
sleep 10
sudo launchctl bootstrap system /Library/LaunchDaemons/com.solarwinds.whd.frontend.plist
sleep 3
sudo launchctl bootstrap system /Library/LaunchDaemons/com.solarwinds.whd.caddy.plist
sleep 90
curl -s http://127.0.0.1:8081/actuator/health; echo

What you should see: {"status":"UP","groups":["liveness","readiness"]}.

Confirm it is behaving as a UI-only node

grep -E 'daemon-enabled|liquibase|cluster-' /opt/webhelpdesk/conf/application.properties
sudo launchctl list | grep com.solarwinds.whd.pgsql || echo "inactive"
tail -n 300 /opt/webhelpdesk/log/whd-nextgen.log | grep -icE 'EmailTicketScheduledTask|ReportScheduler'

What you should see: daemon-enabled=false, liquibase.enabled=false, cluster-secondary=true; the built-in database inactive; and the last command printing 0.

Step 5 - Check the finished system

From any of the three servers:

for ip in 10.XX.XX.80 10.XX.XX.81 10.XX.XX.129; do
 echo -n "$ip: "; curl -sk -o /dev/null -w "%{http_code}\n" https://$ip:8443/
done

All three should answer.

Then confirm they share data: create a ticket on one server, and check it appears on the other two.

Step 6 - Give users one web address (optional)

Until now, users had to know all three server addresses. This part puts a single address in front of the two UI-only nodes, spreads work between them, and takes a failed UI node out of service automatically.

Skip this part if you already own a load balancer (F5, NGINX, AWS, Azure, and similar). Just apply the same two rules below in that product instead.

Two rules you must not break

  1. Users must stay on the same server for their whole visit. WHD keeps part of the sign-in flow in the memory of whichever server the user landed on. If requests are spread across servers at random, users are logged out constantly. The settings below do this with a cookie.

  2. Never put the daemon node in the user-facing pool. It does the background work. Only the two UI-only nodes serve users.

Prepare the log file

sudo touch /opt/webhelpdesk/log/caddy-lb-access.log
sudo chown whd:whd /opt/webhelpdesk/log/caddy-lb-access.log
sudo chmod 640 /opt/webhelpdesk/log/caddy-lb-access.log

The load balancer cannot create its own log file, so create it first. If you skip this, the load balancer fails to start with permission denied.

Create the configuration

Run this on the daemon node. The load balancer uses port 9443, because the daemon node’s own web server already uses 8443.

If you choose Caddy for the load balancer role, you can either use the shipped /opt/webhelpdesk/conf/Caddyfile.cluster-lb template or create an equivalent file like this:

{
 admin off
 http_port 9080
 https_port 9443
}
:9443 {
 tls /opt/webhelpdesk/conf/cert.pem /opt/webhelpdesk/conf/key.pem
 log {
  output file /opt/webhelpdesk/log/caddy-lb-access.log {
   roll_size 50MiB
   roll_keep 10
  }
 }
 reverse_proxy {
  to 10.XX.XX.81:443
  to 10.XX.XX.129:443
  lb_policy cookie whd_lb
  health_uri /actuator/health/readiness
  health_interval 15s
  health_timeout 5s
  health_status 200
  transport http {
   tls
   tls_insecure_skip_verify
   read_timeout 300s
   write_timeout 300s
  }
  header_up Host {hostport}
  header_up X-Real-IP {remote_host}
  header_up -REMOTE_USER
  header_up -X-FORWARDED-USER
  header_up -X-REMOTE-USER
 }
}

Validate it with:

/opt/webhelpdesk/bin/caddy validate --config /opt/webhelpdesk/conf/Caddyfile.cluster-lb --adapter caddyfile

What you should see: Valid configuration.

Keep header_up Host {hostport}. The validator reports it as unnecessary, but it is required when the servers behind the load balancer use HTTPS. Without it, users who open the load balancer are redirected to one individual server’s address – losing the single address, losing failover, and producing a certificate warning.

About tls_insecure_skip_verify: each server made its own certificate during installation, and the load balancer does not trust them. This lets it connect anyway, which is fine on a private network. Install the same CA-signed certificate on every server and you can remove that line.

Start it

sudo -u whd nohup /opt/webhelpdesk/bin/caddy run \
 --config /opt/webhelpdesk/conf/Caddyfile.cluster-lb --adapter caddyfile \
 > /opt/webhelpdesk/log/caddy-lb.log 2>&1 &
sleep 5
sudo lsof -nP -iTCP:9443 -sTCP:LISTEN

What you should see: a line showing Caddy listening on 9443. Note the process number shown – sudo kill <number> stops it.

Confirm it works and sends users to the right place

curl -skI https://10.XX.XX.80:9443/ | grep -iE '^location|^set-cookie'

What you should see:

location: https://10.XX.XX.80:9443/login?returnUrl=%2F
set-cookie: whd_lb=...

The address in location must be the load balancer. If it shows an individual server instead, header_up Host {hostport} is missing from the configuration file created above.

Confirm work is shared between the UI-only nodes

for i in $(seq 1 20); do
 curl -sk -D - -o /dev/null https://10.XX.XX.80:9443/ \
 | grep -i '^set-cookie: whd_lb' \
 | sed 's/.*whd_lb=\([^;]*\).*/\1/'
done | sort | uniq -c

What you should see: two different values, one per UI-only node, in a roughly even split.

If you only see one, check both UI-only nodes are ready first – a node that is still starting correctly receives no traffic:

curl -sk -o /dev/null -w "81=%{http_code}\n" https://10.XX.XX.81:8443/actuator/health/readiness
curl -sk -o /dev/null -w "129=%{http_code}\n" https://10.XX.XX.129:8443/actuator/health/readiness

Both must print 200.

Confirm a server failure does not take the site down

Stop WHD on one UI-only node:

sudo launchctl bootout system/com.solarwinds.whd.backend  # run this ON the UI-only node

Wait about 20 seconds, then on the daemon node:

curl -sk https://10.XX.XX.80:9443/api/v1/public/license/expired; echo
tail -3 /opt/webhelpdesk/log/caddy-lb.log

What you should see: data still returned, and the log noting the stopped server:

"unexpected status code","status_code":502,"host":"10.XX.XX.81:443"

Start it again on the UI-only node:

sudo launchctl bootstrap system /Library/LaunchDaemons/com.solarwinds.whd.backend.plist

After about a minute the log shows it returning by itself:

"unexpected status code","status_code":503,"host":"10.XX.XX.81:443"
"host is up","host":"10.XX.XX.81:443"

502 means stopped, 503 means still starting, host is up means serving again. You do not need to do anything to bring a recovered server back.

Sign in through the load balancer

Open https://10.XX.XX.80:9443, sign in, and click through several pages.

Important – what happens if a server fails while people are signed in. The site stays available. Users are moved to the remaining server automatically, and they do not have to type their password again – their sign-in token is stored in the browser and is accepted by any server.

What they will see is a brief interruption: the page they were on may show the sign-in screen once. Refreshing the page returns them to where they were, without entering credentials. This was confirmed in testing.

Anything unsaved at that moment – a half-completed ticket, an approval being reviewed – may still be lost, so plan restarts for quiet periods.

Step 7 - Upgrade an existing external-database cluster

Use this section when .80, .81, and .129 are already running as a working cluster and you want to move them to a newer PKG.

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

Confirm the current role settings before you touch anything

Run this on every node:

hostname -f
grep -E 'daemon-enabled|liquibase|cluster-' /opt/webhelpdesk/conf/application.properties
sudo launchctl list | grep com.solarwinds.whd

What you should see:

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

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

Copy the new PKG to all nodes

Use the same PKG on all three nodes:

scp /tmp/WebHelpDesk-<new-version>-macos.pkg root@10.XX.XX.80:/tmp/
scp /tmp/WebHelpDesk-<new-version>-macos.pkg root@10.XX.XX.81:/tmp/
scp /tmp/WebHelpDesk-<new-version>-macos.pkg root@10.XX.XX.129:/tmp/

Upgrade the daemon node first

Run on 10.XX.XX.80:

sudo installer -pkg /tmp/WebHelpDesk-<new-version>-macos.pkg -target /
tmp="/tmp/whd-application.properties.$$"
sudo awk '!/^(whd\.node\.daemon-enabled|spring\.liquibase\.enabled|whd\.license\.cluster-secondary|whd\.license\.cluster-primary-url)=/' \
  /opt/webhelpdesk/conf/application.properties > "$tmp"
cat >>"$tmp" <<'EOF'
whd.node.daemon-enabled=true
spring.liquibase.enabled=true
whd.license.cluster-secondary=false
EOF
sudo install -o whd -g whd -m 644 "$tmp" /opt/webhelpdesk/conf/application.properties
rm -f "$tmp"
sudo launchctl unload -w /Library/LaunchDaemons/com.solarwinds.whd.pgsql.plist
sudo launchctl bootout system/com.solarwinds.whd.backend 2>/dev/null || true
sudo launchctl bootout system/com.solarwinds.whd.frontend 2>/dev/null || true
sudo launchctl bootout system/com.solarwinds.whd.caddy 2>/dev/null || true
sudo launchctl bootstrap system /Library/LaunchDaemons/com.solarwinds.whd.backend.plist
sleep 10
sudo launchctl bootstrap system /Library/LaunchDaemons/com.solarwinds.whd.frontend.plist
sleep 3
sudo launchctl bootstrap system /Library/LaunchDaemons/com.solarwinds.whd.caddy.plist
sleep 90
curl -s http://127.0.0.1:8081/actuator/health; echo
sudo launchctl list | grep com.solarwinds.whd.pgsql || echo "inactive"
grep -E 'daemon-enabled|liquibase|cluster-' /opt/webhelpdesk/conf/application.properties

What you should see:

  • backend health UP

  • whd-pgsql still inactive because this cluster uses a shared external database

  • whd.node.daemon-enabled=true

  • spring.liquibase.enabled=true

Upgrade UI-only node 1

Run on 10.XX.XX.81:

sudo installer -pkg /tmp/WebHelpDesk-<new-version>-macos.pkg -target /
sudo /opt/webhelpdesk/stop.sh
test -f /opt/webhelpdesk/conf/.whd.properties || sudo scp root@10.XX.XX.80:/opt/webhelpdesk/conf/.whd.properties /opt/webhelpdesk/conf/.whd.properties
sudo chown whd:whd /opt/webhelpdesk/conf/.whd.properties
sudo chmod 600 /opt/webhelpdesk/conf/.whd.properties
tmp="/tmp/whd-application.properties.$$"
sudo awk '!/^(whd\.node\.daemon-enabled|spring\.liquibase\.enabled|whd\.license\.cluster-secondary|whd\.license\.cluster-primary-url)=/' \
  /opt/webhelpdesk/conf/application.properties > "$tmp"
cat >>"$tmp" <<'EOF'
whd.node.daemon-enabled=false
spring.liquibase.enabled=false
whd.license.cluster-secondary=true
whd.license.cluster-primary-url=https://10.XX.XX.80:8443
EOF
sudo install -o whd -g whd -m 644 "$tmp" /opt/webhelpdesk/conf/application.properties
rm -f "$tmp"
sudo launchctl unload -w /Library/LaunchDaemons/com.solarwinds.whd.pgsql.plist
sudo launchctl bootstrap system /Library/LaunchDaemons/com.solarwinds.whd.backend.plist
sleep 10
sudo launchctl bootstrap system /Library/LaunchDaemons/com.solarwinds.whd.frontend.plist
sleep 3
sudo launchctl bootstrap system /Library/LaunchDaemons/com.solarwinds.whd.caddy.plist
sleep 90
curl -s http://127.0.0.1:8081/actuator/health; echo
grep -E 'daemon-enabled|liquibase|cluster-' /opt/webhelpdesk/conf/application.properties
sudo launchctl list | grep com.solarwinds.whd.pgsql || echo "inactive"
tail -n 300 /opt/webhelpdesk/log/whd-nextgen.log | grep -icE 'EmailTicketScheduledTask|ReportScheduler'

What you should see:

  • backend health UP

  • whd.node.daemon-enabled=false

  • spring.liquibase.enabled=false

  • whd.license.cluster-secondary=true

  • whd-pgsql inactive

  • the last command prints 0

Upgrade UI-only node 2

Run on 10.XX.XX.129:

sudo installer -pkg /tmp/WebHelpDesk-<new-version>-macos.pkg -target /
sudo /opt/webhelpdesk/stop.sh
test -f /opt/webhelpdesk/conf/.whd.properties || sudo scp root@10.XX.XX.80:/opt/webhelpdesk/conf/.whd.properties /opt/webhelpdesk/conf/.whd.properties
sudo chown whd:whd /opt/webhelpdesk/conf/.whd.properties
sudo chmod 600 /opt/webhelpdesk/conf/.whd.properties
tmp="/tmp/whd-application.properties.$$"
sudo awk '!/^(whd\.node\.daemon-enabled|spring\.liquibase\.enabled|whd\.license\.cluster-secondary|whd\.license\.cluster-primary-url)=/' \
  /opt/webhelpdesk/conf/application.properties > "$tmp"
cat >>"$tmp" <<'EOF'
whd.node.daemon-enabled=false
spring.liquibase.enabled=false
whd.license.cluster-secondary=true
whd.license.cluster-primary-url=https://10.XX.XX.80:8443
EOF
sudo install -o whd -g whd -m 644 "$tmp" /opt/webhelpdesk/conf/application.properties
rm -f "$tmp"
sudo launchctl unload -w /Library/LaunchDaemons/com.solarwinds.whd.pgsql.plist
sudo launchctl bootstrap system /Library/LaunchDaemons/com.solarwinds.whd.backend.plist
sleep 10
sudo launchctl bootstrap system /Library/LaunchDaemons/com.solarwinds.whd.frontend.plist
sleep 3
sudo launchctl bootstrap system /Library/LaunchDaemons/com.solarwinds.whd.caddy.plist
sleep 90
curl -s http://127.0.0.1:8081/actuator/health; echo
grep -E 'daemon-enabled|liquibase|cluster-' /opt/webhelpdesk/conf/application.properties
sudo launchctl list | grep com.solarwinds.whd.pgsql || echo "inactive"
tail -n 300 /opt/webhelpdesk/log/whd-nextgen.log | grep -icE 'EmailTicketScheduledTask|ReportScheduler'

Expected result: the same as UI-only node 1.

Confirm the whole upgraded cluster is healthy

for ip in 10.XX.XX.80 10.XX.XX.81 10.XX.XX.129; do
 echo -n "$ip: "; curl -sk -o /dev/null -w "%{http_code}\n" https://$ip:8443/
done

All three should answer.

Then confirm the daemon is still the only node doing background work:

tail -n 300 /opt/webhelpdesk/log/whd-nextgen.log | grep -icE 'EmailTicketScheduledTask|ReportScheduler'
ssh root@10.XX.XX.81 "tail -n 300 /opt/webhelpdesk/log/whd-nextgen.log | grep -icE 'EmailTicketScheduledTask|ReportScheduler'"
ssh root@10.XX.XX.129 "tail -n 300 /opt/webhelpdesk/log/whd-nextgen.log | grep -icE 'EmailTicketScheduledTask|ReportScheduler'"

What you should see: the daemon prints a non-zero number, and each UI-only node prints 0.

If a UI-only node loses its role after upgrade

Re-apply the UI-only role and check again:

tmp="/tmp/whd-application.properties.$$"
sudo awk '!/^(whd\.node\.daemon-enabled|spring\.liquibase\.enabled|whd\.license\.cluster-secondary|whd\.license\.cluster-primary-url)=/' \
  /opt/webhelpdesk/conf/application.properties > "$tmp"
cat >>"$tmp" <<'EOF'
whd.node.daemon-enabled=false
spring.liquibase.enabled=false
whd.license.cluster-secondary=true
whd.license.cluster-primary-url=https://10.XX.XX.80:8443
EOF
sudo install -o whd -g whd -m 644 "$tmp" /opt/webhelpdesk/conf/application.properties
rm -f "$tmp"
sudo launchctl unload -w /Library/LaunchDaemons/com.solarwinds.whd.pgsql.plist
sudo launchctl bootout system/com.solarwinds.whd.backend 2>/dev/null || true
sudo launchctl bootout system/com.solarwinds.whd.frontend 2>/dev/null || true
sudo launchctl bootout system/com.solarwinds.whd.caddy 2>/dev/null || true
sudo launchctl bootstrap system /Library/LaunchDaemons/com.solarwinds.whd.backend.plist
sleep 10
sudo launchctl bootstrap system /Library/LaunchDaemons/com.solarwinds.whd.frontend.plist
sleep 3
sudo launchctl bootstrap system /Library/LaunchDaemons/com.solarwinds.whd.caddy.plist
sleep 90
grep -E 'daemon-enabled|liquibase|cluster-' /opt/webhelpdesk/conf/application.properties

This is safe to run again. The role-setting block is idempotent.

If something goes wrong

What you see

What it means

What to do

BLOCKED when testing the database

The server cannot reach SQL Server

Check firewalls and that SQL Server accepts network connections

Login failed for user in the wizard

Wrong password, or SQL Server only allows Windows logins

Enable SQL logins, re-check the password

CREATE TABLE permission denied

The database user cannot create tables

Give the user owner rights on that database

OUT_OF_SERVICE that never becomes UP

The server cannot use the database

tail -n 50 /opt/webhelpdesk/log/whd-nextgen.log

A UI-only node shows background jobs running

The role was not applied

Re-apply the UI-only property block and restart backend/frontend/Caddy

Everything is slow to start

Normal with an external database

Allow up to 5 minutes

Known limitations

  • The database is a single point of failure.

  • Servers must be started in order. Upgrade and start the daemon first, then let it finish completely before starting any UI-only node. Start UI-only nodes one at a time rather than simultaneously.

  • A server failure briefly interrupts the people using it. The site stays available and users are moved to the remaining server without having to type their password again. They may see the sign-in screen once; refreshing returns them to where they were. Unsaved work at that moment may be lost. Confirmed in testing.

  • Re-check node settings after an upgrade before starting UI-only nodes.

  • macOS does not yet ship a dedicated whd-set-node-role helper for this flow, so role handling is manual on this platform.

  • Do not use /opt/webhelpdesk/start.sh for external-database nodes because it always tries to start the local bundled PostgreSQL service.