Configuration
Environment variables and defaults.
Configuration
The backend reads its configuration from environment variables. There is no configuration file. The Load function in server/internal/config reads the variables and returns a Config value.
Configuration Variables
| Variable | Purpose | Default |
|---|---|---|
NVR_HTTP_ADDR |
The HTTP listen address. | :8080 |
NVR_DATA_DIR |
The data directory for the database. | ./data |
NVR_RECORDINGS_DIR |
The recordings directory. | ./recordings |
NVR_SECRETS_KEY |
The encryption key for secrets. | empty |
NVR_LOG_LEVEL |
The log level. | info |
NVR_RETENTION_DAYS |
The local fallback retention period in days. Drive-archived files are removed sooner after upload. | 7 |
NVR_ARCHIVE_INTERVAL_SECONDS |
Fallback cadence for the Drive archive loop when no segment completions arrive. Minimum 15. | 300 |
NVR_MAX_LOCAL_DWELL_MINUTES |
How long an unarchived segment may stay on the recordings volume before oldest-first eviction. 0 disables. Intended for RAM-staged recordings. |
0 |
NVR_LOCAL_EVICT_THRESHOLD |
Used-percent of the recordings volume that starts oldest-first overflow eviction of unarchived segments. 0 disables. |
0 |
NVR_MEDIAMTX_API_URL |
The MediaMTX control API URL. | http://127.0.0.1:9997 |
NVR_MEDIAMTX_WEBRTC_URL |
The MediaMTX WebRTC URL. | http://127.0.0.1:8889 |
NVR_MEDIAMTX_HLS_URL |
The MediaMTX HLS URL. | http://127.0.0.1:8888 |
NVR_MEDIAMTX_PLAYBACK_URL |
The MediaMTX playback URL. | empty |
NVR_GOOGLE_CLIENT_ID |
The Google OAuth client ID. | empty |
NVR_GOOGLE_CLIENT_SECRET |
The Google OAuth client secret. | empty |
NVR_GOOGLE_REDIRECT_URL |
The Google OAuth redirect URL. | empty |
NVR_PUBLIC_URL |
The externally reachable HTTPS URL of this server. | empty |
NVR_HOSTED_DASHBOARD_URL |
The hosted dashboard URL used to reach this server. | empty |
NVR_CORS_ORIGINS |
Extra exact HTTPS origins allowed cross-origin (comma-separated). | empty |
NVR_ADMIN_USERNAME |
First-admin username for first-start env bootstrap. | empty |
NVR_ADMIN_PASSWORD |
First-admin password for first-start env bootstrap. | empty |
The recognized log levels are debug, info, warn, warning, and error. A malformed integer falls back to the default value.
The secrets key
The NVR_SECRETS_KEY variable is the encryption key. It encrypts the camera credentials and the Google Drive tokens at rest. Set a long random value in production.
The key is required before you connect Google Drive. Without a key, the backend stores credentials as plaintext for development.
Database settings
The backend stores some settings in the SQLite settings table. These settings override the environment variables. The settings are:
- The recordings directory.
- The recording enabled flag.
- The retention period. It controls non-Drive pruning and MediaMTX's
recordDeleteAfterfallback; successful Drive uploads are cleaned up immediately.
When the recordings directory is empty, the backend disables recording.
Public and hosted dashboard URLs
NVR_PUBLIC_URL is the externally reachable HTTPS URL of this recorder. It is
used by the slim/headless build to deep-link to the hosted dashboard, and it is
the URL the browser reaches for playback. NVR_HOSTED_DASHBOARD_URL points at
the separately hosted dashboard that manages this server.
The backend persists both values in the SQLite settings table (keys
publicUrl and hostedDashboardUrl), set through nvrd setup or the setup
wizard. The environment variables deliberately override the persisted
values when non-empty; the persisted database values apply when the environment
is unset. There is no third configuration layer.
Both values must be absolute http or https URLs. In production, set
NVR_PUBLIC_URL to an HTTPS origin so the browser, HLS, and WebRTC signaling
are reachable over the tunnel. See operations for the HTTPS
tunnel requirement.
CORS origins
The backend allows cross-origin requests only from exact origins. The effective allow-list is:
- Every origin in
NVR_CORS_ORIGINS(comma-separated). Each must be HTTPS unless it is a localhost development origin (http://localhost:*orhttp://127.0.0.1:*). - The origin derived from
NVR_HOSTED_DASHBOARD_URL(the resolved hosted dashboard origin is always allowed).
Configured origins receive Access-Control-Allow-Origin,
Access-Control-Allow-Headers (including Authorization and
X-Session-Token), Access-Control-Expose-Headers: X-Session-Token, and
Vary: Origin, but not credential allowance. Localhost development origins
retain credential allowance for the embedded-UI dev flow.
First-admin bootstrap
NVR_ADMIN_USERNAME and NVR_ADMIN_PASSWORD support idempotent first-start
automation. They are honored only when the database has no users; once any
user exists they are ignored. The username must be non-empty after trimming and
the password must contain at least eight runes — invalid partial or weak
bootstrap configuration is a startup error while setup is required. The password
is argon2id-hashed and never persisted. Remove NVR_ADMIN_PASSWORD from the
environment after the first admin is created.
Google Drive configuration
You can set the Google OAuth details in two ways:
- As the
NVR_GOOGLE_*environment variables. - In the dashboard under Settings, Google Drive.
When you save the OAuth details in the dashboard, the backend encrypts them and stores them in the settings table. It prefers these persisted values when they are present.
To set up Google OAuth:
- Enable the Google Drive API.
- Configure the consent screen with the
drive.fileanduserinfo.emailscopes. - Create Web application credentials.
- Make the authorized redirect URI exactly match
NVR_GOOGLE_REDIRECT_URL.
The default redirect URL is:
http://localhost:8080/api/v1/storage/gdrive/callback
RAM-staged recordings
By default, segments land on a normal disk and Drive archival deletes each local file within minutes of its upload. If the recordings volume is an SSD you want to spare, or simply small, you can stage recordings in RAM instead:
- Mount a tmpfs volume at the recordings path. In Docker, back the
nvr-recordingsvolume (the Docker volume name) withtmpfs; see the commented block indeploy/docker-compose.yml. Size it to leave headroom for the OS — for example 2–4 GiB on an 8 GiB host. - Set
NVR_RECORDINGS_DIRto the container mount path:/var/lib/nvr/recordings(not the volume name). The compose file already mounts thenvr-recordingsvolume at that path. - Opt in to the safeguards so a full staging volume never stalls recording:
NVR_MAX_LOCAL_DWELL_MINUTESevicts unarchived segments older than the window, oldest first.NVR_LOCAL_EVICT_THRESHOLDevicts oldest-first while the volume is above the used-percent (a number 0-100, fractions allowed;0disables). The threshold measures the whole recordings filesystem, so give the staging volume its own mount. On a shared mount, unrelated files can pin usage above the threshold and enforcement will warn after evicting without reclaiming space.
With Google Drive connected, every completed one-minute segment nudges an
immediate archive pass, so under normal operation a segment exists locally for
only a few seconds and the SSD receives no recording writes at all (when host
swap is disabled; if swap is enabled, tmpfs pages may be swapped to disk). The
SQLite index stays on NVR_DATA_DIR.
Trade-offs to accept before enabling this mode:
- A reboot, power cut, or Docker daemon restart empties tmpfs. Everything not yet uploaded is gone.
- If Drive is unavailable longer than the dwell window, footage starts dropping
oldest-first. Evicted rows stay in segment list results marked
skipped:expired(but are excluded from day availability) until retention pruning removes them; evictions are reported in the server logs. - Upload bandwidth must keep up with recording bitrate. When it does not, eviction — not the archive backlog — defines what you lose.
The example environment file
The example file is in deploy/nvr.example.env. Copy it and set your values. The file documents the active variables and the commented production options.
See operations for the Docker environment and the deployment model.