Documentation

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 refused

Rule #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.

How updating works

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_SECRET and ENCRYPTION_SALT using 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-upgrades for 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)