Move runtime configuration endpoint from /api/runtime-config to /_config to avoid
conflicts with reverse proxies that route all /api/* requests to the FastAPI backend.
This fixes an issue where users with reverse proxies would see port 5055 incorrectly
appended to their API_URL even when explicitly set via environment variable.
Changes:
- Move frontend/src/app/api/runtime-config/route.ts to frontend/src/app/_config/route.ts
- Update config.ts to fetch from /_config instead of /api/runtime-config
- Add troubleshooting documentation for reverse proxy users
- Update all reverse proxy examples to show correct routing (catch-all handles /_config)
- Bump version to 1.0.11
The new /_config endpoint is automatically handled by standard reverse proxy catch-all
rules (location / { proxy_pass http://frontend; }), requiring no additional configuration
for most users.
Fixes issue where API_URL environment variable was being ignored in reverse proxy setups,
causing CORS errors with "Status code: (null)" and incorrect port 5055 being added.
10 KiB
Reverse Proxy Configuration
This guide helps you deploy Open Notebook behind a reverse proxy (nginx, Caddy, Traefik, etc.) or with a custom domain.
The API_URL Environment Variable
Starting with v1.0+, Open Notebook supports runtime configuration of the API URL through the API_URL environment variable. This means you can use the same Docker image in different deployment scenarios without rebuilding.
How It Works
The frontend uses a three-tier priority system to determine the API URL:
- Runtime Configuration (Highest Priority):
API_URLenvironment variable set at container runtime - Build-time Configuration:
NEXT_PUBLIC_API_URLbaked into the Docker image - Auto-detection (Fallback): Infers from the incoming HTTP request headers
Auto-detection details:
- The Next.js frontend analyzes the incoming HTTP request
- Extracts the hostname from the
hostheader - Respects the
X-Forwarded-Protoheader (for HTTPS behind reverse proxies) - Constructs the API URL as
{protocol}://{hostname}:5055 - Example: Request to
http://10.20.30.20:8502→ API URL becomeshttp://10.20.30.20:5055
Common Scenarios
Scenario 1: Docker on Localhost (Default)
No configuration needed! The system auto-detects.
docker run -d \
--name open-notebook \
-p 8502:8502 -p 5055:5055 \
-v ./notebook_data:/app/data \
-v ./surreal_data:/mydata \
lfnovo/open_notebook:v1-latest-single
Scenario 2: Docker on Remote Server (LAN/VPS)
Access via IP address - auto-detection works, but you can be explicit:
docker run -d \
--name open-notebook \
-p 8502:8502 -p 5055:5055 \
-e API_URL=http://192.168.1.100:5055 \
-v ./notebook_data:/app/data \
-v ./surreal_data:/mydata \
lfnovo/open_notebook:v1-latest-single
Note
: Don't include
/apiat the end - the system adds this automatically!
Scenario 3: Behind Reverse Proxy with Custom Domain
This is where API_URL is essential. Your reverse proxy handles HTTPS and routing.
Important: If your reverse proxy forwards
/apirequests to the backend, setAPI_URLto just the domain (without/apisuffix). The frontend will append/apiautomatically.
Example: nginx + Docker Compose
docker-compose.yml:
version: '3.8'
services:
open-notebook:
image: lfnovo/open_notebook:v1-latest-single
container_name: open-notebook
environment:
- API_URL=https://notebook.example.com
- OPENAI_API_KEY=${OPENAI_API_KEY}
volumes:
- ./notebook_data:/app/data
- ./surreal_data:/mydata
ports:
- "8502:8502" # Frontend
- "5055:5055" # API
restart: unless-stopped
nginx:
image: nginx:alpine
container_name: nginx-proxy
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on:
- open-notebook
restart: unless-stopped
nginx.conf:
http {
upstream frontend {
server open-notebook:8502;
}
upstream api {
server open-notebook:5055;
}
server {
listen 80;
server_name notebook.example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name notebook.example.com;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
# API
location /api/ {
proxy_pass http://api/api/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Frontend (catch-all - handles /_config automatically)
location / {
proxy_pass http://frontend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
}
Scenario 4: Behind Reverse Proxy with Subdomain
If you want API on a separate subdomain:
docker-compose.yml:
services:
open-notebook:
image: lfnovo/open_notebook:v1-latest-single
environment:
- API_URL=https://api.notebook.example.com
# ... other env vars
nginx.conf:
# Frontend server
server {
listen 443 ssl http2;
server_name notebook.example.com;
location / {
proxy_pass http://open-notebook:8502;
# ... proxy headers
}
}
# API server
server {
listen 443 ssl http2;
server_name api.notebook.example.com;
location / {
proxy_pass http://open-notebook:5055;
# ... proxy headers
}
}
Scenario 5: Traefik
docker-compose.yml:
version: '3.8'
services:
open-notebook:
image: lfnovo/open_notebook:v1-latest-single
environment:
- API_URL=https://notebook.example.com
labels:
# Frontend
- "traefik.enable=true"
- "traefik.http.routers.notebook-frontend.rule=Host(`notebook.example.com`)"
- "traefik.http.routers.notebook-frontend.entrypoints=websecure"
- "traefik.http.routers.notebook-frontend.tls.certresolver=myresolver"
- "traefik.http.services.notebook-frontend.loadbalancer.server.port=8502"
# API (higher priority to match first)
- "traefik.http.routers.notebook-api.rule=Host(`notebook.example.com`) && PathPrefix(`/api`)"
- "traefik.http.routers.notebook-api.entrypoints=websecure"
- "traefik.http.routers.notebook-api.tls.certresolver=myresolver"
- "traefik.http.routers.notebook-api.priority=100"
- "traefik.http.services.notebook-api.loadbalancer.server.port=5055"
networks:
- traefik-network
networks:
traefik-network:
external: true
Scenario 6: Caddy
Caddyfile:
notebook.example.com {
# API
reverse_proxy /api/* open-notebook:5055
# Frontend (catch-all - handles /_config automatically)
reverse_proxy / open-notebook:8502
}
docker-compose.yml:
services:
open-notebook:
image: lfnovo/open_notebook:v1-latest-single
environment:
- API_URL=https://notebook.example.com
# No need to expose ports if using Caddy in same network
Troubleshooting
Connection Error: Unable to connect to server
Symptoms: Frontend displays "Unable to connect to server. Please check if the API is running."
Possible Causes:
-
API_URL not set correctly for your reverse proxy setup
- Check browser console (F12) for connection errors
- Look for logs showing what URL the frontend is trying
-
Reverse proxy not forwarding to correct port
- API should be accessible at the URL specified in
API_URL - Test:
curl https://your-domain.com/api/configshould return JSON
- API should be accessible at the URL specified in
-
CORS issues
- Ensure
X-Forwarded-ProtoandX-Forwarded-Forheaders are set in proxy config - Check API logs for CORS errors
- Ensure
-
SSL/TLS certificate issues
- Ensure your reverse proxy has valid SSL certificates
- Mixed content errors (HTTPS frontend trying to reach HTTP API)
Frontend adds :5055 to URL when using reverse proxy (versions ≤ 1.0.10)
Symptoms (only in versions 1.0.10 and earlier):
- You set
API_URL=https://your-domain.com - Browser console shows: "Attempted URL: https://your-domain.com:5055/api/config"
- CORS errors with "Status code: (null)"
Root Cause:
In versions ≤ 1.0.10, the frontend's config endpoint was at /api/runtime-config, which gets intercepted by reverse proxies routing all /api/* requests to the backend. This prevented the frontend from reading the API_URL environment variable.
Solution:
Upgrade to version 1.0.11 or later. The config endpoint has been moved to /_config which avoids the /api/* routing conflict.
Note: Most reverse proxy configurations with a catch-all rule like location / { proxy_pass http://frontend; } will automatically route /_config to the frontend without any additional configuration needed.
Only if you have issues, explicitly configure the /_config route:
# Only needed if your reverse proxy doesn't have a catch-all rule
location = /_config {
proxy_pass http://open-notebook:8502;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
Verification:
Check browser console (F12) - should see: ✅ [Config] Runtime API URL from server: https://your-domain.com
How to Debug
-
Check browser console (F12 → Console tab):
- Look for messages starting with
🔧 [Config] - These show the configuration detection process
- You'll see which API URL is being used
- Look for messages starting with
-
Test API directly:
# Should return JSON config curl https://your-domain.com/api/config -
Check Docker logs:
docker logs open-notebook- Look for frontend and API startup messages
- Check for connection errors
-
Verify environment variable:
docker exec open-notebook env | grep API_URL
Missing Authorization Header
Symptoms: API returns {"detail": "Missing authorization header"}
This happens when:
- You have set
OPEN_NOTEBOOK_PASSWORDfor authentication - You're trying to access
/api/configdirectly without logging in first
Solution: This is expected behavior! The frontend handles this automatically. Just access the frontend URL and log in through the UI.
Best Practices
- Always use HTTPS in production with reverse proxies
- Set
API_URLexplicitly when using reverse proxies to avoid auto-detection issues - Use environment files (
.envordocker.env) to manage configuration - Test your setup by accessing the frontend and checking browser console logs
- Keep ports 5055 and 8502 accessible from your reverse proxy container