Production hardening
A checklist for running QueryProxy on a reachable network, from the application key and HTTPS to read-only connections and what the defaults already cover.
Updated:
QueryProxy stores the credentials to every database it fronts, so treat the instance itself as sensitive. Work through this list before exposing it to a network.
Recommended production setup
- For teams or users who only read, register a separate connection with a
database user that has
SELECTprivileges only. Keep write access on a different connection, since approved writes run through the connection they were submitted on. On SQL Server, which has no read-only session, give the connection a separate read username and password withSELECTonly. - Point that read-only connection at a replica where you can.
- Restrict the network path between QueryProxy and the target database.
- Require TLS on every network connection and verify the server certificate
(
verify-caorverify-fullon PostgreSQL, “Verify the server certificate” on MySQL and MariaDB,TrustServerCertificateoff on SQL Server). TLS is not enforced on connections created before v0.3.0 until you edit them;queryproxy:doctorlists them.
The SQL guard is not a last line of defence. Reads run in a read-only session on the database side, but the privileges of the database user remain the last line, so build the defence with them as well.
Checklist
- Set
APP_KEYexplicitly and keep it stable. It encrypts connection credentials and chat secrets; changing it makes every stored credential unreadable. Generate one withdocker run --rm queryproxy/queryproxy php artisan key:generate --showand pass it as-e APP_KEY=...(every container of the same instance must share it). When rotating, move the old key intoAPP_PREVIOUS_KEYSso existing ciphertext still decrypts. Without an explicitAPP_KEY, the container generates one on first boot and persists it to the data volume. - Serve over HTTPS behind a TLS-terminating reverse proxy (the container
serves plain HTTP on
:7432). Then setSESSION_SECURE_COOKIE=true. - Set
TRUSTED_PROXIESto your proxy’s IP or CIDR whenever a proxy sits in front of QueryProxy. No proxy is trusted by default, so until you set it the app reads the connecting peer as the client:X-Forwarded-Protois ignored (HTTPS is not detected, so no HSTS header) and audit entries record the proxy’s address. Never set it to*: that lets any client forgeX-Forwarded-For, which both fakes the audit trail’s client IP and hands out unlimited login and 2FA attempts, since those throttles are keyed per IP. See Configuration. - Supply secrets as files where your platform provides them: set
APP_KEY_FILE,DB_PASSWORD_FILEand the other*_FILEvariables instead of putting values in the environment. See Configuration. - Keep demo seeding off (
QUERYPROXY_SEED_DEMO=false, the default) on any reachable instance. - Require 2FA for privileged roles with
QUERYPROXY_REQUIRE_2FA=admins(ordba/all). Users are funnelled to enrolment on next login. - If you use single sign-on, enforce MFA at the identity provider. Users who
sign in through SSO are outside
QUERYPROXY_REQUIRE_2FA; QueryProxy cannot see which second factor the provider used. Connect only a provider that verifies e-mail addresses (QueryProxy links and creates accounts only when the provider asserts a verified address:email_verified, orxms_edovfor Entra ID), and considerQUERYPROXY_LOCAL_LOGIN=admins_onlyso that a password stays available to system administrators only, for break-glass access. Signing out of QueryProxy does not sign the user out of the provider. See Single sign-on. - Treat API tokens as credentials. Give each token only the scopes it needs,
and keep
approvals:writefor the few tokens that must decide requests: a leaked reviewer token can approve other users’ requests. LowerQUERYPROXY_API_TOKEN_MAX_DAYS(default90) to the longest lifetime you accept, and revoke tokens of people who change roles. A token cannot approve its owner’s own request or take an override. Token creation and revocation are audited. See REST API and tokens. - On Kubernetes, turn the NetworkPolicy on (
networkPolicy.enabled) and list your ingress controller innetworkPolicy.ingressFrom, so only it reaches port 7432 (your network plugin must enforce NetworkPolicy). List the egress you need withnetworkPolicy.egress.restrict=true. Use your own Secret (secrets.existingSecret) rather than the chart-managed one. See Kubernetes, Helm and high availability. - Prefer time-bound grants for access that is not permanent: a DBA can grant a
connection for a period and at the
readlevel. See Approval workflow. - Keep a copy of the audit log outside the database. Turn on a SIEM sink
(
QUERYPROXY_SIEM_SINKS) so that someone with database access cannot rewrite history unseen. See Audit chain and SIEM export. - Protect
/metricswith a token or an address allowlist, or leave it off (the default). - Run the doctor after every deploy:
php artisan queryproxy:doctor --strict. See Troubleshooting. - Back up the data and the
APP_KEYseparately. See Backup and restore. - Restrict connection targets with
QUERYPROXY_CONNECTION_HOST_DENYLIST/QUERYPROXY_SQLITE_ALLOWED_DIRif DBAs should not reach arbitrary hosts.
What is on by default
No configuration is needed for these:
- Security response headers, including a Content-Security-Policy, and a rate limit on the Livewire endpoint.
- Encrypted connection credentials at rest and TOTP two-factor authentication (optional until you require it).
- Comment-normalized SQL guards with an executor-enforced row ceiling.
- Replay-protected, identity-bound chat callbacks, and self-approval prevention.
- A tamper-evident audit log (hash chain and database triggers), with the SQL shown redacted to Auditors and in chat, and login/webhook rate limiting.
- SSRF-guarded outbound webhooks.
- API tokens that always expire, are stored as a hash and stop working when their owner is suspended or deleted.
- Masked-at-write, fail-closed result storage.
Next steps
- All environment variables: Configuration.
- Who and what QueryProxy defends against: Threat model.
- Non-Docker installs: Manual installation.
- Chat callbacks: Slack integration and Teams integration.