Troubleshooting
All commands are for Linux. Run docker compose commands from the deploy/ directory.
Reading the logs
docker compose ps # are all containers up?
docker compose logs -f --tail=200 api # or watcher, sweeper, payouts, caddy
docker compose logs --since=1h watcher | grep -i -E 'warn|error'
Each log line is JSON with a msg and a level. Internal API errors are logged with the request path and a
request_id; the client only sees internal error.
Install and Docker
Containers can't resolve DNS, or
npm cihangs duringdocker build. This is common with Tailscale or a local DNS stub (nameserver 100.100.100.100or127.0.0.53), which containers can't reach. Give Docker real resolvers:echo '{ "dns": ["1.1.1.1", "9.9.9.9"] }' | sudo tee /etc/docker/daemon.json sudo systemctl restart dockerFor a one-off build, pass
--network hosttodocker buildinstead.npm hangs on hosts with broken IPv6:
export NODE_OPTIONS="--dns-result-order=ipv4first --no-network-family-autoselection", or pass it todocker buildas--build-arg NODE_OPTIONS=....go mod downloadfails with permission errors under~/go/pkg/mod: point the module cache somewhere writable, e.g.go env -w GOMODCACHE=/var/tmp/$USER-gomodcache.Published ports don't answer, but
docker compose exec caddy wget -qO- http://localhost/healthzworks: the firewall is blocking host-to-container forwarding. Checksudo iptables -L DOCKER-USERand whether ufw or Tailscale rules drop forwarded traffic.permission deniedon the Docker socket: runsudo usermod -aG docker $USER, then log out and back in.Caddy can't get a certificate: the DNS name must point at this server, and ports 80 and 443 must be reachable from the internet. Check
docker compose logs caddy.
Sign-in
"Too many attempts": login is limited to 10 attempts a minute per IP and per email. Wait a minute.
Two-factor codes rejected: check the server clock (
timedatectl; it should say "System clock synchronized: yes"). Each code works once, so wait for the next code. After five wrong codes, checks are locked for 15 minutes.Lost two-factor device: there is no reset button, on purpose. Someone with server access can clear it from the database, after which the user signs in with their password and enrols again:
docker compose exec postgres psql -U coldtill coldtill -c \ "UPDATE users SET totp_enabled = false, totp_secret = NULL WHERE email = 'user@example.com'"
Deposits
- A deposit isn't showing up:
- Check the network under Nodes & networks: the last processed block should be close to the chain head. If not, the nodes are failing or rate limiting you. Add your own node.
- Check that the token is enabled on that network, and that the customer used the right network.
- Look for
watcherwarnings mentioning the address or transaction.
- A deposit is stuck "confirming": it needs the network's confirmation count. On Bitcoin a block can take an hour or more.
- "evm watcher catching up after downtime" warning: the watcher was down for more than 10,000 blocks. It catches up block by block; that can take a while, but nothing is skipped.
- A Bitcoin deposit was marked orphaned: every node reported the transaction gone (replaced or dropped). The customer should pay again.
Sweeps
- Nothing is being swept: sweeps run when an address holds more than the activation minimum, then on the
amount, address-count or time triggers. Check the hot wallet has gas, and look for
sweepererrors. - "insufficient funds" in the sweeper log: top up the EVM or Solana hot wallet with native coin.
- A wallet is left unswept after a sweep: the token's transfer failed inside the contract (for example a paused or blocklisted token). The funds stay at the deposit address, still locked to your cold wallet.
- A Solana sweep failed after 10 minutes of backoff: see the error in the
sweeperlog; usually the hot wallet lacks SOL, or the RPC node is rate limiting.
Payouts
- Stuck in
pending: is thepayoutscontainer running and did it unlock the hot wallet? Checkdocker compose logs payoutsfor passphrase errors. - Stuck in
initiated: EVM payouts are re-signed with higher fees automatically. Check the hot wallet has enough native coin for the higher fee. needs-review: see Payouts → Needs review.- No approval email: check the
COLDTILL_SMTP_*settings and theapilog. On testnet, codes are printed to theapilog instead.
Webhooks
- All deliveries fail: on mainnet the endpoint must be a public
httpsURL; private addresses are refused. Redirects aren't followed. Use Send test to check. - Signature mismatch: verify against the raw body bytes, before JSON parsing. Frameworks that parse the
body first (for example Express with
express.json()) change it. - Timestamp outside the window: your server clock is off. Enable time sync with
sudo timedatectl set-ntp true.
Auto-convert
- No swap options at checkout: auto-convert is off for the project, the install is in testnet mode, or the provider couldn't quote that amount (very small amounts often can't be swapped).
- A conversion shows
mismatch: read the reason in Conversions. The payment wasn't credited. Check the delivery transaction on a block explorer and contact the provider if funds didn't arrive.
Database
docker compose exec postgres psql -U coldtill coldtill
Migrations run automatically at start-up. If the api container exits with a migration error, restore the
last backup before retrying, and report the error.