Skip to main content
All configuration lives in config/nightowl.php after running php artisan nightowl:install. Most values come from environment variables, and the defaults suit a single agent running on the same host as your app.
When changes take effect. Settings the agent reads (database, drain, health, alerts, retention, ignored routes, searchable log context) apply when you restart nightowl:agent. Settings your app reads (NIGHTOWL_ENABLED, NIGHTOWL_INGEST_URI, NIGHTOWL_INGEST_TIMEOUT, NIGHTOWL_PARALLEL_WITH_NIGHTWATCH and the capture settings) apply when your app’s processes reload their config: reload PHP-FPM or Octane and restart queue workers. NIGHTOWL_TOKEN is read by both.If you run php artisan config:cache, Laravel stops reading .env and the environment at runtime. Re-run it after every change. Variables set inline on a command line or in a systemd Environment= line are ignored under a cached config; the agent’s --host, --port and --sqlite-path options still apply.NightOwl attaches to Nightwatch when your app starts booting, and checks its settings again once every service provider has booted, so a value set in a provider’s boot() still applies. Setting nightowl.enabled there turns NightOwl on or off for that process. Turned off, the process runs as if NightOwl had never attached. A change to nightowl.agent.port, nightowl.agent.token, nightowl.agent.ingest_uri, nightowl.agent.ingest_timeout, nightowl.parallel_with_nightwatch, nightowl.capture, nightwatch.token or nightwatch.redact_payload_fields is applied with the new value.

Database connection

NIGHTOWL_DB_SSLMODE applies to every connection the agent makes to this database: the drain, health alerts, and the artisan commands (nightowl:install, nightowl:migrate, the migration the agent runs at startup, and the other nightowl:* commands), which connect through Laravel’s nightowl connection. The default, prefer, uses TLS when the server offers it, without checking the certificate. When NIGHTOWL_DB_SSLMODE isn’t set, the drain uses prefer. Health alerts and the artisan commands then leave the mode to libpq, which applies a PGSSLMODE environment variable if there is one. Set NIGHTOWL_DB_SSLMODE to give every connection the same mode. Use the same credentials you entered when creating the app in the dashboard — the hosted UI uses them to read the telemetry your agent wrote.
Managed Postgres (Supabase, Neon, RDS): point the agent at the direct or session connection on port 5432, not a transaction-mode pooler (Supabase’s 6543). The agent holds one long-lived connection per drain worker, so it needs few connections, and php artisan nightowl:partition needs a direct session: behind a transaction-mode pooler it stops with an error. Set NIGHTOWL_DB_SSLMODE=require, and use the provider’s existing database (postgres on Supabase, neondb on Neon). NightOwl creates the tables, not the database.

Sharing one database across environments

NightOwl stamps an environment column (your APP_ENV) on every row, so you can point several app environments (local, staging, production) at one NightOwl database and filter them apart in the dashboard. The environments are separated only by that column; the nightowl_* tables themselves are shared. (Unrelated to table partitioning — see Data management for that.) nightowl:install and nightowl:migrate track their migration history inside the NightOwl database, so they’re idempotent across every environment that shares it. Run php artisan nightowl:migrate as part of each deploy:
  • the first environment to deploy creates the tables;
  • the rest are no-ops;
  • when a package upgrade adds a migration, it applies on whichever environment deploys first.
No “owner” environment, no flags. If a database already has the nightowl_* tables but no NightOwl migration history (for example it was created by an older version, or by your app’s php artisan migrate), the command adopts the existing schema as a baseline instead of trying to recreate it — so you never hit relation "nightowl_requests" already exists.
By default NightOwl’s migrations are not bundled into your app’s php artisan migrate. Manage the schema with nightowl:migrate (or nightowl:install). See NIGHTOWL_RUN_MIGRATIONS below if you want the legacy ride-along behavior.
If a package upgrade adds a migration and you forget to run nightowl:migrate, php artisan nightowl:agent warns at startup that the schema is behind (and keeps running) rather than failing silently mid-drain. So wiring nightowl:migrate into your deploy is the way to keep the schema current.

Agent runtime

The ingest socket always sets SO_REUSEPORT, so several agents on one host can share a port. Each one still needs its own buffer and its own health port. See Multiple instances.

Health API and reporting

See Health monitoring for what the reports and /status contain.

Authentication

For a standalone install, set NIGHTOWL_TOKEN only. For parallel mode, set both: NIGHTOWL_TOKEN is what the NightOwl agent verifies against, NIGHTWATCH_TOKEN is your Nightwatch token used by the Nightwatch SDK to reach Laravel Cloud’s hosted ingest in parallel.

Drain pipeline

Issue lifecycle

See throughput tuning for how to size these.

Ignored routes

See Ignoring routes for the pattern rules and what ignoring does to your error rate.

Sampling

Sampling is a Nightwatch setting, applied in your app before anything reaches the agent. Every rate defaults to 1.0, which keeps everything:
If your app already sets these for Nightwatch, they keep working. The agent stores what Nightwatch ships and never samples a second time. See Filtering and context for the full recipe.

Redaction

PII redaction is handled upstream by laravel/nightwatch before the payload reaches the agent. Configure it in your customer app’s .env:
Both lists are applied inside the customer’s PHP process before the record leaves the host, so the agent’s TCP buffer and PostgreSQL drain never see the un-redacted values.

Request payloads and response bodies

On its own, Nightwatch records a request’s payload only when NIGHTWATCH_CAPTURE_REQUEST_PAYLOAD=true (it’s off by default), and then only for 500 responses. It never records response bodies. The NightOwl settings below replace that. Set them in your application’s .env: The status and route filters apply to both payloads and response bodies. Route patterns use the same syntax as NIGHTOWL_IGNORE_ROUTES: * is the only wildcard, and a leading slash is optional. If a filter is set but none of its entries is valid (a typo such as errors), nothing is captured and a warning starting [NightOwl Agent] says why. Your app reads these settings, so your app writes that warning, to PHP’s error log (for web requests, the web server or PHP-FPM error log), not to the agent’s output. A mistake in a filter will never widen capture to every request.
Redaction. Payloads are redacted with NIGHTWATCH_REDACT_PAYLOAD_FIELDS, plus any Nightwatch::redactRequests() callback you registered. JSON response bodies are redacted with NIGHTOWL_REDACT_RESPONSE_FIELDS, which matches key names at any depth and ignores case. When unset, it redacts password, password_confirmation, token, access_token, refresh_token, api_key, secret and client_secret. Setting it replaces that list rather than adding to it, and setting it to an empty value turns response redaction off. HTML, XML and plain-text responses have no fields to redact, so they are stored exactly as sent. Keep that in mind before enabling capture on routes that render personal data. What is not stored. File downloads, streamed responses and binary content types (images, PDFs) are recorded as a short marker instead of their bytes. If a request’s payload and response together exceed about 4 MB, the response (and if needed the payload) is replaced by a TOO_LARGE marker whatever NIGHTOWL_CAPTURE_MAX_BYTES says. The agent refuses a single transmission over 10 MB, and that would lose the request’s queries, logs and exceptions along with the body. Storage. Captured bodies are compressed like the rest of your request data. Capturing every response on a busy app adds real storage to your database, so the status and route filters are the main way to keep it small. Response bodies need a new database column, added by a migration in the same agent release. The agent runs pending migrations when it starts; if you set NIGHTOWL_AUTO_MIGRATE=false, run php artisan nightowl:migrate yourself. Until the migration has run, requests are still recorded and only their response bodies are dropped. In parallel mode, Nightwatch’s own copy of each request is not affected by any of these settings.

Retention and caching

Full retention and pruning strategy lives in Data management.

Searchable log context

By default a log’s context is stored compressed. PostgreSQL cannot decompress it while running a query, so the dashboard’s log search matches the message only and shows a “Narrowed search” note saying so.
With this on, searching your logs for 8412 finds the line written by Log::error('Payment failed', ['order_id' => 8412]), not just lines with 8412 in the message. Restart the agent after changing it. The search covers context only for a time range that starts at least 24 hours after the point where uncompressed context begins, which allows for clock differences between machines and drain delays. So context search starts working about a day after you turn the setting on. Converting existing logs (below) moves that point back. The agent only stores context uncompressed once its schema has the column for it (added by php artisan nightowl:migrate, which the agent normally runs itself on boot). If the setting is on but the column is missing, the agent keeps storing compressed context, logs a warning saying so, and the dashboard keeps showing the “Narrowed search” note — nothing breaks, it just is not searchable yet. Run the migration and restart.
This trades storage for searchability, and how much depends entirely on what your app logs — small contexts barely compress at all (under about 256 bytes, compression can make them larger), while large ones compress well. Measure yours before deciding:
That reports what your context currently occupies and what it would occupy uncompressed, and changes nothing.

Making existing logs searchable

Turning the flag on is forward-only — it changes what the agent stores from that moment. Logs already on disk stay compressed and stay unsearchable, and because NightOwl retention is typically long, waiting for them to age out is not a real answer. Convert them:
It works newest-first, so the logs you are most likely to search become searchable first, and you can stop it at any time and re-run later to continue — an interrupted run leaves a smaller searchable range, never a wrong one. The dashboard keeps showing the “Narrowed search” note for ranges it has not reached yet. Useful options:
A full conversion rewrites every log row in range, which leaves dead rows behind until autovacuum (or --vacuum) reclaims them, so the table temporarily needs more disk than the figures above. On a large history, run it deliberately rather than during a deploy. --since is usually the better trade: most people search recent logs.

If the setting is turned off again

Removing NIGHTOWL_LOG_CONTEXT_SEARCHABLE (a deploy that drops the variable, for instance) makes the agent store compressed context again, and log search goes back to matching the message only — including over the history you converted. That data is not lost or changed; the search simply cannot cover a range that has compressed logs in it. To get it back, set the variable again, restart the agent, and re-run:
This second run is quick — your already-converted logs need no conversion, so it only re-establishes the searchable range over them.

Parallel mode

In parallel mode NightOwl’s copy goes to NIGHTOWL_INGEST_URI and Nightwatch’s to NIGHTWATCH_INGEST_URI; each side reads only its own. See Running alongside Nightwatch.

Artisan commands

The agent command accepts --driver=async|sync. async is the default and required for multi-worker drain; sync is a single-process fallback for hosts without pcntl/posix, and it sends no health reports. It also accepts --host, --port and --sqlite-path, which override the matching settings and still apply under a cached config.