Troubleshooting
Common issues and solutions for self-hosted LinkDen instances.
Troubleshooting
Solutions to common problems for self-hosted LinkDen instances. For deployment steps, see the Cloudflare guide.
Cloudflare Workers
Worker names are linkden (web) and linkden-api (API). Their configuration lives in apps/server/wrangler.jsonc (local dev bindings) and packages/infra/alchemy.run.ts (what actually gets deployed). There is no wrangler.toml.
D1 binding errors
The API Worker can't find the database.
- Verify the D1 database exists:
bunx wrangler d1 list(it is namedlinkden-db) - Check the
DBbinding inapps/server/wrangler.jsoncandpackages/infra/alchemy.run.ts - Redeploy with
bun shipto recreate bindings
R2 bucket not working
Image uploads fail with "Image storage not configured".
- Ensure the
linkden-imagesbucket exists and is bound asIMAGES_BUCKETinpackages/infra/alchemy.run.ts - Verify with
bunx wrangler r2 bucket list
CORS errors on workers.dev subdomains
CORS_ORIGINmust exactly match the web Worker URL, includinghttps://and no trailing slash- Example:
CORS_ORIGIN=https://linkden.your-subdomain.workers.dev
Admin login loops back to the login page on workers.dev
On workers.dev the web Worker (linkden.<sub>.workers.dev) and the API Worker (linkden-api.<sub>.workers.dev) are separate origins. workers.dev is on the Public Suffix List, so the session cookie set by the API is never sent back from the web origin and /admin bounces to /admin/login on every request.
This is expected. workers.dev is a staging target: the public page and /api/health prove the deploy, nothing more.
Fix: set SITE_DOMAIN to your hostname, point BETTER_AUTH_URL, NEXT_PUBLIC_SERVER_URL, CORS_ORIGIN, and NEXT_PUBLIC_SITE_URL at https://SITE_DOMAIN, and redeploy. The web app gets the hostname as a Custom Domain and the API is routed under it at /api/* and /trpc/*, so everything is one origin. See Custom Domain.
Reading API errors
Unhandled errors in the API are logged as one JSON line with the request method, path, and error message. Stream them with:
bunx wrangler tail linkden-api --format=prettyRestore a D1 backup
The backup-db workflow exports the database every day to the linkden-backups R2 bucket as d1/<date>.sql (kept 30 days). To restore one:
# 1. Download the export you want
bunx wrangler r2 object get linkden-backups/d1/2026-08-21.sql --file=backup.sql --remote
# 2. Replay it into the live database
bunx wrangler d1 execute linkden-db --remote --file=backup.sqlThe export contains CREATE TABLE and INSERT statements. Restoring over a database that already has data will fail on duplicate rows, so either restore into a fresh database or run the reset script first (bun reset:factory, then restore). Take a manual export before you start (bunx wrangler d1 export linkden-db --remote --output=before-restore.sql) so you can undo it.
General Issues
CORS errors
Symptom: Browser console shows Access-Control-Allow-Origin errors.
Fix:
CORS_ORIGINmust exactly match the frontend URL (protocol + domain + port, no trailing slash)- Wrong:
http://localhost:3001/orlocalhost:3001 - Right:
http://localhost:3001
Auth cookie issues
Symptom: Login succeeds but you're immediately logged out, or admin pages show "Unauthorized".
Causes:
BETTER_AUTH_URLdoesn't match the actual API URL the browser reaches- Frontend and API are on different origins (the
workers.devcase above) BETTER_AUTH_SECRETchanged between deployments (invalidates existing sessions)
Fix:
- Ensure
BETTER_AUTH_URLmatches the publicly accessible API URL (https://SITE_DOMAINin production) - Set
SITE_DOMAINso web and API share one origin - If you changed
BETTER_AUTH_SECRET, users need to log in again
Email sending failures
Symptom: Password reset or magic link emails don't arrive.
Causes:
- Email provider not configured in admin settings
- API key is invalid or expired
- From email address not verified with the provider
Fix:
- Go to
/admin/settings> Email - Select your provider (Resend or Cloudflare Email Workers)
- Enter a valid API key
- Ensure the "From" email is verified with your provider
- Check spam folders
"Registration is closed" message
Symptom: Trying to sign up returns 403 "Registration is closed".
Explanation: This is expected behavior. LinkDen is a single-user platform. After the first admin account is created, registration is permanently locked. This protects your instance from unauthorized account creation.
If you need to reset and create a new admin account, clear the user table in your D1 database using the D1 console in the Cloudflare dashboard (or bunx wrangler d1 execute linkden-db --remote --command "DELETE FROM user;").
File upload errors
Symptom: Image uploads are rejected.
Possible causes:
- 413 error: File exceeds the 5 MB size limit. Resize or compress the image.
- 400 error (file type): Only image files are allowed: jpg, jpeg, png, gif, webp, ico.
- 400 error (signature): The file's bytes don't match its extension (for example an HTML file renamed to
.png). Re-export the file in a supported format. - 500 error: R2 bucket (Cloudflare) or image storage is not configured. Check your deployment's storage bindings.
Settings validation errors
Symptom: Saving settings fails with a validation error.
Explanation: The settings API only accepts known setting keys. If you're making direct API calls, ensure you're using valid keys. The admin UI handles this automatically.