# Troubleshooting Common issues and how to fix them. If your problem isn't listed here, [open an issue](https://github.com/OmniRise/LinkBreeze/issues). --- ## Docker Desktop not running (Windows) **Symptom:** `failed to connect to the docker API at npipe:////./pipe/dockerDesktopLinuxEngine` Docker Desktop hasn't started yet. Open it from the Start menu, wait for the whale icon in the system tray to turn green (30–60 seconds on first boot), then retry your command. If the whale never turns green: open Docker Desktop → Settings → ensure the WSL2 backend is enabled. --- ## PowerShell command syntax errors **Symptom:** `Missing expression after unary operator '--'` PowerShell does not support `\` for line continuation (that's bash syntax). Either run the `docker run` command as a single line (recommended): ```powershell docker run -d --name linkbreeze --restart unless-stopped -p 3000:3000 -v linkbreeze-data:/app/data docker.io/omnirise/linkbreeze:latest ``` Or use backticks `` ` `` instead of backslashes for multi-line: ```powershell docker run -d ` --name linkbreeze ` --restart unless-stopped ` -p 3000:3000 ` -v linkbreeze-data:/app/data ` docker.io/omnirise/linkbreeze:latest ``` --- ## Forgot admin password or username LinkBreeze v1 is single-user and self-hosted — there is no self-service password reset (no email server, no SMTP). Pick one of these methods. First, find your container name (it isn't always `linkbreeze` — compose projects and platforms like TrueNAS name it differently): ```bash docker ps --format '{{.Names}}\t{{.Image}}' | grep -i linkbreeze ``` If you run it via docker compose, `docker compose ps` from the compose folder works too. Replace `` in the commands below with that name. ### Option A — Reset the password hash (keeps all data) ```bash # Replace "newpassword" with your desired password docker exec -it node -e " const Database = require('better-sqlite3'); const { hash } = require('@node-rs/argon2'); const db = new Database('/app/data/linkbreeze.db'); hash('newpassword', { memoryCost: 19456, timeCost: 2, parallelism: 1 }).then((h) => { db.prepare('UPDATE users SET password_hash = ?').run(h); console.log('Password updated successfully'); }); " ``` Note: passwords are hashed with Argon2id. If you plant an old-style bcrypt hash (e.g. from a pre-upgrade backup), it still verifies and is automatically upgraded to Argon2id on the next successful login. If you also forgot the username, list it first: ```bash docker exec -it node -e " const db = new (require('better-sqlite3'))('/app/data/linkbreeze.db'); console.log(db.prepare('SELECT username FROM users').all()); " ``` ### Option B — Delete the volume and re-setup (loses all data) ```bash docker stop && docker rm docker volume rm linkbreeze-data # Re-run the docker run command — the setup wizard will appear ``` ### Option C — Running without Docker ```bash sqlite3 data/linkbreeze.db "DELETE FROM users;" npm run dev # Visit the app → setup wizard appears ``` --- ## Port 3000 already in use **Symptom:** `EADDRINUSE: address already in use 0.0.0.0:3000` Either stop the other process or map to a different port: ```bash docker run -d --name linkbreeze -p 8080:3000 -v linkbreeze-data:/app/data docker.io/omnirise/linkbreeze:latest ``` --- ## Database is locked / corrupted Stop the container, back up the database file, then restart: ```bash docker stop linkbreeze cp ./data/linkbreeze.db ./data/linkbreeze.db.bak docker start linkbreeze ``` If it won't start after that, delete the database and re-run setup: ```bash docker stop linkbreeze && docker rm linkbreeze docker volume rm linkbreeze-data # Re-run the docker run command ``` --- ## Analytics not tracking 1. Check that the public page loads at `http://localhost:3000/your-slug` 2. Open browser DevTools → Network tab → look for a `POST /api/track` call 3. If it returns 429, you're being rate-limited (10 requests / 10 seconds per IP) 4. If retention is set to a low number, old data may have been pruned automatically --- ## Changes not showing on public page The public page is cached. If you changed something in the admin panel and it isn't reflected on the public page, hard-refresh: `Ctrl+Shift+R` (or `Cmd+Shift+R` on Mac). In Docker, ensure the container was restarted after any environment variable changes. --- ## Upgrading LinkBreeze ### Docker ```bash docker compose pull && docker compose up -d ``` That's it. New versions pull automatically. ### What happens to my data on upgrade? **Nothing destructive.** LinkBreeze uses Drizzle ORM migrations that run **automatically on container startup**. When you pull a new image and restart: 1. The app checks for pending schema migrations 2. Applies them forward-only (no destructive operations without backup) 3. Your links, settings, analytics, and themes are preserved You never need to run `drizzle-kit migrate` manually in Docker — it's handled for you. If a migration ever fails, the old database is left untouched and the container logs will show the error. ### Running without Docker ```bash git pull npm install npx drizzle-kit migrate # apply any new migrations npm run build && npm start ``` --- ## How slugs work Your public page lives at `/`. The default slug is `u`. - **Changing the slug**: Go to Settings → Page URL. The slug must be alphanumeric with hyphens/underscores, max 64 characters. - **After changing**: The old slug immediately returns a 404. There are **no redirects** — update any links or QR codes pointing to the old URL. - **Reserved words**: `/login`, `/setup`, `/dashboard`, `/links`, `/profile`, `/settings`, `/theme`, `/pages`, `/domains` are admin-reserved and cannot be used as slugs. - **QR codes**: QR codes are generated for the current slug. If you change your slug, download a new QR code from the admin panel. ## Custom domains not working Full setup guide: [docs/custom-domains.md](docs/custom-domains.md). ### Domain shows 404 or the wrong site 1. **DNS**: the domain needs an `A`/`AAAA` record (or `CNAME`) pointing at the machine running LinkBreeze. Check with `dig jane.com` or the **Check DNS** button in Admin → Domains. 2. **Reverse proxy**: every bound host must route to the LinkBreeze container, and the proxy must pass the original host through (`proxy_set_header Host $host;` in nginx). If the proxy sends its `server_name` instead of the visitor's host, host routing cannot match. 3. **Binding**: the domain must exist in Admin → Domains. A binding shows **Pending** until its first real request arrives — visit the domain once (or press **Activate now** to test before DNS propagates). ### TLS / certificate errors LinkBreeze never terminates TLS — the reverse proxy does. You need a wildcard certificate (e.g. `*.example.com`) or per-domain certificates. Caddy automates this (see `docs/custom-domains.md` for a working config); with nginx use certbot's DNS-01 challenge for wildcards. ### Admin, login, or setup returns 404 on my domain That is the **admin host lock** working as designed. Admin surfaces only answer on the primary host — the one configured in Admin → Domains → Routing policy or the `LINKBREEZE_PRIMARY_HOST` environment variable (env wins). Visit your primary host to log in. To disable the lock entirely, set Admin host lock to **Off** — not recommended for public deployments. ### www.jane.com does not work By default `www.` redirects to the bound domain. If it doesn't, either bind `www.jane.com` explicitly in Admin → Domains, or check that your DNS record and reverse proxy cover the `www` subdomain. The behavior is configurable in Routing policy → www handling. ### The same page is reachable at /slug and the domain With the default canonical strategy (**301**), slug URLs on the primary host redirect to the domain automatically. If your edge proxy makes 301s impossible, switch the canonical strategy to **Canonical tag only** — the page then emits `` pointing at the domain root instead. ## SSO (OIDC) sign-in not working LinkBreeze's SSO (#124 multi-user) talks to any standard OpenID Connect provider (Authentik, Keycloak, Authelia, Pocket ID...) via discovery - no per-provider configuration. **Setup checklist:** 1. In your identity provider, register a confidential client (authorization code flow + PKCE) with redirect URI `https://YOUR-LINKBREEZE-HOST/api/auth/oidc/callback`. 2. Set the client secret as the `OIDC_CLIENT_SECRET` environment variable on the LinkBreeze container and restart it. The secret is never entered in the admin UI - settings are exported verbatim into backup files. 3. In LinkBreeze, open **Settings → Security → Single sign-on** and fill in the issuer URL (e.g. `https://idp.example.com/application/o/linkbreeze`) and the client ID. LinkBreeze fetches `/.well-known/openid-configuration` from that issuer. **Common failures:** - The login page shows no SSO button → issuer or client ID is empty, or `OIDC_CLIENT_SECRET` is not set. All three are required. - "Could not reach the discovery document" → wrong issuer URL, or the LinkBreeze container cannot reach the identity provider (check egress and DNS). In production the issuer must be **https** - `http://localhost` issuers only work for local development instances. - Login redirects back with an error immediately → the redirect URI registered at the provider must match `https://YOUR-HOST/api/auth/oidc/callback` byte-for-byte (scheme, host, path). Behind a proxy that rewrites `Host`, set `LINKBREEZE_TRUST_X_FORWARDED_HOST=true` (only behind a proxy you control that OVERWRITES the header). - "This SSO identity is already linked to another account" → the identity provider account is bound to a different LinkBreeze user. Unbind it there or use that account. - JIT accounts don't appear → JIT provisioning is off (Settings → Security), or an admin disabled the account after creation (disabled accounts cannot sign in via SSO either). - Locked out with SSO-only mode? Set the mode back to "SSO alongside password sign-in" - or clear the `oidcMode` key in the `settings` table of `data/linkbreeze.db` - then sign in with the local admin account.