Elyra
Elyra The coding agent eTerm The terminal that knows where each command ends Starf An activity monitor for Apple silicon that never invents a number Litr A small, native web browser for macOS Notr A notebook for macOS e The native code editor Elyra Grove Native local development environment Askr The real server for Laravel & PHP Elyra Framework Rust + Svelte 5 framework for desktop apps Elyra Conductor Local project conductor Refr Local-first PDF workspace for macOS Elyra Workspace A desktop workspace for coding agents Elyra SQL Server MySQL-compatible SQL server in Rust Elyra Félagi Agents as teammates on one board Elyra SQL Client Native desktop SQL workbench Elyra SQL Anywhere Replication-ready SQL engine Elyra Sjá SEO & GEO workspace for macOS Elyra DataGrid Server-driven data grid for Laravel
Release notes
Changelog
Elyra
Configuration

Configuration

Askr can be configured with CLI flags (see CLI) or a typed askr.toml file. The config file is the declarative source of truth — the thing tooling and the admin GUI edit — and is recommended for production.

askr config-check askr.toml     # validate + print resolved settings
askr serve --config askr.toml   # run (the file is authoritative)

When --config is given, the file provides everything; other serve flags are ignored.

A config file is the whole configuration. --config is not merged with the other flags — it replaces them. Askr refuses to start if you pass both, naming the flags that would have been ignored, because the alternative is a server that runs with settings you didn't choose and says nothing. Everything a flag can do has a key here.

askr.toml reference

A complete, commented example lives at examples/askr.toml. An unknown key is ignored with a warning that names it and the key it was probably meant to be — at startup and in askr config-check, so run that after editing the file.

[server]

Key Type Default Meaning
listen string 127.0.0.1:8000 Address to bind.
root path public Document root (the app's public/).
front string index.php Front controller, relative to root.
workers string auto Number of worker processes, or auto (= CPU cores).
max_requests int 0 Recycle each worker after N requests (0 = never).
max_rss int 0 Recycle a worker gracefully once its RSS exceeds this many MB (0 = never). Leak-aware; Linux only.
shadow_to string — Mirror sampled safe requests to this upstream URL for deploy validation.
shadow_sample int 100 Percent of eligible requests to mirror.
max_body_size string 16M Reject larger bodies with 413. K/M/G or plain bytes.
tls_handshake_timeout int 10 Seconds a client may take to finish the TLS handshake (slowloris guard).
header_read_timeout int 15 Seconds a client may take to send request headers (slowloris guard).
https bool false Force HTTPS in $_SERVER (e.g. behind a TLS terminator). Implied by TLS.
force_https bool false Redirect plain HTTP to HTTPS (308), using the connection's TLS state / https / X-Forwarded-Proto.
http_redirect — Answer plain HTTP here and 308 it to HTTPS, e.g. "0.0.0.0:80". Needs force_https. Automatic on the ACME challenge address with --acme.
traffic_log path Record one JSON line per PHP-served request for askr cache-report. A diagnostic — turn it on for an hour, then off.
trusted_proxies list [] Proxies whose X-Forwarded-For may be believed, as IPs or CIDRs ("10.0.0.0/8"). Sets the client identity everywhere: $_SERVER['REMOTE_ADDR'] (since 1.7.1), [[ratelimit]] buckets, and the forwarding headers PHP is shown — X-Forwarded-For collapsed to the client from a trusted peer, and every forwarding header removed from any other. See Behind a reverse proxy.
workers_min int = workers CoW autoscaling floor (with --cow).
workers_max int = workers CoW autoscaling ceiling (> min enables autoscaling).
access_log path — JSON access log per request; - for stdout. Off if unset.
http3 bool false Serve HTTP/3 (QUIC) on the TLS port (requires TLS; build with --features http3).
sandbox bool false Linux hardening: seccomp no-exec. See Sandbox.
sandbox_write path[] [] Landlock: writes allowed only under these paths (enables the FS restriction).
sandbox_required bool false Refuse to serve if the sandbox doesn't fully apply. Needs sandbox_write.

[worker]

Omit this whole section to run in per-request mode. Present it to enable worker mode (boot once, serve many — see Worker mode).

Key Type Meaning
script path Worker script that boots the app and loops.
app_base path Application base path, exported as $ASKR_APP_BASE for the worker script (inherited across fork).
ini string Extra php.ini lines (newline-separated), e.g. to load opcache.
paranoid bool Dev only: detect state bleed between requests (expensive). See Worker mode.

[tls]

Key Type Meaning
cert path TLS certificate chain (PEM). Use with key.
key path TLS private key (PEM).
self_signed bool Generate a v3 self-signed cert on startup (dev). Mutually exclusive with cert/key.

Enabling TLS negotiates HTTP/2 or HTTP/1.1 via ALPN and sets HTTPS=on in $_SERVER (so Laravel emits secure cookies). Certs must be X.509 v3.

Auto-TLS (ACME / Let's Encrypt) has its own section — see [acme]. Do not set both: [tls] is a certificate you supply, [acme] is one Askr fetches.

[acme]

Key Type Default Meaning
enabled bool false Obtain and renew a certificate over HTTP-01.
domains list [] Hostnames to certify. At least one required. Bare hostnames — no scheme, port or wildcard.
email string admin@<first domain> Contact address for the ACME account.
dir path /var/lib/askr/acme Where the account key and certificate are cached. Must survive restarts, or you will hit Let's Encrypt's rate limits.
staging bool false Use Let's Encrypt staging: untrusted certs, far higher limits. Do this first.
directory_url string Let's Encrypt Custom ACME directory (a Pebble test server). Distinct from dir.
http address 0.0.0.0:80 Where HTTP-01 challenges are answered — and, with force_https, where plain HTTP is redirected from.
ca_root path – Extra CA root to trust for the directory. Testing only.
[server]
listen = "0.0.0.0:443"
root = "/var/www/example.com/public"
force_https = true
trusted_proxies = ["172.17.0.1"]   # file-only, which is why [acme] had to exist

[acme]
enabled = true
domains = ["example.com", "www.example.com"]
email = "admin@example.com"
dir = "/var/lib/askr/acme"

One long-lived listener on http answers challenges and 308s everything else, so a certificate can be issued and HTTP redirected without the two fighting over port 80.

Askr refuses to start rather than let a mistake here become a site quietly serving plain HTTP: domains without enabled, enabled without domains, [acme] alongside [tls], and a wildcard domain (HTTP-01 cannot validate one) are all errors.

Until 1.4.10 ACME was flags-only. Since --config is the whole configuration rather than a set of defaults, that made auto-TLS and a config file mutually exclusive — and combinations like "auto-TLS behind a proxy" unreachable, because trusted_proxies has never had a flag.

[admin]

Key Type Meaning
listen string Admin dashboard/API address (e.g. 127.0.0.1:9000). Omit to disable. See Admin.

[queue]

Run queue workers in the same binary, supervised alongside the web workers.

Key Type Meaning
workers int Number of queue-worker processes (0 = off; floor when autoscaling).
workers_max int Autoscaling ceiling. When > workers, the pool scales on backlog (Horizon balance=auto, no extra daemon). Defaults to workers.
script path Queue runner script (e.g. examples/askr-queue.php).
slots int Shared-memory job queue slots (0 = off; 32 KB each) — askr_queue_* + the AskrQueue driver. See Cache.
persist string Name a shared-memory object for the ring so pending jobs survive a restart (1.5.1). Off by default: the object lives in /dev/shm, which containers cap at 64 MiB — see Docker. A ring whose layout no longer matches the binary is recreated, empty, with a log line saying why.
stall_secs int How long a job may sit ready and unclaimed before Askr calls the lane stalled — in the log, in /api/status's warnings, and in askr_queue_unattended. Default 30; 0 keeps the default.
root path Docroot of the application whose jobs these workers consume. Defaults to [server] root. Only needed with [[site]] — see below.

slots is required when workers is set, and Askr refuses to start without it. The ring is only mapped when slots are configured; without it every push returns 0, Laravel does not check that, and queued jobs are discarded silently — which is how a live site stopped sending mail with nothing in any log.

Ten seconds of queue latency is unremarkable; thirty means nothing is listening, which is why stall_secs defaults to 30. Raise it for an app whose queues are deliberately batchy, lower it to be told sooner. One threshold drives the watchdog log line, the warnings array in GET /api/status and the askr_queue_unattended metric together, so a dashboard cannot disagree with the log about whether a lane is stalled — see Admin.

root is the application these workers belong to, and with [[site]] you have to say it. Shared memory is namespaced per application, the namespace is derived from the docroot, and askr_queue_pop matches the namespaced key. A queue sidecar is one process with one namespace for its whole life, so it consumes exactly one application's jobs — set root to the same path as the [[site]] root of the application that dispatches them. If that is the top-level application, set it equal to [server] root: that is a valid and expected answer, it just has to be an answer. Without [[site]] there is only one application and the default is right.

Since 1.7.0 Askr refuses to start when [[site]] is configured together with a queue or scheduler sidecar and neither [queue] root nor [scheduler] root is set — it cannot tell which application the sidecar serves, and the error names the key to set. Guessing is what the release fixes: an instance whose jobs were dispatched by a [[site]] application accepted every job for six days and read none of them, with nothing in any log from the application's side. See Hosting.

[scheduler]

Run the scheduler (built-in cron) in the same binary.

Key Type Meaning
script path Scheduler runner script (e.g. examples/askr-scheduler.php). Omit to disable.
root path Docroot of the application the scheduler runs for. Defaults to [queue] root, then [server] root.

The scheduler sidecar is namespaced the same way as the queue workers and for the same reason. When both run for the same application — the usual case — [queue] root already covers it and there is no second line to write. Set [scheduler] root when the scheduler belongs to a different application than the queue workers, or when an instance runs the scheduler without queue workers at all; it is honoured independently of [queue] root, not merely as a fallback.

[[sidecar]]

Supervise arbitrary external commands (array of tables; respawned if they die). Run via sh -c in $ASKR_APP_BASE. Used for e.g. Inertia SSR — see Docker.

[[sidecar]]
command = "node bootstrap/ssr/ssr.mjs"

[[redirect]]

Declarative host redirects (array of tables), evaluated before any dispatch. from matches the Host header exactly or as a *.suffix glob; the request path + query are preserved; status defaults to 308 (permanent, method-preserving).

[[redirect]]
from = "www.domene.no"
to   = "https://domene.no"     # → https://domene.no/<path>?<query>

[[redirect]]
from   = "*.old.no"
to     = "https://ny.no"
status = 301

For plain-HTTP→HTTPS across all hosts, use [server] force_https = true instead.

[[site]]

Virtual hosts — serve several domains/apps from one Askr instance, routed by the Host header. Each site has its own document root + front controller; hosts match exactly or as a *.suffix glob. A request whose Host matches no site falls back to [server] root.

[server]
listen = "0.0.0.0:443"
root   = "/var/www/default/public"   # fallback

[[site]]
hosts = ["domene.no", "*.domene.no"]
root  = "/var/www/domene/public"

[[site]]
hosts = ["annet.no"]
root  = "/var/www/annet/public"
front = "index.php"

Static files are served from the matching site's root in any mode. Full dynamic dispatch (a different app per host) works in per-request mode — each request runs that site's front controller fresh. In worker mode the single booted app is fixed, so give each app its own instance (or route by host inside the worker script) until per-site worker pools land. Combine with [[redirect]] for per-host www→apex.

[cache]

Enable the shared-memory cache (askr_cache_*, and the Laravel driver). See Cache.

Key Type Meaning
slots int Small kv cache slots (0 = disabled). ~4.3 KB each — counters, locks, small values.
large_slots int Large-value region slots (0 = off). 64 KB each — Laravel sessions, cached fragments/collections.
response_slots int Response cache slots (0 = off). ~140 KB each — full-response edge cache with tag invalidation.
strip_query_params list Query parameters ignored when building the response-cache key. Trailing * globs ("utm_*"). PHP still receives the full query.
ignore_cookies list Cookies that don't make a request non-cacheable (analytics: "_ga", "_gid", "_fbp"). Trailing * globs. Default: any cookie defeats caching.
vary_user_agent bool Split the response-cache key on mobile vs desktop User-Agent (also sets Vary: User-Agent). Default false.
persist path Save the response cache here on graceful shutdown and load it at boot, so a restart doesn't start cold. Unset = off.
persist_key string Release identifier. When set, a saved cache only loads if it matches — set it to your release SHA so a deploy can't resurrect pre-deploy HTML.
saint_seconds int Saint mode: seconds to treat PHP as unhealthy after a 5xx, during which requests holding a stale-if-error entry skip PHP entirely. 0 = off (default).

[[ratelimit]]

Rate limits enforced in the Rust layer before PHP is woken, with token buckets in shared memory (so a limit spans the whole worker fleet). First match wins. See Features.

Key Type Meaning
path string Path glob (*, ?), must start with /. Globs, not regexes.
limit int Requests allowed per window. Must be > 0.
window int Window length in seconds. Default 60.
by string Identity counted: ip (default), header:<Name>, cookie:<name>.
burst int Extra tokens a bursty client may accumulate on top of limit.
[[ratelimit]]
path = "/login"
limit = 5
window = 300

Refused requests get 429 with Retry-After. Reserved /askr/* endpoints are exempt. Set [server] trusted_proxies when running behind a load balancer, or X-Forwarded-For is ignored and every client shares one bucket. The same setting is what gives PHP the client in REMOTE_ADDR — see Behind a reverse proxy.

[[cache.rule]]

Per-path cache policy, applied without touching the app. First match wins — put specific rules above the catch-all. See Features.

Key Type Meaning
path string Path glob (*, ?), must start with /. Globs, not regexes — a regex-shaped pattern is rejected at load.
action string "pass" = never cache this path, even if the app sent Askr-Cache. Responses carry X-Askr-Cache: PASS.
ttl int Fresh seconds. Caches a path the app never opted in to; overrides the app's TTL (the app's tags are kept).
swr int Stale-while-revalidate window, seconds past ttl.
stale_if_error int stale-if-error window, seconds past ttl.
force bool Cache even when the request carries cookies. Dangerous on anything user-specific — one visitor's page is then served to everyone.
[[cache.rule]]
path = "/admin/*"
action = "pass"

[[cache.rule]]
path = "/static/*"
ttl = 86400
force = true

Cache-key normalisation example — tracking parameters and analytics cookies stop fragmenting the cache:

[cache]
response_slots = 512
strip_query_params = ["utm_*", "gclid", "fbclid", "_ga"]
ignore_cookies = ["_ga", "_gid", "_fbp"]
vary_user_agent = false

[broadcast]

Enable askr_broadcast() and the SSE endpoint. See Broadcasting.

Key Type Meaning
enabled bool Turn on the broadcast ring + GET /askr/events.

[pusher]

Pusher-compatible WebSocket + HTTP trigger (drop-in Reverb). Auto-enables the broadcast ring.

Key Type Meaning
enabled bool Turn on the WS endpoint /app/{key} + trigger /apps/{id}/events.
secret string App secret to verify private/presence subscription auth (omit = accept, dev).

[record]

Key Type Meaning
dir path Record failing (5xx) requests here for askr replay. Captures bodies — sensitive; created 0700, files 0600, credentials redacted.

[reload]

Key Type Meaning
canary bool Canary reload: roll one worker and health-check it before rolling the rest.
canary_window int Seconds to watch the canary. Default 5.
canary_min_requests int Requests the canary must serve for a verdict; below this the rollout is inconclusive and continues with a warning. Default 20.
canary_max_error_rate float Percentage points of error rate the canary may exceed the fleet by. Default 2.0.
canary_max_latency_factor float Mean-latency factor vs the fleet. Default 3.0.

Example

[server]
listen = "0.0.0.0:8000"
root = "/var/www/app/public"
workers = "auto"
max_requests = 1000
max_body_size = "16M"

[worker]
script = "/opt/askr/examples/laravel-worker.php"
app_base = "/var/www/app"
ini = "zend_extension=/opt/askr/vendor/php-build/install/lib/php/extensions/no-debug-non-zts-20240924/opcache.so\nopcache.enable=1\nopcache.validate_timestamps=0"

[tls]
cert = "/etc/askr/cert.pem"
key = "/etc/askr/key.pem"

[admin]
listen = "127.0.0.1:9000"

Environment variables

Variable Meaning
ASKR_PHP_INI Extra php.ini lines, appended to the engine defaults. Overridden by --ini / [worker] ini. Commonly used to load opcache.
ASKR_APP_BASE Application base path for the worker script (set automatically from [worker] app_base, or export it yourself in flag mode).
ASKR_PHP_CONFIG Path to a php-config for a specific embed-enabled, non-ZTS PHP install (used at build time).
RUST_LOG Log filter, e.g. askr=debug. Default askr=info.
ASKR_CACHE_DB / ASKR_QUEUE_DB / ASKR_BROADCAST_DB Durable L2 backend paths (--features sql-backend; unset = L1 shared memory). See Storage backends.
ASKR_OBSERV_DSN (+ ASKR_OBSERV_SERVICE/HOST/BATCH/FLUSH_MS/QUEUE) Ship per-request logs to a MySQL-wire database (--features observ). See Observability.
ASKR_OTEL_ENDPOINT (+ ASKR_OTEL_SERVICE) Export OpenTelemetry traces (root http.request + child php.execute) over OTLP/gRPC (--features otel). See Observability.

opcache

PHP 8.5 compiles OPcache into libphp and auto-registers it, so there is no zend_extension line — just enable it (and JIT) in the INI:

[worker]
ini = "opcache.enable=1\nopcache.enable_cli=1\nopcache.validate_timestamps=0\nopcache.jit=tracing\nopcache.jit_buffer_size=128M"

validate_timestamps=0 maximises throughput (no stat() per file); pair it with a SIGHUP reload on deploy so fresh workers recompile the new code. opcache.jit enables the JIT (on by default in this build). askr-run.sh sets sensible defaults automatically.