# Manual installation

> Install QueryProxy without Docker — requirements, setup steps, and the queue worker that approved queries depend on.

- Documentation: Getting started
- Updated: 2026-10-10
- Source: https://queryproxy.com/docs/manual-installation/
- Language: en-US
- Author: Muhammet Şafak

---
Prefer running QueryProxy directly on a server? It is a standard Laravel
application.

## Requirements

- **PHP ≥ 8.3** with the PDO drivers for the databases you will proxy
  (`pdo_pgsql`, `pdo_mysql`, `pdo_sqlite`; `pdo_sqlsrv` for SQL Server targets)
- **Composer**
- **Node.js 20.19+ or 22.12+** (only to build the frontend assets; this is what the pinned Vite 8 needs)

## Steps

```bash
git clone https://github.com/QueryProxy/QueryProxy.git
cd QueryProxy

composer install
cp .env.example .env
php artisan key:generate

touch database/database.sqlite
php artisan migrate            # add --seed for the demo team
php artisan queryproxy:create-admin   # the first administrator (skip it if you seeded the demo team)

npm install && npm run build
```

Then run the three processes:

```bash
php artisan serve                                                        # the web portal (http://localhost:7432)
php artisan queue:work --queue=queries,default --tries=1 --timeout=310   # the worker; --timeout = QUERYPROXY_EXECUTION_TIMEOUT + 10 (300 by default)
php artisan schedule:work                                                # the scheduler
```

> **The queue worker is not optional.** Approved queries execute on the worker,
> never inside a web request. Without a running worker, requests will be
> approved and then sit in the queue forever. In production, keep it alive with
> a process supervisor (systemd, Supervisor) and set its `--timeout` slightly
> above `QUERYPROXY_EXECUTION_TIMEOUT`.
>
> The `retry_after` warning (at or below the execution timeout) is logged when
> `queue:work` starts, and also for `queue:work --once` and `queue:listen`.
> If you run the worker from the repository's Compose example, its `--timeout`
> comes from the image entrypoint (`QUERYPROXY_EXECUTION_TIMEOUT` + 10); on a
> manual install you set the number yourself.

## Production notes

- Serve the app through PHP-FPM behind a real web server (nginx, Caddy) with
  TLS; `php artisan serve` is fine for evaluation only. The official Docker image
  already runs nginx + php-fpm as a non-root user — mirror that for a manual
  install. When behind a TLS-terminating proxy, set `SESSION_SECURE_COOKIE=true`.
- **Set `TRUSTED_PROXIES` to your proxy's IP or CIDR whenever a proxy sits in
  front of QueryProxy.** No proxy is trusted by default, so until then
  `X-Forwarded-Proto` is ignored (HTTPS is not detected, no HSTS header) and
  audit entries record the proxy's address instead of the client's. Never use
  `*`. Set `APP_URL` to the address users actually reach — the request host is
  pinned to it. See [Configuration](/docs/configuration/).
- **Keep `QUERYPROXY_RESULT_DISK` on a private disk.** QueryProxy refuses to
  write results to a publicly visible disk; result paths are predictable, so such
  a disk would bypass the download authorization entirely.
- **Set `APP_KEY` explicitly and keep it stable across deploys.** It encrypts
  your stored connection credentials and chat secrets; a regenerated key makes
  them unreadable. Rotate keys via `APP_PREVIOUS_KEYS` so existing ciphertext
  still decrypts.
- Point `DB_*` at MySQL or PostgreSQL when SQLite no longer fits — see
  [Configuration](/docs/configuration/).
- The scheduler powers result-retention pruning; run it via
  `schedule:work` or a cron entry calling `php artisan schedule:run` every minute.
- Cache the configuration on deploy: `php artisan config:cache && php artisan route:cache`.
  `TRUSTED_PROXIES` and `TRUSTED_HOSTS` are read through the config, so `.env` is
  enough — re-run `config:cache` after changing them.
