QueryProxy
Documentation menu

Configuration

Every QueryProxy environment variable — query limits, execution, result storage, trusted proxies, the application database and the queue.

Updated:

All configuration is environment variables (.env locally, container environment in Docker). .env.example in the repository documents the full set; this page explains the ones you’ll actually change.

QueryProxy settings

Variable Default Meaning
QUERYPROXY_SELECT_DEFAULT_LIMIT 1000 LIMIT injected into SELECTs that have none
QUERYPROXY_SELECT_HARD_LIMIT 10000 Larger LIMITs are clamped down to this
QUERYPROXY_EXECUTION_TIMEOUT 300 Max seconds a single request may run on the worker
QUERYPROXY_RESULT_DISK local Laravel filesystem disk for result files (s3 supported); a publicly visible disk is refused
QUERYPROXY_RESULT_TTL_DAYS 30 Days before results:prune deletes stored results
QUERYPROXY_SEED_DEMO false Docker only: seed the demo team on first boot (one random password, printed once in the logs; refused under APP_ENV=production unless QUERYPROXY_SEED_DEMO_FORCE=true)
QUERYPROXY_ADMIN_EMAIL / QUERYPROXY_ADMIN_PASSWORD — Docker only: create the first system administrator on boot while the instance has no users (password at least 8 characters); a no-op once any user exists
QUERYPROXY_ADMIN_NAME Admin Docker only: display name for that first administrator
QUERYPROXY_RUN_WORKER true Docker only: run the queue worker inside the container; set false when the worker runs in its own container
QUERYPROXY_RUN_SCHEDULER true Docker only: run the scheduler (schedule:work) inside the container
QUERYPROXY_WORKER_PROCESSES 1 Docker only: number of queue worker processes
QUERYPROXY_KEY_FILE /var/www/html/storage/app/.app_key Docker only: where the generated APP_KEY is persisted when APP_KEY itself is not set. When the variable is unset and a key file already exists at the legacy path /var/www/html/database/.app_key, the entrypoint keeps using that file
QUERYPROXY_REQUIRE_2FA none Enforce TOTP two-factor auth: none, admins, dba (admins plus anyone with a DBA role) or all
QUERYPROXY_CHAT_WEBHOOK_ALLOWED_HOSTS — Extra allowed hosts for outbound chat webhooks, comma-separated, * wildcards allowed (SSRF guard; hooks.slack.com, *.webhook.office.com and *.logic.azure.com are allowed by default)
QUERYPROXY_CHAT_INCLUDE_SQL true Embed a SQL preview in chat notifications
QUERYPROXY_CONNECTION_HOST_DENYLIST — Extra hosts blocked as connection targets (comma-separated)
QUERYPROXY_SQLITE_ALLOWED_DIR — Restrict SQLite connection files to this directory. Files inside the application directory (where QueryProxy’s default SQLite database lives) are always rejected, whether or not this is set
QUERYPROXY_EXTRA_UNTRUSTED_LANGUAGES — Extra procedural languages the guard refuses in CREATE EXTENSION (comma-separated); extends the built-in list. DO, CREATE FUNCTION and CREATE PROCEDURE accept only plpgsql and sql regardless
QUERYPROXY_EXTRA_DANGEROUS_VARIABLES — Extra server variables the guard refuses in SET (persistent scopes on MySQL / MariaDB, every scope on PostgreSQL) and in set_config (comma-separated); extends the built-in list
QUERYPROXY_EXTRA_BLOCKED_FUNCTIONS — Extra functions the guard refuses outright (comma-separated); extends the built-in list
QUERYPROXY_EXTRA_STATE_CHANGING_FUNCTIONS — Extra functions that make a statement a write (comma-separated); extends the built-in list
QUERYPROXY_EXTRA_RESOURCE_CONSUMING_FUNCTIONS — Extra functions that make a statement a write because they hold resources, such as sleeps (comma-separated); extends the built-in list
QUERYPROXY_EXTRA_BLOCKED_PROCEDURES — Extra SQL Server procedures the guard refuses (comma-separated); extends the built-in list
QUERYPROXY_EXTRA_SQLITE_PRAGMAS — Extra SQLite pragmas allowed in their bare, read form (comma-separated); extends the built-in list
TRUSTED_PROXIES — Reverse proxy IPs/CIDRs allowed to set X-Forwarded-* (comma-separated); empty trusts no proxy
TRUSTED_HOSTS — Extra hostnames this installation answers to, beyond APP_URL’s host (comma-separated)
DB_QUEUE_RETRY_AFTER QUERYPROXY_EXECUTION_TIMEOUT + 30 (330) Seconds before a reserved queue job is retried. Derived from the execution timeout when unset; an explicit value wins. BEANSTALKD_QUEUE_RETRY_AFTER and REDIS_QUEUE_RETRY_AFTER work the same way

retry_after is derived: unless you set DB_QUEUE_RETRY_AFTER (or the Beanstalkd / Redis equivalent) yourself, it is QUERYPROXY_EXECUTION_TIMEOUT

  • 30 seconds (330 for the default 300). The Docker entrypoint derives the worker --timeout (execution timeout + 10) and stopwaitsecs (execution timeout + 20) from the same setting. If you set retry_after explicitly, keep it above the execution timeout, because the query job is stopped at that point; the default +30 leaves a margin. A value at or below it lets a slow query be re-reserved while it is still running and then marked failed, even though its write may commit, and logs a warning when queue:work (daemon or --once) or queue:listen starts. A fixed *_QUEUE_RETRY_AFTER=330 left over from the 0.2.0 .env.example stops the derivation, so remove it unless you set it on purpose.

A TOTP code accepted for QUERYPROXY_REQUIRE_2FA is burned, so it cannot be replayed for the rest of its window. One practical consequence: signing in on a second device within the same 30-second step means waiting for the next code.

QUERYPROXY_EXTRA_UNTRUSTED_LANGUAGES and QUERYPROXY_EXTRA_DANGEROUS_VARIABLES add to the two lists the SQL guards judge CREATE EXTENSION and SET by. They can only extend: the built-in entries are the floor, merged back on top of whatever the environment provides, so a trimmed list cannot disarm the guard.

MySQL / MariaDB targets: sql_mode

The guard reads quotes and backslashes the default way, so a request is refused when the session sql_mode would read them differently. Before it runs the SQL on a mysql or mariadb connection, QueryProxy reads @@SESSION.sql_mode; if it contains NO_BACKSLASH_ESCAPES or ANSI_QUOTES, the request fails and nothing runs, with a message naming the flag. QueryProxy itself sets a strict sql_mode when it connects, which replaces the server’s default, so if this fires, look for what changes the mode after connect (a proxy in between, a changed connection configuration); keeping the flags out of the server and connection-user defaults is still sensible, but on its own it will not clear it. Submitting a SET that changes sql_mode is refused on every driver (see SQL guards).

Application key and HTTPS

APP_KEY encrypts your stored connection credentials and chat secrets. For production, set it explicitly and keep it stable — a regenerated key (e.g. on container recreation) makes every stored credential unreadable. Generate one with php artisan key:generate --show, and when rotating, move the old key into APP_PREVIOUS_KEYS (comma-separated) so existing ciphertext still decrypts.

When serving over HTTPS behind a reverse proxy, set SESSION_SECURE_COOKIE=true.

Behind a reverse proxy

Breaking change — set TRUSTED_PROXIES when a proxy sits in front of QueryProxy. Earlier versions trusted every proxy. From this version no proxy is trusted by default, and until you name yours the app reads the connecting peer as the client.

TRUSTED_PROXIES takes a comma-separated list of the IPs or CIDRs allowed to set X-Forwarded-* headers:

TRUSTED_PROXIES=10.0.0.0/8

Until that list names your proxy, X-Forwarded-Proto is ignored — HTTPS is not detected, so no HSTS header is sent — and audit entries record the proxy’s address instead of the client’s.

Never set it to *. Trusting every proxy lets any client send its own X-Forwarded-For, which forges the client IP in the audit trail and hands out unlimited login and 2FA attempts, because both throttles are keyed per IP.

X-Forwarded-Host is not honoured at all, whatever the list says: a forged host header rewrote the link in password-reset mail. As a second layer, the request host is pinned to APP_URL’s host (and its www. sibling), so absolute URLs — signed routes, password-reset mails — always point at your own installation. Set APP_URL to the address users actually reach.

If the installation answers to more than one name, list the others in TRUSTED_HOSTS:

TRUSTED_HOSTS=queryproxy.internal,db.example.com

That is the supported way to run a multi-domain deployment now that X-Forwarded-Host is ignored — a hostname you have not listed still cannot rewrite a password-reset link. The names are added to APP_URL’s host (and its www. sibling), never replace it. Entries that do not parse as a hostname are dropped silently, so a typo cannot take the installation down.

Both values are read through the application config, so setting them in .env is enough. If you cache the configuration, re-run php artisan config:cache after changing them; the Docker image re-caches on every start.

Application database

QueryProxy’s own data (users, teams, requests, audit log) lives in SQLite by default — zero configuration. For MySQL or PostgreSQL, set the standard Laravel variables:

DB_CONNECTION=pgsql        # or mysql
DB_HOST=db
DB_PORT=5432
DB_DATABASE=queryproxy
DB_USERNAME=queryproxy
DB_PASSWORD=change-me

This is the application database — the databases you proxy are configured in the UI under Connections, per team, with encrypted credentials.

Queue

The queue is database-backed by default (no Redis required). To move it to Redis:

QUEUE_CONNECTION=redis
REDIS_HOST=redis

REDIS_QUEUE_RETRY_AFTER is optional: when unset it is derived from QUERYPROXY_EXECUTION_TIMEOUT + 30 seconds (see the note under the settings table). Only set it to override that value, and keep it above the execution timeout.

The worker command stays the same. Keep its --timeout a little above the execution timeout (execution timeout + 10 seconds; QUERYPROXY_EXECUTION_TIMEOUT is 300 by default, hence 310):

php artisan queue:work --queue=queries,default --tries=1 --timeout=310

In a manual setup this number is not derived for you: if you change QUERYPROXY_EXECUTION_TIMEOUT, change --timeout to match (the Docker entrypoint derives it itself).

The same warning (retry_after at or below the execution timeout) is logged once when queue:listen or queue:work --once starts, not only for a daemon queue:work. The short-lived queue:work --once processes that queue:listen spawns do not repeat it.

In the repository’s docker-compose.yml, the separate worker example no longer hard-codes --timeout=310: it passes QUERYPROXY_WORKER_TIMEOUT, which the image entrypoint derives as QUERYPROXY_EXECUTION_TIMEOUT + 10. Set QUERYPROXY_EXECUTION_TIMEOUT on that service and the worker follows. This needs the image’s default entrypoint, so do not override entrypoint: on the worker service.

Result storage on S3

Set QUERYPROXY_RESULT_DISK=s3 and the standard AWS_* variables. Result files are written as NDJSON, streamed — memory use stays flat regardless of result size.

The disk must be private. Result paths are predictable (results/{team_id}/{id}.ndjson), so a publicly served disk would hand out every team’s rows straight from the web root, bypassing the download authorization entirely. QueryProxy therefore refuses to write results when QUERYPROXY_RESULT_DISK points at the public disk or at any disk configured with visibility => public, and the request fails with an explicit message instead.

SQL Server targets

The default Docker image ships pdo_pgsql, pdo_mysql and pdo_sqlite. To proxy Microsoft SQL Server databases, extend the Dockerfile with Microsoft’s ODBC driver and the sqlsrv / pdo_sqlsrv PECL extensions, then build and run your own image in place of queryproxy/queryproxy. The extensions have to be present in the PHP build that runs both the web process and the queue worker, because approved queries execute on the worker.

Arrow keys to move, Enter to open.