# Manual installation

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

- Documentation: Getting started
- Updated: 2026-09-07
- 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+** (only to build the frontend assets)

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

npm install && npm run build
```

Then run the three processes:

```bash
php artisan serve                                              # the web portal
php artisan queue:work --queue=queries,default --timeout=310   # the worker
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`.

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