Troubleshooting
Common errors with their exact error messages and fixes. If your issue isn't listed here, check the server logs first — they almost always contain the real error.
The logs contain the actual error in almost every case. Before anything else:
bash# Docker docker compose logs -f app docker compose logs -f db # Bare-metal / systemd journalctl -u tether -f journalctl -u tether -n 100 # last 100 lines
Installation & startup issues
Error: Can't connect to MySQL server / Connection refused
errorsqlalchemy.exc.OperationalError: (pymysql.err.OperationalError) (2003, "Can't connect to MySQL server on 'db' ([Errno 111] Connection refused)")
What it means: The app container started before MariaDB was ready to accept connections.
Fix: This usually resolves itself — the app retries on startup. If it persists:
bash# Check if the database container is healthy docker compose ps # "tether-db" should show "healthy", not "starting" # If MariaDB is stuck starting, check its logs docker compose logs db # Restart both containers docker compose restart
Docker Compose starts containers in dependency order and waits for the healthcheck to pass, but on first boot MariaDB takes 15–30 seconds to initialise. The healthcheck in docker-compose.yml handles this, but sometimes the first boot takes longer than expected on slow machines.
Error: Access denied for user 'tether'@'...'
errorsqlalchemy.exc.OperationalError: (pymysql.err.OperationalError) (1045, "Access denied for user 'tether'@'172.18.0.3' (using password: YES)")
What it means: The database credentials are wrong, or the MariaDB user was not created correctly.
Fix:
- Verify
DB_PASSWORDin.envmatches what was used to create the MariaDB user - Check the MariaDB user exists:
bash
docker compose exec db mariadb -u root -p -e "SELECT User, Host FROM mysql.user;" - If the user is missing, recreate it:
bash
docker compose exec db mariadb -u root -p -e " CREATE USER IF NOT EXISTS 'tether'@'%' IDENTIFIED BY 'yourpassword'; GRANT ALL PRIVILEGES ON tether.* TO 'tether'@'%'; FLUSH PRIVILEGES;"
In Docker, the app container connects from a different IP each time. The MariaDB user must allow connections from any host (%), not just localhost. Using 'tether'@'localhost' will fail in Docker.
Error: Unknown database 'tether'
errorsqlalchemy.exc.OperationalError: (pymysql.err.OperationalError) (1049, "Unknown database 'tether'")
What it means: The database was never created.
Fix (Docker): The Docker Compose MariaDB service creates the database automatically using MARIADB_DATABASE. If it didn't:
bashdocker compose exec db mariadb -u root -p -e "CREATE DATABASE IF NOT EXISTS tether CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
Fix (bare-metal):
bashsudo mysql -e "CREATE DATABASE tether CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
Error: jose.exceptions.JWTError / Invalid signature
errorjose.exceptions.JWTError: Signature verification failed.
What it means: The SECRET_KEY in .env changed after tokens were issued. All existing sessions are invalid.
Fix: This is expected behaviour — log in again. If you didn't intentionally change the key, check your .env file hasn't been reset to defaults.
Networking & subdomain issues
All subdomains show the MSP dashboard or wrong tenant
What it means: The Host header is not being passed through the reverse proxy correctly. Tether is seeing the proxy's hostname instead of the client's subdomain.
Fix: Ensure your nginx config includes:
nginxproxy_set_header Host $host;
Then reload nginx:
bashsudo nginx -t && sudo systemctl reload nginx
Client subdomain returns 404 or "tenant not found"
What it means: Either the DNS wildcard record isn't set, or the tenant slug doesn't match the subdomain.
Diagnose:
bash# Check DNS resolves to your server dig acme.atechsolutions.org A +short # Should return your server's IP # Check the tenant slug matches # Log in as MSP admin and go to Clients — the slug shown must match exactly # "acme" slug → "acme.atechsolutions.org"
Browser shows certificate error on client subdomains
What it means: Your SSL certificate doesn't cover the subdomain. A standard (non-wildcard) certificate only covers the exact domain it was issued for.
Fix: You need a wildcard certificate covering *.atechsolutions.org. See Reverse proxy & HTTPS for setup instructions.
Login issues
Login fails with "Incorrect email or password"
Possible causes:
- Wrong email or password — check capslock and copy-paste spaces
- Account is deactivated — an MSP admin needs to re-enable it
- You're on the wrong URL — if logging into a client portal, make sure you're at the client's subdomain, not the root domain
Reset password as MSP admin:
- Log in as MSP admin at the root domain
- Go to Users, find the user, click Edit
- Enter a new password and save
Forgot the MSP admin password and can't log in
If you've lost access to the only admin account:
bash# Docker — connect to MariaDB and update the password hash directly # First, generate a bcrypt hash of your new password: docker compose exec app python3 -c " import bcrypt new_password = 'yournewpassword' hashed = bcrypt.hashpw(new_password.encode(), bcrypt.gensalt()).decode() print(hashed) " # Then update it in the database: docker compose exec db mariadb -u tether -ptether tether -e " UPDATE users SET password_hash='PASTE_HASH_HERE' WHERE email='[email protected]';"
Editing the database directly bypasses all validation. Only do this if you have truly lost all admin access. Change the password through the UI immediately after regaining access.
Asset and import issues
POST /api/assets returns 402 Payment Required
json{"detail": "Asset limit reached for your Growth plan (1000/1000 assets). Please upgrade to add more."}
What it means: You are in SaaS mode (DEPLOYMENT_MODE=saas) and have reached your plan's asset limit.
Fix: Either upgrade the plan via PUT /api/billing/tier or delete some assets to make room. If you are running a self-hosted install and don't want limits, change DEPLOYMENT_MODE=selfhosted in .env and restart.
CSV import returns errors for some rows
Common causes and fixes:
| Error message | Cause | Fix |
|---|---|---|
Invalid status 'in use' | Unrecognised status value | Only available, deployed, maintenance, retired are valid |
purchase_cost must be numeric | Cost column contains text | Remove currency symbols and text — use plain numbers like 1499 or 1499.00 |
Unrecognised date format | Date not in an accepted format | Use YYYY-MM-DD for consistency |
asset_tag already exists | Duplicate asset tag in the same CSV | Asset tags must be unique per tenant within one import batch — deduplicate before importing |
Import times out on large files
Imports of 500+ rows can take more than 60 seconds on slower servers, hitting the nginx proxy_read_timeout.
Fix: Split large imports into batches of ~500 rows, or increase the nginx timeout:
nginxlocation /api/assets/import/ { proxy_pass http://127.0.0.1:8000; proxy_read_timeout 300s; # 5 minutes for large imports }
Performance issues
Asset list is slow to load
If the asset list takes more than 2–3 seconds to load, common causes are:
- SQLite with many assets — SQLite is not optimised for large datasets with concurrent access. Switch to MariaDB. See Database setup.
- Missing database indexes — if you upgraded from a very old version, some indexes may be missing. Run:
docker compose restart app— SQLAlchemy recreates indexes on startup. - Server resources — check CPU and memory with
docker statsorhtop.
MariaDB: connection lost after idle period
errorsqlalchemy.exc.OperationalError: (pymysql.err.OperationalError) (2006, 'MySQL server has gone away')
What it means: MariaDB closed an idle connection after wait_timeout (default 8 hours). The connection pool held an old connection that no longer works.
Fix: This is handled automatically by the pool_pre_ping=True and pool_recycle=3600 settings in Tether's database config. If you still see it, ensure DB_HOST is set (not using SQLite) so the pool settings are active. Restart the app container to clear stale connections:
bashdocker compose restart app
Docker-specific issues
Error: port is already allocated
errorError response from daemon: driver failed programming external connectivity on endpoint tether-app: Bind for 0.0.0.0:8000 failed: port is already allocated
Fix: Something else is using port 8000. Either stop the conflicting service or change Tether's port in docker-compose.yml:
yamlports: - "8001:8000" # use port 8001 instead
Error: no space left on device
errorOSError: [Errno 28] No space left on device
Fix:
bash# Check disk usage df -h # Free up Docker space (unused images, stopped containers, build cache) docker system prune # Check the size of Tether volumes docker system df -v
Error: Permission denied on volume mount
errorPermissionError: [Errno 13] Permission denied: '/data/tether.db'
Fix:
bash# Fix ownership on the volume mount point docker compose exec app chown -R app:app /data
Getting help
If your issue isn't covered here:
- Search GitHub Issues — github.com/atechlab-am/tether/issues. Many issues have already been answered.
- Open a new issue — include the full error from your logs, your
DEPLOYMENT_MODE, your database type, and your OS. Without these, it's very hard to help. - Email support — [email protected] for hosted instances or urgent issues.
The most common reason we can't help quickly is missing logs. Always include the full error output from docker compose logs app or journalctl -u tether -n 100 when reporting an issue.