# Environment variables

> Every environment variable the Open branch reads and where to set it, plus the access control, reverse proxy and HTTPS an exposed instance needs.

- Canonical: https://big-agi.com/docs/self-host-configuration
- Minimum tier: open  [Open ⊂ Free ⊂ Pro - a tier only ever ADDS to the Open baseline]
- Kind: reference · Last content update generation: 2026-07-27 · Review by: 2026-10-25
- Answers: Which environment variables exist and what does each one do? · Which ones are required and which are optional? · How do I check that a variable was actually read? · Does my self-hosted instance send analytics anywhere? · How do I stop strangers from using my instance and my keys? · How do I put it behind a reverse proxy without breaking streaming?

> **You run the Open branch.** Everything here applies to the open-source `main` branch on a machine you control: the same product the hosted site runs, with the pieces hosted operates for you left in your hands. The code lives on [GitHub](https://github.com/enricoros/big-AGI) - if Open serves you, star the repo.

Every variable is optional. The app starts with an empty environment, and each person configures their own AI services inside it. Each variable below adds a capability or moves a default. One AI service key is the whole minimum for an instance that works on first load.

## Precedence

```
a person's own setting  ->  your environment variable  ->  the built-in default
```

That order holds at every point where a credential or an address is read. One deliberate inversion: when a Bedrock request falls through to your server credential, the region comes from `BEDROCK_REGION`. The region the browser sent is ignored.

## AI service keys

Set the key and the service appears, already configured, for everyone who opens the instance.

| Variable | What it does | Default |
|---|---|---|
| `ANTHROPIC_API_KEY` · `ANTHROPIC_API_HOST` | Anthropic credential; host override | `api.anthropic.com` |
| `OPENAI_API_KEY` · `OPENAI_API_HOST` · `OPENAI_API_ORG_ID` | OpenAI credential; host override; organization id | `api.openai.com` |
| `GEMINI_API_KEY` | Google Gemini credential | - |
| `DEEPSEEK_API_KEY` · `GROQ_API_KEY` · `MISTRAL_API_KEY` · `MOONSHOT_API_KEY` · `OPENROUTER_API_KEY` · `PERPLEXITY_API_KEY` · `TOGETHERAI_API_KEY` · `XAI_API_KEY` | One credential each | - |
| `ALIBABA_API_KEY` · `ALIBABA_API_HOST` | Alibaba credential; host override | `https://dashscope-intl.aliyuncs.com/compatible-mode` |
| `NVIDIANIM_API_KEY` · `NVIDIANIM_API_HOST` | NVIDIA NIM credential; host override | `https://integrate.api.nvidia.com` |
| `CEREBRAS_API_KEY` | Cerebras credential - [reports nothing](#fails-without-an-error) | - |
| `SAKANA_API_KEY` · `SAKANA_API_HOST` | Sakana credential; host override; reports nothing too | `https://api.sakana.ai` |
| `OLLAMA_API_HOST` | Address of an Ollama server the app's server can reach | `http://127.0.0.1:11434` |
| `LOCALAI_API_HOST` · `LOCALAI_API_KEY` | Address of a LocalAI server; optional credential | `http://127.0.0.1:8080` |
| `AZURE_OPENAI_API_ENDPOINT` · `AZURE_OPENAI_API_KEY` | Azure endpoint and credential - both required together | - |
| `AZURE_OPENAI_API_VERSION` · `AZURE_DEPLOYMENTS_API_VERSION` · `AZURE_OPENAI_DISABLE_V1` | Azure API versions; `true` turns the v1 API off | `2025-04-01-preview` · `2023-03-15-preview` · unset |
| `BEDROCK_BEARER_TOKEN` **or** `BEDROCK_ACCESS_KEY_ID` + `BEDROCK_SECRET_ACCESS_KEY` | AWS Bedrock credential, either form | - |
| `BEDROCK_SESSION_TOKEN` · `BEDROCK_REGION` | Temporary-credential token; region, server-side only | - · `us-east-1` |

Azure's v1 API is the default. It calls `/openai/v1/responses` with no deployment id; disabling it sends every model back through `/openai/deployments/<name>/`.

Cohere, LM Studio and Z.ai take no server-side variable: those are added per person in the app.

## Services

| Variable | What it does | Default |
|---|---|---|
| `PUPPETEER_WSS_ENDPOINT` | Remote browser for page fetching. `ws://` or `wss://` only | - |
| `GOOGLE_CLOUD_API_KEY` · `GOOGLE_CSE_ID` | Custom Search credentials - both required, or neither works | - |
| `ELEVENLABS_API_KEY` · `ELEVENLABS_API_HOST` · `ELEVENLABS_VOICE_ID` | ElevenLabs credential, host, voice - [unread today](#fails-without-an-error) | - |
| `POSTGRES_PRISMA_URL` · `POSTGRES_URL_NON_POOLING` | Share-link database - both required | - |

## Access and behaviour

| Variable | What it does | Default |
|---|---|---|
| `HTTP_BASIC_AUTH_USERNAME` · `HTTP_BASIC_AUTH_PASSWORD` | Credentials for the basic-auth middleware, which also has to be enabled in a build - [below](#keys-and-access-control) | - |
| `AIX_STRICT_PARSING` | `true` makes the wire parser throw in production instead of warning. For diagnosing an AI service whose API drifted | warn only |

## Compiled into the browser bundle

These are read when the JavaScript is built, so they take effect only in a build you run.

| Variable | What it does | Default |
|---|---|---|
| `NEXT_PUBLIC_MOTD` | The message-of-the-day banner - [below](#message-of-the-day) | - |
| `NEXT_PUBLIC_GOOGLE_DRIVE_CLIENT_ID` | Google Drive in the attachment menu | - |
| `NEXT_PUBLIC_PLANTUML_SERVER_URL` | Where diagrams are rendered | `https://www.plantuml.com/plantuml/svg/` |
| `NEXT_PUBLIC_GA4_MEASUREMENT_ID` · `NEXT_PUBLIC_POSTHOG_KEY` | Your own analytics properties - [below](#what-your-instance-reports) | - |
| `NEXT_PUBLIC_BUILD_HASH` | Overrides the build identifier the app displays | git revision, else `2-dev` |
| `NEXT_PUBLIC_DEBUG_BREAKS` | `true` pauses the browser debugger on every `error`, `critical` and `DEV` log. Development builds only: `npm run dev`, never a published image | - |

Three rules apply to all of the above:

- **A host or endpoint value must carry its scheme.** `api.openai.com` fails the build; `https://api.openai.com` passes.
- **A bad value stops the build, not a request** - see [When this doesn't work](#when-this-doesnt-work).
- **A key may be several keys**, separated by commas, one picked at random per request. That spreads rate limits at no cost.

| The field takes | AI services |
|---|---|
| several keys | Alibaba · Cerebras · Cohere · Deepseek · Gemini · Groq · Mistral · Moonshot · NVIDIA NIM · OpenRouter · Perplexity · Sakana · Together AI · xAI · Z.ai |
| one value | OpenAI · Anthropic · Azure · Bedrock · LocalAI · Ollama |

## Build time and run time

Everything named `NEXT_PUBLIC_*` is compiled into the JavaScript when the app is built. Everything else is read from the process environment while it runs.

| What you do | What happens |
|---|---|
| prebuilt image + `-e OPENAI_API_KEY=...` | works immediately, no rebuild |
| prebuilt image + `-e NEXT_PUBLIC_MOTD=...` | nothing happens, ever |
| a server-side variable changed | takes effect on restart |
| its name contains `_API_` | every browser re-scans its model services once |

The message of the day, Drive import, the analytics keys and the diagram server need a build you control: a Vercel project variable, or a build from source. The re-scan is [Update](/docs/self-host#update).

## Where to set them

| You run it with | Put them in |
|---|---|
| Source build | `.env` in the project root |
| `docker run` | `--env-file .env`, or one `-e` per variable |
| Compose | the `env_file` entry the shipped file already has |
| Kubernetes | `stringData` in the secret manifest |
| Vercel | the project's Environment Variables screen |

A variable that was accepted and changes nothing is one of the cases above, or a name that never reached the process. Everything else is [My self-hosted instance is broken](/docs/self-host-issues).

## Check what the server read

`/dev/debug` on your instance lists what the server picked up as capability booleans - this provider configured, that one not - plus build information, and never a value. **Download debug JSON** captures the same for a report. A variable that reads `false` there never reached the process: the name, the file, or the restart is missing. A variable that reads `true` and still misbehaves is a request-time problem: [My self-hosted instance is broken](/docs/self-host-issues).

## Keys and access control

A key in your server's environment belongs to the instance: everyone who opens it has working models and nobody types a secret. A key someone pastes into the app belongs to that browser alone. Both work at once, decided per service - and the person always wins: a key typed into the UI takes precedence over yours for that browser. Either way, whoever can reach the instance decides who spends, so access control goes in front of the app. The open branch ships no sign-in of its own and sits behind whatever authentication you already run.

### What people see when the key is on the server

| Where | What a person sees |
|---|---|
| first load | the service creates itself, fetches its models, picks one per job |
| Models -> Add service | a lime mark on the provider's icon |
| the setup panel | API key field: `Already set on server`, with a green check - the field stops being required |

Nobody opens Models. The three browser-only vendors above - Cohere, LM Studio, Z.ai - still ask each person for a key, and Cohere's error names the difference: `Missing Cohere API Key. Add it on the UI (Models Setup).`

### What a person can extract

**Not the value.** The browser is sent booleans - this provider is configured, that one is not - plus a hash and build information. No variable's value is ever serialised to the client. Provider-side, requests arrive from your server's address on your account: the dashboard attributes them to you, not to whoever asked.

**The spend, entirely.** In the open branch every API procedure is unauthenticated. Anyone who can reach the instance's API can bill your key without opening the app. Everything below is the fix.

### What is reachable if you do nothing

| Anyone who has the URL can | What it costs you |
|---|---|
| Send model requests through the API | Every key you set server-side, without limit |
| List which providers and services you configured | Booleans and your build hash, at `/dev/debug` |
| Run a web search | Your search quota |
| Synthesise speech | Your OpenAI key, through the speech route |
| Fetch a transcript | Your server's bandwidth |
| **Make your server fetch any URL** | Your remote-browser endpoint, unauthenticated by default |
| Write and read share links | Rows in your database |

The app's dev pages - `/dev`, `/dev/debug`, `/dev/llms`, `/dev/inspect/*` - are served in production: `/dev/debug` lists what the server picked up, and `/dev/llms` probes your configured models on a click.

### Put authentication in front

The strongest options need no rebuild and cover every path, dev pages included. Each is its vendor's own procedure:

- **Cloudflare Access** (Zero Trust) in front of any origin - [their docs](https://developers.cloudflare.com/cloudflare-one/policies/access/).
- **Vercel Deployment Protection** - password or Vercel authentication on the project - [their docs](https://vercel.com/docs/deployment-protection).
- **Your reverse proxy's own auth**: `auth_basic` in nginx, `basicauth` in Caddy, forward-auth in Traefik.

Company SSO belongs in the same layer. An identity-aware proxy - oauth2-proxy, Authelia, Pomerium, Cloudflare Access - authenticates the person and passes an already-authorised request through. Big-AGI needs no integration for this, and has none.

### Basic auth in the app

The repository ships `middleware_BASIC_AUTH.ts`. Rename it to `middleware.ts` and rebuild, then set `HTTP_BASIC_AUTH_USERNAME` and `HTTP_BASIC_AUTH_PASSWORD`. It is a build-time change, so the prebuilt image cannot have it.

| Basic auth | Paths |
|---|---|
| covered | `/`, everything under `/api`, a handful of app routes |
| not covered | `/workspace`, `/draw`, `/diff`, `/tokens`, `/dev/*` |

Your keys are covered; the dev pages are not. To cover everything with one control, use the platform or proxy option above.

Two response bodies tell you which mistake you made:

| Response body | What it means |
|---|---|
| `Unauthorized` | no credentials, or wrong ones |
| `Unauthorized/Unconfigured` | middleware enabled, the two variables are not set |

The second also logs `HTTP Basic Authentication is enabled but not configured` on the server: the file was renamed and the variables were never set.

### Never expose

- **The page-fetch route without authentication.** It makes your server an open URL fetcher through your browser endpoint.
- **`/dev/*`**, which lists your configuration.
- **Your remote-browser endpoint and your database.** Private network only, never published ports.

## Behind a reverse proxy

Streaming answers arrive as one long chunked `application/json` response on `/api/*` - not server-sent events. Four requirements follow:

1. No response buffering and no caching on `/api/*`.
2. Read and send timeouts well past a long generation - at least 300 seconds.
3. A request body limit large enough for your attachments.
4. `Upgrade` and `Connection` passed through, if you also want the in-app developer tooling.

```nginx
location / {
  proxy_pass http://127.0.0.1:3000;
  proxy_http_version 1.1;
  proxy_set_header Host $host;
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection 'upgrade';
  proxy_buffering off;
  proxy_cache off;
  chunked_transfer_encoding on;
  proxy_read_timeout 300s;
  proxy_send_timeout 300s;
  client_max_body_size 100M;
}
```

Caddy and Traefik do not buffer by default; their timeout and body-size settings still need raising. Answers that appear all at once at the end, die partway, or bounce with `**Request too large**` are the requirements above being violated - the symptom-by-symptom decode is [Stalled or cut off](/docs/issue-messages#stalled-or-cut-off).

## HTTPS

Caddy and Traefik obtain certificates by themselves; nginx and Apache use certbot - their documentation, not ours. The Big-AGI-specific reason it matters beyond secrecy: microphone capture, clipboard reads and screen capture are secure-context browser features. `http://localhost` counts as secure; a LAN address like `http://192.168.1.10` does not. On a development machine, `npm run dev-https` serves the dev server over TLS.

## Message of the day

`NEXT_PUBLIC_MOTD` renders a banner above the app for everyone on the instance. Build-time only, like every `NEXT_PUBLIC_*` variable, and each person can dismiss it permanently. Two placeholders expand at render: `{{app_build_hash}}` and `{{app_build_pkgver}}`.

## What your instance reports

Nothing, by default. The two analytics integrations activate only when a build carries your own keys - `NEXT_PUBLIC_GA4_MEASUREMENT_ID`, `NEXT_PUBLIC_POSTHOG_KEY` - and then report to your properties, not ours. The shipped `Dockerfile` sets `NEXT_TELEMETRY_DISABLED=1`, so Next.js's own telemetry is off in every image built from it.

## Fails without an error

| What happens | How to tell |
|---|---|
| Someone types their own key into a service you configured | They spend their own from that moment - there is no per-person quota and no way to force yours |
| **Cerebras** and **Sakana** answer requests and announce nothing | No lime mark, no green check, no self-configuration - add the service by hand, key field empty |
| The `Already set on server` label goes as soon as someone types a custom OpenAI host | Your server key is not used against an address the browser chose |
| Basic auth is on and `/dev/*` still answers | Load `/dev/debug` from outside your session; a stranger reads the same capability list |
| The microphone stops working on a LAN address over plain HTTP | Secure-context features need HTTPS or `localhost`, and nothing in the app says so |

## When this doesn't work

A key that is set but never reaches the request says so per provider, in one shape: `Missing OpenAI API Key. Add it on the UI or server side (your deployment).` Confirm the server read it at all before touching anything else: [Check what the server read](#check-what-the-server-read). Errors the provider sent back are [access errors from the provider](/docs/issue-key-and-access). Streaming that stalls without a proxy in the path is [Stalled or cut off](/docs/issue-messages#stalled-or-cut-off). Anything else on your own deployment is [My self-hosted instance is broken](/docs/self-host-issues).

## Related

- [Run it yourself](/docs/self-host)
- [Turn on the optional services](/docs/self-host-optional-services)
- [Keep your key safe](/docs/key-storage)
