Repository navigation
Troubleshooting
Real failures and what to do about them, grouped by symptom. If something behaves oddly but consistently, check Tips and Pitfalls first — most "this is broken" reports turn out to be documented behaviour listed there.
Settings → Debug has runtime logging toggles that need no restart and no environment variable:
-
ServerQuery Debug logs every command sent to your TeamSpeak server and the raw response or error. This is the single fastest way to find out why almost anything isn't working — it shows the actual command and TeamSpeak's actual answer, error code and all. Credentials in commands and answers (passwords, API keys, privilege keys, file-transfer keys and similar) are written as
***in this log; what the pages and bot flows receive is not affected. - Voice Bot and Rank Check logging cover the music bots and automatic ranking respectively.
Read the output with:
docker compose logs -f backend
Almost every section below is quicker to diagnose with ServerQuery Debug on.
The clearest signal is Settings → Connections → Test Connection, which now shows the real reason, not just success/fail. Match it to one of these:
-
ECONNREFUSED, or "connection refused / reset". Nothing is listening where the manager is looking. Three usual causes: WebQuery isn't enabled on the TeamSpeak server, the port is wrong (WebQuery defaults to10080, not the client port9987), or "Host" isn't reachable from inside the backend container. That last one catches almost everyone running in Docker:localhostmeans the backend container itself, not your TeamSpeak server — see Setup and Tips and Pitfalls. - "invalid apikey" (error 5122). The stored API key is wrong or has been rotated. Re-copy it from the TeamSpeak server and save it again. The server itself being up doesn't mean the key still matches — keys change independently.
-
The connection is accepted, then immediately reset, and TeamSpeak logs nothing. The server's query IP allowlist is rejecting the manager's address. A connection arriving through Docker's published-port NAT appears to come from the Docker bridge gateway (typically
172.17.0.1), not127.0.0.1, and the server drops it before it gets anywhere. Add the manager's real source range to the allowlist — this is covered in detail for both local and remote setups in Setup: elsewhere / rented. - "The query connection for this server is not active". The manager answers this (HTTP 409) when a page or a bot flow action needs WebQuery on a connection that has none: the connection is disabled, or it was added without an API key. For a connection without a key the pages that need WebQuery are hidden in the sidebar, so you meet the message through a bookmark or a flow action. Enter the key under Settings → Connections → Edit, or enable the connection. Music bots keep working without a key (what differs).
-
ENOTFOUND/ DNS errors. The hostname doesn't resolve from inside the container. Use an address the backend can actually resolve, or the container's gateway. A music bot reports the same thing as "Cannot resolve TeamSpeak server host" (see below).
These are the two things that need SSH ServerQuery, not just WebQuery (why). A cron- or webhook-triggered flow that works while an event-triggered one doesn't is the giveaway that SSH is the problem.
-
ECONNREFUSEDon the SSH port. SSH ServerQuery isn't enabled on the TeamSpeak server, or the port is wrong (defaults to10022). - "All configured authentication methods failed". The SSH username or password is wrong. Note these are the ServerQuery login credentials, which are separate from the WebQuery API key — having a working WebQuery connection tells you nothing about whether SSH will authenticate.
- It worked, then stopped, and now even correct credentials fail. Repeated failed ServerQuery logins can trip the TeamSpeak server's own query ban. Stop the manager from retrying, clear the ban on the server, fix the credentials, and only then reconnect — otherwise each failed attempt re-arms the ban. This is a server-side protection, not something this app can override.
Work down this list — it's ordered by how often each is the answer:
- Is the flow enabled? A new flow is disabled until switched on (Tips).
- Open the flow's execution log. Every run is recorded per node: Executions in the flow editor's toolbar lists the last 50 runs, and a click shows a run's log (Bot Flows). No entry at all means the trigger never fired; an entry marked dropped means it fired while the flow was already running its maximum number of executions. It tells you whether the flow even fired (a trigger problem) or fired and an action failed (an action problem) — the single most useful split.
- Turn on ServerQuery Debug and run it again. An action that "does nothing" almost always shows a real TeamSpeak error there — a permission refusal, a bad channel ID, an out-of-scope identity.
- "out of scope" errors mean the Bot Identity's key was issued with too little access. Settings → Connections → Bot Identity → Fix Permissions reissues it (Tips).
-
A placeholder renders blank — usually the WebQuery-result-is-a-list gotcha: use
{{temp.name.0.field}}, not{{temp.name.field}}(Tips).
-
The bot won't join. It's a real TeamSpeak client connection, so it needs the voice port (
9987/udp) reachable and a free slot. The bot's card on the Music Bots page says what it is doing and why it failed (frontend 3.67 and later; see Music Bots); turn on Voice Bot logging (Settings → Debug) to see the connection attempts themselves. - "connection failed, you are banned (flood prevention, please try again later)" — but nobody is banned. TeamSpeak answers its own flood protection with the same error as a real ban, and the text in brackets tells them apart (a real ban says "You are banned temporarily" or "permanently", with the reason and the time left). Nothing shows up in the ban list. With the default limits the server lets only about three new voice connections from one IP address through in a row, and every refused attempt counts against the limit too, so with four or more music bots the later ones are refused after a backend restart. From backend 2.52.2 such a bot waits and tries again by itself (30 seconds, then 60, 120, 240 and at most 5 minutes), and the bots of one server take their turns; before that it retried every few seconds and kept the limit exceeded. From backend 2.53.0 the card shows the waiting with a countdown, and after the 10th failed retry the bot stops for good (the card says Failed; Start tries again, counting from one). A real ban, a wrong server password or a full server is final at once instead: the bot stays stopped, and Start works again once the cause is fixed. To get a stuck bot in at once, open Advanced Settings (the Anti-Flood card of the virtual server) and either raise Points to Block Connections above what the counter has reached, or set Points Reduced for Being Good to a large value for a minute (5000 was enough in a test) so the counter drains, then Start the bot and put the values back — a high value leaves the protection practically switched off. A server group with the Ignore Antiflood permission does not change this; it only covers a bot's commands (Music Bots).
- The bot drops out in the middle of a track, and only when the server is entered as a hostname. Before backend 2.51.3 the hostname was looked up again for every voice packet, about 50 times a second for each playing bot. A resolver that can't keep up — typically the one inside a container — then fails a lookup and the bot's connection falls over, while the same setup with an IP address is fine. The hostname is now looked up once per connection, so update; if you can't yet, enter the server's IP address as the connection's host instead. A bot picks up a changed server IP the next time it connects, so restart it after the address behind a name changes.
-
"Cannot resolve TeamSpeak server host" in the backend log. The name doesn't resolve from inside the container at all (the same cause as the
ENOTFOUNDentry under The connection won't establish). The bot stops, then retries by itself with growing pauses (10 times, about four minutes in all). If the name or the container's DNS is fixed within that time, that is enough; after the 10th retry the bot stops for good (backend 2.53.0 and later; the card says Failed and shows this error), and pressing Start once the name resolves brings it back — no need to recreate the bot. - A track fails to play or download. Most often yt-dlp: the source is age-restricted or members-only (needs cookies — Music Bots), or yt-dlp has gone stale as YouTube changed. The backend updates yt-dlp on every start, so a restart often fixes extraction that broke overnight.
-
"cookies stopped working" banner. Cookies rotate and expire; re-upload a fresh
cookies.txtunder Settings → YouTube. -
Library files aren't found. They must be under
MUSIC_DIR; use the Library tab's Scan to pick up files added outside the app (Music Bots). - A bot goes quiet by itself, or a video stream ends by itself. Two optional rules do that, both off until you set them: a bot's Pause when alone switch (Music Bots, backend 2.60.0 and later) and End the stream when nobody is watching under Settings → Streaming (Music Bots, backend 2.58.0 and later). A bot the first rule paused carries on by itself when somebody enters its channel; a stream the second rule ended is started again like any other. Turn the bot's switch off, or set the streaming field to 0, to switch a rule off.
Video is the one feature with a third moving part — the Go sidecar — so it has its own failure modes:
-
"Sidecar health check timeout". The backend can't reach the sidecar. Check the sidecar container is running and that
SIDECAR_URLpoints at it (Configuration). - The stream starts but the player stays black, or nobody sees video. Almost always the sidecar's UDP ports aren't reachable, or the shared volume between backend and sidecar is missing so the sidecar can't read the downloaded file. Both are covered as the single most common streaming failure in Deployment.
- A queued video is skipped. Expected if the link went stale (deleted, region-locked, over the duration limit) — the queue logs it and carries on (Music Bots).
The containers run as unprivileged users (Deployment); what used to work because everything ran as root can show up like this:
-
The backend stops right at the start with
unable to open database file(orpermission denied/EACCESin its log). The data directory is not writable for uid1000: a bind mount, a compose file of your own without thevolume-initservice, or a backup that was copied back in as root. Give the directory to1000:1000once. The named volumes of the compose files in the repository are handled bydocker compose upitself. -
docker compose upfails with a YAML error (could not find expected ':') right after the update'sgit pull. You had changed the frontend's port mapping (or another line the update touched) indocker-compose.yml.git pull --autostashreports the conflict but ends without an error and leaves conflict markers in the file; nothing was stopped or changed. Deployment says how to resolve it. - A reverse proxy or Coolify answers 502 after the update. It still talks to the frontend container on port 80; nginx there listens on 8080 now. Change the port in the proxy's target or in Coolify's domain setting for the frontend. A proxy that talks to the published port 3000 is not affected.
-
The "cookies stopped working" banner appears, or YouTube downloads fail, right after the update, and the cookies come from a file mounted through
YT_COOKIE_FILE. yt-dlp ends such a run with aPermissionErroron that file when uid1000cannot write it (it rewrites the file after every run), even though the download itself worked. Make the file writable for uid1000, or upload the cookies under Settings → YouTube instead. -
volume-initshows asExited (0)indocker compose ps -aor in Coolify. That is expected: it hands the volumes over and ends.
Before every start that would change the database (apply a migration), the backend keeps a copy of the database in data/backups/ (Deployment). To go back to one:
-
Pick the copy. Its name says when it was taken and which backend version was about to start:
…-before-2.71.0.dbholds the data as it was before 2.71.0 first started. - Stop the backend container, so nothing has the database open.
-
Put the copy in place of
ts6webui.dbin the same folder, and delete thets6webui.db-walandts6webui.db-shmfiles next to it: they belong to the database you are replacing. The files have to belong to uid1000; for the named volume of the compose files the nextdocker compose uptakes care of that, for a bind mount you do. - Start the backend version the copy came from, that is, the release before the update. Starting the newer version again re-applies the same migrations (and takes no new copy, because one for that version already exists).
The ENCRYPTION_KEY (or JWT_SECRET) in your .env has to be the one the copy was written with, or the stored TeamSpeak credentials cannot be decrypted.
The backend stops at the start with [db-backup] FATAL: could not back up …. The copy before a schema change could not be written, most often because the volume is full. The database has not been touched and the schema change has not run. Free space on the disk (older copies in data/backups/ can go) and start again.
Two deliberate stops while the backend brings the database up to date at the start; both leave the database untouched and the server not listening:
-
the existing database does not match the expected schema. An existing database is adopted into the migration history only when it already matches what this version expects. If columns or tables differ from that, the start refuses rather than guessing. On a normal update from a released version this should not happen; if it does, restore the pre-update copy (above) and run the version you came from, or have the difference reconciled in a migration before trying again. -
A migration failed to apply (the log names the migration and a database error). The copy taken just before it is in
data/backups/. Fix the cause and start again — the backend will not run against a half-applied schema. If a later start reports that migration as failed and refuses to continue, restore the pre-update copy and go back to the earlier version.
As a last resort, setting the DB_MIGRATE_SKIP environment variable to a non-empty value starts the backend without touching the database. The schema may then not match the running version, so use it only to get in, take a backup or read the logs, never as a normal setting.
The sign-in is limited to 15 attempts per 15 minutes per visitor address. Behind a reverse proxy of your own the backend may take your proxy's address for every visitor, and then all of them share one limit: a few failed logins by one person lock out the rest. Settings → Network shows which address the backend takes for you (it should be your own, not the proxy's); if it is the proxy's, raise TRUST_PROXY to the number of proxies in front of the backend. Deployment explains the number and the risk of setting it too high. The limit counts in memory, so restarting the backend clears it.
The IP bans of the Connection Journal turn an address away from the web interface with a 403 ("Your address is blocked").
-
You banned the address you come in from. The app refuses that on purpose (your own address, this machine and private networks cannot be banned), but it can still happen if your address changed since, or the backend sees another address for you than your browser has. Set
IP_BANS_DISABLED=truein the backend's environment and restart it: nothing is turned away then, and you can lift the ban under the journal's Bans tab. Remove the setting again afterwards (Configuration). -
A banned address still gets in. The backend works out the visitor's address from the proxy in front of it (
TRUST_PROXY). If that number is too low, the backend sees your proxy instead of the visitor, and a ban never matches; Settings → Network shows which address it takes for you. Also note that a ban answers new requests: an event stream (the Query Console's live events) that was open before keeps running until its connection ends. - "Banned" on a TeamSpeak row, but the client can still join. The badge says a ban on that virtual server covers the client. A ban on one virtual server does not cover the others; ban on all of a connection's in the dialog, or on the Bans page.
- A TeamSpeak ban is refused. TeamSpeak's own answer is shown in the message - usually a missing permission to create bans for the account the app uses on that server, or a virtual server that is not running.
- Banning is refused for a private address. Behind a proxy that is not set up right every visitor looks like a private address, and a TeamSpeak server in a container sees the Docker gateway as the address of every client - banning it would shut everybody out. Fix the proxy setting first.
How the page works is described on Files; this section is for when it doesn't.
-
"Could not reach the file-transfer port". Listing works over the SSH query connection, but the bytes travel over a separate TCP port on the TeamSpeak server (
30033by default). It has to be open to the backend - published in Docker, allowed through the firewall (Deployment). - "The upload was rejected by a reverse proxy in front of the app (HTTP 413)", or a larger upload fails while smaller ones work. A proxy in front of the app refuses the request body for being too large. The fix is in that proxy: HTTP 413: upload rejected by a reverse proxy.
-
A file name with umlauts or other non-ASCII characters is refused. Seen on a TS6 server (
6.0.0-beta13.1, Docker, Linux): the server answersfile input/output errorfor such names, in both directions, and its log saysInvalid or incomplete multibyte or wide character; files with such names that were put on its disk directly do not show up in its listing at all. A server started with a UTF-8 locale (LANG=C.UTF-8) accepted them. That is the TeamSpeak server, not this app. - A file with umlauts or other non-ASCII characters in its name uploads without an error, but downloading it says "file not found". Reproduced with the TeamSpeak client alone (6.0.0-beta4.1) on a TS6 server (6.0.0-beta13.1) set up from scratch on Windows: the upload gave no message, the download "file not found". A file uploaded through the manager behaves the same for the TeamSpeak client (where the server accepts the name, the manager itself can still download it). This is a TeamSpeak limitation, not this app, and it is reported in the TeamSpeak forum. Give files that TeamSpeak clients have to fetch a plain ASCII name. The Files page's upload window flags such a name before anything is sent and offers to upload the file under a plain ASCII name instead (ä becomes ae, ö oe, ü ue, ß ss), per file or for all at once; the name can also be kept as it is.
- A download in the TeamSpeak client stays at about 100 % and never finishes. Seen with the TeamSpeak 6 client when a file of the same name already existed in the target folder, for example the original that had just been uploaded. Saved into an empty folder, the same download completed. Move or rename the local file, or choose another folder.
- A folder upload fails for one folder and everything in it. The folders are made first, and a folder that cannot be made takes the files in it with it: they fail with the reason while the rest of the upload carries on. The usual reason is the one above, a name with umlauts or other non-ASCII characters on a TeamSpeak server that cannot store it ("The TeamSpeak server refused this file name"). The upload window's plain ASCII names cover the folder's own name and every name inside it, so choosing them avoids it. The other reason is a file that already has the folder's name in that place ("A file with the name of this folder is in the way"): rename or delete the file, or upload the folder under another name.
- A folder with many small files takes a long time, or the upload window says an upload takes at most 2000 files and folders. Every file is its own request and costs the TeamSpeak server a few query commands, and the files go up two at a time, so hundreds of small files take minutes even on a fast connection (about 3 to 5 files a second on a local test server; a slower line or server is slower still). Pack many tiny files into one archive and upload that, or upload a big folder in parts. The limit of 2000 is the page's own, to keep the progress list usable.
- An empty folder did not arrive. The browser's folder picker (Choose a folder) only reports files, so a folder with nothing in it is only taken along when the folder is dropped on the Files page or on the upload window.
-
A picture has no eye button. The preview is offered for names ending in
.png,.jpg(.jpeg,.jpe,.jfif),.gif,.bmpor.webp, for files from 1 byte up to 10 MB. Anything else, SVG included, is only offered as a download. -
The preview says "This file is not a picture the preview can show". The server recognises a picture by its first bytes and not by its name, and shows PNG, JPEG, GIF, BMP and WebP only. A file called
.pngthat is really something else (a renamed document, HTML or SVG, a cut-off upload) is refused on purpose, and SVG is never shown whatever it is called, because it can carry scripts. Download it instead. - The preview says "This picture is too large to preview". Pictures of up to 10 MB and up to 64 megapixels (about 8000 x 8000) are loaded. A small file that declares an enormous size in its header is refused as well. Download it instead.
- The preview says "The server is busy with other previews". At most four previews run at once, so that they cannot use up the TeamSpeak server's few transfer slots. Wait a moment and press Try again.
- A folder will not download as a ZIP ("This folder can't be downloaded as one ZIP"). The ZIP is made while it downloads and uses the plain ZIP format, so a folder is refused before anything starts when it holds more than 65,534 files and folders, is larger than 4 GiB together, holds more than 10,000 sub-folders or is nested more than 32 levels deep. Download parts of it instead (a sub-folder, or single files). The folder is looked through first, with one query command per sub-folder, so a folder with hundreds of sub-folders takes a while to start (about 13 seconds for 300 on a local test server).
-
A
.ts6m-upload-....partfile in a channel. That is an upload in progress (the app writes to a temp file and renames it once every byte has arrived). One that stays after the app was restarted mid-upload is safe to delete. A folder's ZIP leaves such files out.
What you see. A larger file fails on the Files page with "The upload was rejected by a reverse proxy in front of the app (HTTP 413). Raise its request size limit (nginx: client_max_body_size)." In the German interface the text begins "Der Upload wurde von einem Reverse-Proxy vor der App abgelehnt (HTTP 413)." Small files still go through - with nginx's default, anything up to 1 MB.
What it means. The file travels as one request through every proxy between your browser and the backend, and one of them refused it for being too large before the app saw the file. nginx does exactly that when the body exceeds client_max_body_size, which is 1 MB unless you set it. It is not the app's own limit (FILES_MAX_UPLOAD_MB); that one reads "The file is larger than the maximum upload size of this app."
An update does not fix it: the frontend container's own nginx, which comes with every update, is already set up for uploads. A reverse proxy you run in front of it - typically the nginx that holds your domain and HTTPS - is a separate piece that no update touches.
How to fix it. The details depend on your distribution and on how that proxy is installed, so this is the direction rather than a recipe:
- Find the proxy that faces the internet and holds your domain.
- In it, raise the request-size limit for the upload route (nginx:
client_max_body_size) and, for the same route, turn off request buffering and allow long timeouts. The directives are sketched under Deployment. - Check the configuration and reload the proxy (nginx:
nginx -t, then reload it the way your system does, for examplesystemctl reload nginx). - Upload again.
A proxy other than nginx has an equivalent setting for the maximum request body size; look for it in that proxy's documentation.
Checking without logging in. This sends 2 MB to an upload address that does not exist. The app answers 401 because there is no login, and nothing is stored:
head -c 2097152 /dev/zero | curl -s -o /dev/null -w '%{http_code}\n' -X PUT -H 'Content-Type: application/octet-stream' --data-binary @- https://manager.example.com/api/servers/1/vs/1/files/0/uploads/probe
-
401- the request got all the way to the app, so no proxy on the way refuses it for its size. -
413- a proxy still refuses it. If your compose file publishes the frontend container on a host port (3000in the default one), run the same command againsthttp://127.0.0.1:3000:401there and413on your public address means the proxy in between is the one. -
413even though your own proxy allows large bodies - something further in front of it, such as a CDN or your hosting provider's proxy, has a limit of its own; check its documentation for the maximum request size.
Live events use an SSH query session of their own (why SSH), so many problems are the SSH ones above.
- The Events panel says it needs SSH credentials, and Start listening is greyed out. The server connection has none. Add them in the server settings — the same SSH fields event-triggered bot flows use.
- "TeamSpeak refused the SSH login of this server connection". The SSH username or password is wrong; these are the ServerQuery login, not the WebQuery API key. The backend does not try again for a minute, so that a wrong password does not trigger TeamSpeak's own query ban. Correcting it in the connection's settings lifts that at once.
- "Connection lost - reconnecting..." and it stays there. The backend lost its SSH session to TeamSpeak (the server restarted, or the SSH port is closed) and keeps retrying with a pause that grows up to 30 seconds. Once TeamSpeak answers, it registers everything again by itself.
-
A category is shown as refused. TeamSpeak refused the registration, usually for a missing permission on the SSH login: Bans needs
b_client_ban_list, the other categoriesb_virtualserver_notify_register. - "Another admin is already listening to the chat of channel N". The listener of a virtual server sits in one channel, and TeamSpeak delivers the chat of the channel it sits in. Listen to the same channel, or wait until the other admin has stopped.
- "You already have the most live event streams open", or too many listeners. Every browser tab that listens is a stream: an admin may have four open at once, and a server connection has at most three listener sessions (one per virtual server), because TeamSpeak only lets one address open so many query connections. Stop one you no longer need.
- Events arrive in bursts, or not at all, behind my own reverse proxy. See Deployment.
Open the Connection Journal's Settings tab and look at TeamSpeak servers watched: it lists each virtual server that is being watched, how (live events or every 15 seconds) and how many clients it has right now. What it says tells you where to look:
- The connection is not listed. Recording is off (the master switch on that tab, or Record who joins in Settings → Connections for this connection), or the connection is disabled.
- It is listed as "not watched". It has neither an API key nor an SSH login (a connection that only runs music bots), so there is nothing to ask the server with.
- "The SSH login does not work". The connection has no API key and its SSH login is refused or not reachable. See Event triggers and chat commands don't fire for the usual causes. The journal does not retry a refused SSH login for 15 minutes unless the credentials change, because repeated failed logins can trip the server's own query ban.
- "The server did not answer". The WebQuery connection to it failed the last time it was asked. Nothing is closed because of that: a failure to look is not an end.
-
Rows with the address "unknown". TeamSpeak sent no client addresses; the account the app uses may not hold the permission
b_client_remoteaddress_view. The default server admin has it. - A client you expected is missing. Without an SSH login the server is asked every 15 seconds; a client that joined and left between two questions is not seen. A client the server refused (banned, flood protection) leaves nothing to see at all. ServerQuery clients and this app's own music bots are not recorded unless the switches on the page's Settings tab say so.
-
Far too many rows named
serveradmin. The ServerQuery clients switch is on: every request this app makes to a server is a query client. Turn it off.
The country comes from a database file on your server (how), so first look at the Country lookup card on the page's Settings tab:
- "No database installed". Nothing was downloaded or uploaded yet. Click Download now, or upload a file of your own.
-
The download fails ("The download failed: ..."). The detail names the cause. A refused connection or a timeout means the backend cannot reach
download.db-ip.com- behind a firewall that only lets chosen hosts through, allow that one, or download the file on another machine and upload it instead. "Not published" means DB-IP has neither this month's nor last month's file under the expected name. -
A file you uploaded is refused. It must be a country or city database in MMDB format (
.mmdbor.mmdb.gz); an ASN database or a damaged file is rejected and what was installed stays. - A row shows only a dash. No country is known for it: either the database has no entry for that address (reserved ranges, some new allocations), or the row was stored before a database was installed - those older entries are looked up once the database is there, and the card shows how far that is.
- A city is missing, or wrong. Only the City edition has cities, and rows stored under the Country edition keep their country only. A city worked out from an address is a rough guess in any case.
Server Logs (the page in full) shows the TeamSpeak server's own log, not this app's. You can switch between the virtual server log and the instance log, filter by level and by text, and choose how many lines to load. It's the place to confirm what the server actually saw — for example whether a query client's disconnect was logged as connection lost (an abrupt drop) versus disconnecting (a clean logout), or whether an action the manager sent was accepted. You can also append your own entry at a chosen level, useful for marking a point in time before reproducing something.
For this app's own logs — the backend, the bot engine, the music bots — use docker compose logs -f backend instead.
v1.25, last edited 24.10.2026
Getting started
Using the app
Running it
When something is odd
Contributing