Troubleshooting
the usual problems and their fixes.
Start with what is running and what the server log says. Most problems are DNS, ports, the clock, or an IP whitelist that does not match.
01First look
cd kestrel
docker compose ps # is everything Up / healthy?
docker compose logs --tail 100 server # the server log
docker compose logs --tail 100 web # Caddy: HTTPS and proxy
curl -s https://your-domain/health
The console's Status page shows the database, the trading worker, exchange reachability and each integration (Status).
02Install and HTTPS
HTTPS does not work, or there is a certificate error
Ports 80 and 443 must be open to the internet, in your VPS provider's firewall panel and in ufw (sudo ufw allow 80,443/tcp). The DNS A record must point to this server. Certificates are requested on the first visit and can take a minute. Check docker compose logs web.
Port 80 or 443 is already in use
Another web server (Apache, nginx) is running. Stop and disable it: sudo systemctl disable --now nginx apache2, then docker compose up -d.
The build stops with "Killed"
It ran out of memory. Add swap: sudo fallocate -l 2G /swapfile && sudo chmod 600 /swapfile && sudo mkswap /swapfile && sudo swapon /swapfile, or use a VPS with more RAM, then run ./install.sh again.
"The server did not become healthy"
The installer prints the last log lines. The most common causes: KESTREL_MASTER_KEY was edited by hand and no longer decodes to 32 bytes, or DATABASE_URL points somewhere unreachable. Fix .env and run docker compose up -d.
I cannot find the console setup code
docker compose logs server | grep -A3 "setup code". If the log was rotated, restart the server (docker compose restart server): each start before the owner exists prints a new code. Once an owner exists no code is printed.
03Cloudflare
Live prices or bots show "disconnected" behind Cloudflare: in Cloudflare, Network, make sure WebSockets is on. With the orange cloud (proxied), set SSL/TLS to Full (strict). If it still fails, use "DNS only" (grey cloud). Behind Cloudflare the server sees Cloudflare's IP for rate limits and the audit log unless you raise TRUST_PROXY_HOPS, so keep DNS only if you can.
04Exchange keys and orders
"IP not allowed" or "invalid signature"
The IP in the exchange whitelist does not match https://your-domain/api/server-ip. If your provider changed the IP, update every key's whitelist and SERVER_PUBLIC_IP.
The key is refused: "can withdraw"
Create a new key with withdrawals off (on Binance also Enable Internal Transfer off). See Exchange API keys.
"Could not verify this key's permissions"
The server could not read the permissions with certainty and refuses the key to be safe. Create a plain system-generated trade-only key. On Bitget, a key with an unfamiliar permission is refused.
hedge_mode_unsupported
Switch the futures account to one-way position mode on the exchange, then add the key again.
A bot or account shows "error"
The exchange rejected the key (revoked, expired, IP changed). Fix the cause and use Rotate key; that clears the error at once (Watchdog).
"rate limited" or the account is backing off
The exchange asked the server to slow down. The account backs off for up to 5 minutes and recovers by itself. Many bots on one account and many backtests make it more likely.
An order is skipped "below the minimum"
Each exchange has a minimum order amount and value per market. Raise the bot's order size, or use fewer grid lines or safety orders.
Orders are refused with kill_switch_on
The kill switch is on (perhaps turned on by the risk guard or a panic). Check Safety, then turn it off on the Home screen.
05The app
"Cannot reach the server"
Check the address on the sign-in screen or in Settings, Server, and press Test connection. The mobile apps need the https address of your domain. A build without --dart-define=KESTREL_API_URL=… points to http://localhost:8080.
Two-factor codes are always rejected
The server's or the phone's clock is wrong. On the server: sudo timedatectl set-ntp true, then timedatectl must say "System clock synchronized: yes".
Telegram, push, email or the AI assistant is missing in the app
A feature shows only when the operator switched it on (console, Features) and configured it (Integrations). Check the console's Status page.
"Not available in this build: Firebase is not configured"
The app was built with the stub firebase_options.dart. Run flutterfire configure and rebuild (Push).
The web app does not show the new version after an update
Reload the page. Caddy serves the app shell with Cache-Control: no-cache; a browser that installed the app may need one more reload for the service worker to update.
The journal is missing old trades
History starts with what the exchange returns on the first read; some exchanges return only recent fills. Press Rebuild from the exchanges for the latest minute.
06Changed the domain
Edit PUBLIC_URL, SITE_ADDRESS, CORS_ORIGINS, DOMAIN (and ACME_EMAIL) in .env, then run ./update.sh so the web app is rebuilt with the new address. Signal bot webhook URLs change with PUBLIC_URL: update them in TradingView. Rebuild the mobile apps if they used the old address.
07Stopping and starting
docker compose down stops everything and keeps the data. docker compose up -d starts it again. Never run docker compose down -v unless you want to delete the database.