1 Troubleshooting
Emily edited this page 2026-11-04 23:26:39 +01:00

Troubleshooting

Startup and configuration

JWT_SECRET is required and must contain at least 32 characters

Set a random secret in .env:

openssl rand -base64 48

Configuration error: PUBLIC_BASE_URL must be a valid http(s) URL

Use an absolute URL with http:// or https:// and no path:

PUBLIC_BASE_URL=https://account.example.com
APP_PUBLIC_URL=https://games.example.com

TRUSTED_PROXIES entry "..." must be an IP address, CIDR range, or ... preset

Use a valid IP, CIDR or one of loopback, linklocal, uniquelocal. Remove hostnames, schemes and spaces inside entries.

The server exits immediately

Check the logs:

docker compose logs accounts

Common causes are a missing JWT_SECRET, invalid URL, invalid proxy entry or database connection failure.

Database

Invalid URL

Older releases built DATABASE_URL by string concatenation. Passwords containing @, :, /, ?, # or % produced an invalid URL.

Release 1.2.1+ builds the URL with encodeURIComponent. Fixes:

  1. Upgrade to the current release.
  2. Prefer POSTGRES_* and leave DATABASE_URL empty.
  3. If you must set DATABASE_URL, percent-encode the password.
# Preferred
POSTGRES_HOST=db
POSTGRES_DB=universal_gaming
POSTGRES_USER=universal_gaming
POSTGRES_PASSWORD=p@ss:word/with?special#chars

password authentication failed for user "..." / code 28P01

POSTGRES_PASSWORD only initializes a new data directory. Changing it in .env does not alter an existing role.

  1. Confirm the container's effective environment:
docker compose exec accounts env | grep -E 'POSTGRES|DATABASE_URL'
  1. Change the role password inside PostgreSQL:
docker compose exec db \
  psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" \
  -c "ALTER ROLE \"$POSTGRES_USER\" WITH PASSWORD '<new-password>';"
  1. Update .env and restart.

Do not delete the PostgreSQL volume or data directory. It contains all accounts, scores and matches.

ECONNREFUSED to db:5432

  • docker compose ps should show db healthy.
  • The accounts service depends on the database health check.
  • If running Node on the host, use POSTGRES_HOST=127.0.0.1, not db.

Migrations fail

  • Read the exact SQL error in the logs.
  • Take a backup before retrying.
  • Do not edit a released migration; add the next version.
  • If the schema is partially changed, restore the pre-update dump and retry.
  • Verify schema_migrations:
docker compose exec db \
  psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" \
  -c 'SELECT * FROM schema_migrations ORDER BY version;'

Accounts or scores disappeared

Check whether the Compose project name or volume changed. Compose uses the project name universal-gaming and the volume postgres-data.

docker volume ls
docker compose ps

Look for an older volume before doing anything destructive. Restore from a dump if needed. Never initialize a fresh database over existing data.

SQLite better-sqlite3 build fails

Install build tools and retry:

npm rebuild better-sqlite3

The Docker image already includes the required build dependencies.

E-mail and SMTP

tls_validate_record_header:wrong version number

Cause: implicit TLS was requested on a STARTTLS port (commonly SMTP_SECURE=true with port 587). OpenSSL tries to parse the plaintext SMTP greeting as a TLS record.

Fix:

SMTP_PORT=587
SMTP_SECURE=false
SMTP_REQUIRE_TLS=true

For port 465:

SMTP_PORT=465
SMTP_SECURE=true

Restart and check the startup log:

[mail] SMTP host=... port=587 secure=false requireTLS=true

Release 1.4.0+ auto-corrects SMTP_SECURE=true on a STARTTLS port and logs a warning, but the environment should still be fixed.

502 on resend verification

The SMTP server rejected or timed out the message. Check:

docker compose logs --tail=100 accounts

Then verify credentials and connectivity from inside the container:

docker compose exec accounts node -e "
  import('./src/config.js').then(async ({loadConfig}) => {
    const {createMailer} = await import('./src/mailer.js');
    const m = createMailer(loadConfig());
    await m.verify();
    console.log('SMTP verify ok');
  });
"

503 This server has no SMTP configuration

Set both SMTP_HOST and SMTP_FROM. Without them the mailer is disabled and verification/reset mail cannot be sent. Accounts still work.

Password reset mail never arrives

  • The account must have an assigned and confirmed e-mail.
  • EMAIL_CONFIRMATION_ENABLED must allow confirmation.
  • SMTP must be configured.
  • The endpoint always returns 202, even for unknown addresses, to prevent enumeration.
  • Check spam and the SMTP provider's logs.
  • Reset links expire after 30 minutes by default.

HTTP and proxying

CORS errors in the web app

Set the exact origin:

CORS_ORIGINS=https://games.example.com
  • Include the scheme.
  • No trailing slash.
  • Multiple origins are comma-separated.
  • * is for development only.

Rate limits trigger for everyone at once

The backend sees the proxy IP instead of the client IP. Set TRUSTED_PROXIES to the proxy address or CIDR:

TRUSTED_PROXIES=127.0.0.1,::1

Ensure the proxy sends X-Forwarded-For. Do not use TRUST_PROXY=true publicly.

Logs show ip=10.x.x.x for every request

Same cause: trusted proxies are not configured, or the proxy is not forwarding the client IP.

413 Payload Too Large

The JSON body limit is 16 KB. Send smaller payloads; this limit is intentional.

429 Too many attempts

Wait for the window to pass. Limits are listed in API Reference. If legitimate traffic is affected, check proxy/IP configuration before raising limits.

Accounts and sessions

401 Session expired. Sign in again.

The token's auth_version no longer matches the account. This happens after a password reset. Sign in with the new password.

401 Invalid or expired session

  • The token expired.
  • JWT_SECRET changed.
  • The token is malformed or from another server.

409 That username or e-mail address is already registered

Usernames and e-mails are compared case-insensitively. Choose another value or sign in.

Account deletion fails

The request requires the current password:

{ "password": "..." }

A wrong password returns 401.

Leaderboard

403 The leaderboard is disabled on this server

Set LEADERBOARD_ENABLED=true and restart.

400 Unknown leaderboard game

Use one of the ten ranked IDs:

sudoku, killer-sudoku, mahjong, minesweeper, klondike, classic-solitaire, spider, freecell, pyramid, tripeaks.

A new score did not appear

Only the best score per user, game and mode is stored. A worse time or move count does not replace the existing row. Check the improved field in the response.

Chess

403 Online chess is disabled on this server

Set CHESS_ENABLED=true and restart.

409 It is not your turn

Refresh the match. The client may be showing stale state.

409 The match changed while your move was being saved

Two writes raced. Fetch the match again and retry.

The server replays the full history with chess.js. The move is illegal in the stored position, or the client is out of sync.

Releases and updates

404 Release proxy is disabled

Set UPDATE_ENABLED=true and restart.

502 Release source returned ...

The upstream Gitea release feed is unreachable or returned an error. Check UPDATE_SOURCE_URL and outbound network access.

App shows an old version

  • Check https://games.example.com/version.json and latest.json.
  • The release proxy caches for UPDATE_CACHE_SECONDS (default 300).
  • The browser may cache the service worker; hard-refresh or clear site data.

Management panel

/management returns 404

MANAGEMENT_PASSWORD is empty. Set it and restart. The panel is intentionally disabled without a password.

/management keeps asking for a password

Use MANAGEMENT_USER (default admin) and the exact MANAGEMENT_PASSWORD. Basic auth has no logout; close the browser or use a private window.

Containers and disk

no space left on device

df -h /
docker system df
docker image prune -f

Prune old images, not volumes. Database dumps and images are the usual consumers.

Container restarts repeatedly

docker compose ps
docker compose logs --tail=200 accounts
docker compose logs --tail=200 db

Check configuration, database health and migrations before restarting in a loop.