Security Best Practices
QueryGlow connects directly to your production data. Treating it as critical infrastructure is non-negotiable.
Rule #1: Network Isolation
Never expose your database port (5432, 3306) directly to the internet.
QueryGlow should run inside your private network or connect via SSH Tunnel.
# Check your firewall: database ports should NOT be listed
sudo ufw status | grep -E "5432|3306"
# Should return: nothing
# QueryGlow port should also NOT be exposed
curl http://your-server-ip:3000
# Should return: Connection refusedRule #2: Keep Safe Mode Enabled
Safe Mode blocks dangerous queries in the Query Editor and for agents. Leave it ON unless you have a specific reason to disable it.
Blocked by default:
DROP TABLE, TRUNCATE, DELETE/UPDATE without WHERE
Always blocked (can't disable):
DROP DATABASE, SHUTDOWN, file system operations and statements that run commands on the server
Verify: grep QUERYGLOW_ALLOW_DESTRUCTIVE .env → should be false or not set.
Rule #3: Mark Production Connections
Set the environment of every live database to Production. QueryGlow then shows it in red everywhere, the Query Editor asks before it runs any INSERT, UPDATE, DELETE or DDL there (on by default, switchable in Preferences), and deleting rows in the Data Browser needs a typed confirmation.
Rule #4: Check SSH Host Keys and Certificates
QueryGlow pins each SSH server's host key on the first connect. If a connection is later refused because the host key changed, the error shows the pinned and the presented fingerprint. Find out why before you remove the entry from data/ssh-known-hosts.json: a reinstalled server is harmless, anything else may be someone intercepting the connection.
For SSL connections to a database whose certificate comes from a publicly trusted authority, tick "Verify server certificate". Certificates from a private authority (Amazon RDS, Google Cloud SQL) need it off.
Rule #5: Keep Agents on Read-Only Tokens
Give each AI agent its own token, read-only unless it really has to write, with an expiry. Add production connections to a token only on purpose. Every agent call lands in the Activity tab, and you can pause one token or all of them there. Agent Access guide
Rule #6: Keep QueryGlow Current
Security fixes arrive as regular releases. Check with ./queryglow update --check (exit code 10 when a release is available) and apply with ./queryglow update, which backs up first and rolls back if the new version does not come up.
Access Control Layers
Layer 1: Reverse Proxy
Always put QueryGlow behind nginx, Caddy, or Traefik. This handles SSL termination and Basic Auth (htpasswd).
./queryglow install sets this up automatically.
Layer 2: Private Network
For maximum security, don't expose QueryGlow to the public web at all. Use a mesh VPN like Tailscale or WireGuard to reach it.
This adds a second barrier in front of the login.
Credential Storage
QueryGlow stores your credentials encrypted:
- ✓Database passwords, SSH keys and AI keys entered in the app are encrypted with AES-256-GCM (authenticated encryption with tamper detection)
- ✓The encryption key is derived from your
SESSION_SECRETandENCRYPTION_SALTusing scrypt - ✓Credentials are never exposed via API responses
- ✓Modified ciphertext fails to decrypt (GCM tamper detection)
⚠️ Critical: Back up with ./queryglow backup: it stores .env and the app data. Without SESSION_SECRET and ENCRYPTION_SALT, you cannot decrypt saved database passwords.
Rate Limiting
What the installer and the app configure:
General (standalone mode)
60 requests per second per IP at nginx
429 on excess
Ask AI (standalone mode)
30 requests per minute per IP at nginx
Keeps AI provider costs in check
Agent endpoint (both modes)
240 per minute per IP, 2,000 per minute overall
Enforced in the app
Basic Auth itself is not rate limited. Use a strong password and let fail2ban watch the nginx log for repeated 401s.
Database Permissions
Principle of Least Privilege
Avoid connecting with the postgres or root superuser. If a credential leak occurs, damage is limited to what that user can access. These are database users, not QueryGlow accounts: create a connection per user to give people or tasks different rights.
Read-only database user
For connections that only need to query data.
-- PostgreSQL
GRANT SELECT ON ALL TABLES IN SCHEMA public TO viewer;
-- MySQL / MariaDB
GRANT SELECT ON mydb.* TO 'viewer'@'%';Read-write database user
Can modify data but cannot drop tables or alter schema.
-- PostgreSQL
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO operator;
-- MySQL / MariaDB
GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO 'operator'@'%';Host Server Hardening
- Disable Password Auth: Only allow SSH key authentication to your VPS.
- Enable UFW/Firewall: Only allow ports 22, 80, and 443. Block 3000 (QueryGlow internal) and 5432/3306 (DB).
- Automatic Updates: Enable
unattended-upgradesfor operating system security patches. - Fail2ban: Install it for SSH brute-force protection, and let it watch nginx for repeated 401s on the QueryGlow login.
Verify Your Security
Run these commands after deployment:
# 1. HTTPS works (the health check needs no login)
curl https://your-domain.com/api/health# Should return JSON with "status":"ok"
# 2. Auth is required
curl https://your-domain.com# Should return: 401 Unauthorized
# 3. Port 3000 is NOT exposed
curl http://your-server-ip:3000# Should return: Connection refused
# 4. Database ports are closed
sudo ufw status | grep -E "5432|3306"# Should return: nothing
# 5. Safe Mode is ON
grep QUERYGLOW_ALLOW_DESTRUCTIVE .env# Should return: false (or nothing = defaults to false)