Talk Recording on separate subdomains: "No configured signaling secret"and a hidden HTTP 500 "PhpToken not found" (missing php tokenizer on the LinuxServer image)

Posting this because I hit two separate problems getting server-side Talk call
recording to work, and the second one produced an error I could not find
documented anywhere. Maybe it saves someone a few hours - and maybe it’s worth
fixing upstream.

ENVIRONMENT

SYMPTOM

  • Starting a recording in a call immediately fails with:
    “Die Aufnahme ist fehlgeschlagen. Bitte wende dich an deine Administration.”
    (“Recording failed. Please contact your administrator.”)

==================================================

PROBLEM 1 - “No configured signaling secret for https://talk.example.com

Recording container log:
Exception: No configured signaling secret for https://talk.example.com

Failed to start recording

ROOT CAUSE
The aio-talk-recording image builds its signaling URL as:
url = ${HPB_PROTOCOL}://${HPB_DOMAIN}${HPB_PATH}
and HPB_PATH defaults to “/standalone-signaling/” (correct for the AIO / single-domain
layout, where that path is proxied to the signaling server).

But in a multi-subdomain layout the signaling server sits at the ROOT of its own
subdomain, so Nextcloud’s Talk admin setting is just:
https://talk.example.com (no /standalone-signaling/ path)

The recording service looks up the signaling secret by the exact URL Nextcloud
advertises. Its own config had the URL WITH the path, so the lookup for
https://talk.example.com” found nothing → “No configured signaling secret”.

The two URLs MUST be byte-for-byte identical:

  • Nextcloud → Talk admin → “High-performance backend” URL
  • Recording → recording.conf → [signaling-1] url

FIX (subdomain layout)
Set HPB_PATH to empty so the recording URL becomes https://talk.example.com :
environment:

  • HPB_PATH= # override the image default “/standalone-signaling/”

Resulting recording.conf:
[signaling-1]
url = https://talk.example.com
internalsecret = <same as HPB [clients] internalsecret>

(If you instead use the AIO single-domain layout, do the opposite: keep
/standalone-signaling/ on BOTH sides. The rule is simply: both must match.)

Secrets to double-check while you’re in there:

  • recording [signaling-1] internalsecret == HPB signaling [clients] internalsecret
  • recording [backend-1] secret == Nextcloud occ “recording_servers” secret
  • Nextcloud “signaling_servers” secret == HPB [backend-1] secret

==================================================

PROBLEM 2 - HTTP 500 “PhpToken not found” (the undocumented one)

After fixing problem 1, the bot joined the call but recording still failed. This
time the recording container reported a 500 from Nextcloud:

HTTPError: 500 Server Error: Internal Server Error for url:
https://nc.example.com/ocs/v2.php/apps/spreed/api/v1/recording/backend

Nextcloud log (nextcloud.log):
“message”: “Class “PhpToken” not found in file
‘…/apps/spreed/lib/Vendor/CuyZ/Valinor/Utility/Reflection/TokenParser.php’”

ROOT CAUSE
The Talk app bundles the Valinor library, which uses PHP’s PhpToken class to
validate the recording backend request. PhpToken belongs to the PHP “tokenizer”
extension. On the LinuxServer image the php84-tokenizer package is NOT installed,
so PhpToken does not exist and the endpoint 500s.

Verify:
docker exec php -m | grep -i tokenizer # → empty
docker exec php -r ‘var_dump(class_exists(“PhpToken”));’ # → bool(false)

Why this is easy to miss: “tokenizer” is listed neither under REQUIRED nor under
RECOMMENDED PHP modules in the Nextcloud admin manual, so the whole instance runs
fine - every other feature works. Only Talk’s server-side recording path needs it.
I could not find a single public write-up of this exact recording-endpoint error.

FIX (persistent, LinuxServer image)
Install the extension via the LinuxServer universal-package-install mod, chained
after the notify-push mod, so it survives redeploys:

environment:
  - DOCKER_MODS=linuxserver/mods:nextcloud-notify-push|linuxserver/mods:universal-package-install
  - INSTALL_PACKAGES=php84-tokenizer

After redeploy:
docker exec php -m | grep -i tokenizer # → tokenizer
php -r ‘var_dump(class_exists(“PhpToken”));’ # → bool(true)

==================================================

SUMMARY CHECKLIST for server-side Talk recording

HPB running and calls work (browser signaling OK)
Recording [signaling] url == Nextcloud Talk-admin signaling URL (EXACT match)
Recording internalsecret == HPB [clients] internalsecret
Recording backend secret == Nextcloud recording_servers secret
PHP “tokenizer” extension present in the Nextcloud container (PhpToken exists)
Outbound UDP media (Janus RTP range) reachable by the recording bot, or TURN
relay available - otherwise you get an empty/black recording despite “started”

==================================================

POSSIBLE UPSTREAM ACTIONS

  1. LinuxServer nextcloud image: consider shipping php84-tokenizer by default (or
    at least documenting it as needed for Talk recording).
  2. Nextcloud Talk (spreed): consider declaring “tokenizer” as a requirement, or
    catching the missing-class case with a clearer error than a bare 500.

Hope this helps the next person. Happy to provide full logs / configs if useful.