# Reverse Proxy Configuration Deploy Open Notebook behind nginx, Caddy, Traefik, or other reverse proxies with custom domains and HTTPS. --- ## Simplified Setup (v1.1+) Starting with v1.1, Open Notebook uses Next.js rewrites to simplify configuration. **You only need to proxy to one port** - Next.js handles internal API routing automatically. ### How It Works ``` Browser → Reverse Proxy → Port 8502 (Next.js) ↓ (internal proxy) Port 5055 (FastAPI) ``` Next.js automatically forwards `/api/*` requests to the FastAPI backend, so your reverse proxy only needs one port! --- ## Quick Configuration Examples ### Nginx (Recommended) ```nginx 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; # Allow file uploads up to 100MB client_max_body_size 100M; # Single location block - that's it! location / { proxy_pass http://open-notebook:8502; 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; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_cache_bypass $http_upgrade; } } # HTTP to HTTPS redirect server { listen 80; server_name notebook.example.com; return 301 https://$server_name$request_uri; } ``` ### Caddy ```caddy notebook.example.com { reverse_proxy open-notebook:8502 { transport http { read_timeout 600s write_timeout 600s } } } ``` Caddy handles HTTPS automatically. The timeout settings ensure long-running operations (transformations, podcast generation) don't fail. ### Traefik ```yaml # Add this to your docker-compose.yml alongside the surrealdb service # See full base setup: https://github.com/lfnovo/open-notebook/blob/main/docker-compose.yml services: open-notebook: image: lfnovo/open_notebook:v1-latest pull_policy: always environment: - API_URL=https://notebook.example.com labels: - "traefik.enable=true" - "traefik.http.routers.notebook.rule=Host(`notebook.example.com`)" - "traefik.http.routers.notebook.entrypoints=websecure" - "traefik.http.routers.notebook.tls.certresolver=myresolver" - "traefik.http.services.notebook.loadbalancer.server.port=8502" # Timeout for long-running operations (transformations, podcasts) - "traefik.http.services.notebook.loadbalancer.responseforwarding.flushinterval=100ms" networks: - traefik-network ``` **Note**: For Traefik v2+, you may also need to configure `serversTransport` timeouts in your static configuration: ```yaml # traefik.yml (static configuration) serversTransport: forwardingTimeouts: dialTimeout: 30s responseHeaderTimeout: 600s idleConnTimeout: 90s ``` ### Coolify 1. Create new service using [Docker Compose](../1-INSTALLATION/docker-compose.md) 2. Set port to **8502** 3. Add environment: `API_URL=https://your-domain.com` 4. Enable HTTPS in Coolify 5. Done! --- ## Environment Variables ```bash # Required for reverse proxy setups API_URL=https://your-domain.com # Optional: For multi-container deployments # INTERNAL_API_URL=http://api-service:5055 ``` **Important**: Set `API_URL` to your public URL (with https://). **Note on HOSTNAME**: The Docker images set `HOSTNAME=0.0.0.0` by default, which ensures Next.js binds to all interfaces and is accessible from reverse proxies. You typically don't need to set this manually. --- ## Understanding API_URL The frontend uses a three-tier priority system to determine the API URL: 1. **Runtime Configuration** (Highest Priority): `API_URL` environment variable set at container runtime 2. **Build-time Configuration**: `NEXT_PUBLIC_API_URL` baked into the Docker image 3. **Auto-detection** (Fallback): Infers from the incoming HTTP request headers ### Auto-Detection Details When `API_URL` is not set, the Next.js frontend: - Analyzes the incoming HTTP request - Extracts the hostname from the `host` header - Respects the `X-Forwarded-Proto` header (for HTTPS behind reverse proxies) - Constructs the API URL as `{protocol}://{hostname}:5055` - Example: Request to `http://10.20.30.20:8502` → API URL becomes `http://10.20.30.20:5055` **Why set API_URL explicitly?** - **Reliability**: Auto-detection can fail with complex proxy setups - **HTTPS**: Ensures frontend uses `https://` when behind SSL-terminating proxy - **Custom domains**: Works correctly with domain names instead of IP addresses - **Port mapping**: Avoids exposing port 5055 in the URL when using reverse proxy **Important**: Don't include `/api` at the end - the system adds this automatically! --- ## Complete Docker Compose Example > **Note:** This example only shows the open-notebook and nginx services. You also need a `surrealdb` service. See the [full base docker-compose.yml](https://github.com/lfnovo/open-notebook/blob/main/docker-compose.yml) for the complete setup. ```yaml services: open-notebook: image: lfnovo/open_notebook:v1-latest pull_policy: always container_name: open-notebook environment: - API_URL=https://notebook.example.com - OPEN_NOTEBOOK_ENCRYPTION_KEY=${OPEN_NOTEBOOK_ENCRYPTION_KEY} - OPEN_NOTEBOOK_PASSWORD=${OPEN_NOTEBOOK_PASSWORD} volumes: - ./notebook_data:/app/data # Only expose to localhost (nginx handles public access) ports: - "127.0.0.1:8502:8502" 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 ``` --- ## Full Nginx Configuration ```nginx events { worker_connections 1024; } http { upstream notebook { server open-notebook:8502; } # HTTP redirect server { listen 80; server_name notebook.example.com; return 301 https://$server_name$request_uri; } # HTTPS server 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; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; # Allow file uploads up to 100MB client_max_body_size 100M; # Security headers add_header X-Frame-Options DENY; add_header X-Content-Type-Options nosniff; add_header X-XSS-Protection "1; mode=block"; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains"; # Proxy settings location / { proxy_pass http://notebook; 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; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_cache_bypass $http_upgrade; # Timeouts for long-running operations (transformations, podcasts, etc.) # 600s matches the frontend timeout for slow LLM operations proxy_read_timeout 600s; proxy_connect_timeout 60s; proxy_send_timeout 600s; } } } ``` --- ## Direct API Access (Optional) If external scripts or integrations need direct API access, route `/api/*` directly: ```nginx # Direct API access (for external integrations) location /api/ { proxy_pass http://open-notebook:5055/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 (handles all other traffic) location / { proxy_pass http://open-notebook:8502; # ... same headers as above } ``` **Note**: This is only needed for external API integrations. Browser traffic works fine with single-port setup. --- ## Advanced Scenarios ### Remote Server Access (LAN/VPS) Accessing Open Notebook from a different machine on your network: **Step 1: Get your server IP** ```bash # On the server running Open Notebook: hostname -I # or ifconfig | grep "inet " # Note the IP (e.g., 192.168.1.100) ``` **Step 2: Configure API_URL** ```bash # In docker-compose.yml or .env: API_URL=http://192.168.1.100:5055 ``` **Step 3: Expose ports** ```yaml # Add to your docker-compose.yml (requires surrealdb service, see installation guide) services: open-notebook: image: lfnovo/open_notebook:v1-latest pull_policy: always environment: - API_URL=http://192.168.1.100:5055 ports: - "8502:8502" - "5055:5055" ``` **Step 4: Access from client machine** ```bash # In browser on other machine: http://192.168.1.100:8502 ``` **Troubleshooting**: - Check firewall: `sudo ufw allow 8502 && sudo ufw allow 5055` - Verify connectivity: `ping 192.168.1.100` from client machine - Test port: `telnet 192.168.1.100 8502` from client machine --- ### API on Separate Subdomain Host the API and frontend on different subdomains: **docker-compose.yml:** ```yaml # Add to your docker-compose.yml (requires surrealdb service, see installation guide) services: open-notebook: image: lfnovo/open_notebook:v1-latest pull_policy: always environment: - API_URL=https://api.notebook.example.com - OPEN_NOTEBOOK_ENCRYPTION_KEY=${OPEN_NOTEBOOK_ENCRYPTION_KEY} # Don't expose ports (nginx handles routing) ``` **nginx.conf:** ```nginx # Frontend server 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; location / { proxy_pass http://open-notebook:8502; 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; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_cache_bypass $http_upgrade; } } # API server (separate subdomain) server { listen 443 ssl http2; server_name api.notebook.example.com; ssl_certificate /etc/nginx/ssl/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/privkey.pem; location / { proxy_pass http://open-notebook:5055; 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; } } ``` **Use case**: Separate DNS records, different rate limiting, or isolated API access control. --- ### Multi-Container Deployment (Advanced) For complex deployments with separate frontend and API containers: **docker-compose.yml:** ```yaml services: frontend: image: lfnovo/open_notebook_frontend:v1-latest pull_policy: always environment: - API_URL=https://notebook.example.com ports: - "8502:8502" api: image: lfnovo/open_notebook_api:v1-latest pull_policy: always environment: - OPEN_NOTEBOOK_ENCRYPTION_KEY=${OPEN_NOTEBOOK_ENCRYPTION_KEY} ports: - "5055:5055" depends_on: - surrealdb surrealdb: image: surrealdb/surrealdb:latest command: start --log trace --user root --pass root file:/mydata/database.db ports: - "8000:8000" volumes: - ./surreal_data:/mydata ``` **nginx.conf:** ```nginx http { upstream frontend { server frontend:8502; } upstream api { server api:5055; } server { listen 443 ssl http2; server_name notebook.example.com; # API routes 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) location / { proxy_pass http://frontend; 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; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_cache_bypass $http_upgrade; } } } ``` **Note**: Most users should use the [Docker Compose](../1-INSTALLATION/docker-compose.md) approach (`v1-latest`). Multi-container with separate nginx is only needed for custom scaling or isolation requirements. --- ## SSL Certificates ### Let's Encrypt with Certbot ```bash # Install certbot sudo apt install certbot python3-certbot-nginx # Get certificate sudo certbot --nginx -d notebook.example.com # Auto-renewal (usually configured automatically) sudo certbot renew --dry-run ``` ### Let's Encrypt with Caddy Caddy handles SSL automatically - no configuration needed! ### Self-Signed (Development Only) ```bash openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout ssl/privkey.pem \ -out ssl/fullchain.pem \ -subj "/CN=localhost" ``` --- ## Troubleshooting ### "Unable to connect to server" 1. **Check API_URL is set**: ```bash docker exec open-notebook env | grep API_URL ``` 2. **Verify reverse proxy reaches container**: ```bash curl -I http://localhost:8502 ``` 3. **Check browser console** (F12): - Look for connection errors - Check what URL it's trying to reach ### Mixed Content Errors Frontend using HTTPS but trying to reach HTTP API: ```bash # Ensure API_URL uses https:// API_URL=https://notebook.example.com # Not http:// ``` ### WebSocket Issues Ensure your proxy supports WebSocket upgrades: ```nginx proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; ``` ### 502 Bad Gateway 1. Check container is running: `docker ps` 2. Check container logs: `docker logs open-notebook` 3. Verify nginx can reach container (same network) ### Timeout Errors **Symptoms:** - `socket hang up` or `ECONNRESET` errors - `Timeout after 30000ms` errors - Operations fail after exactly 30 seconds **Cause:** Your reverse proxy has a default timeout (often 30s) that's shorter than Open Notebook's operations. **Solutions by proxy:** **Nginx:** ```nginx proxy_read_timeout 600s; proxy_send_timeout 600s; ``` **Caddy:** ```caddy reverse_proxy open-notebook:8502 { transport http { read_timeout 600s write_timeout 600s } } ``` **Traefik (static config):** ```yaml serversTransport: forwardingTimeouts: responseHeaderTimeout: 600s ``` **Application-level timeouts:** If you still experience timeouts after configuring your proxy, you can also adjust the application timeouts: ```bash # In .env file: API_CLIENT_TIMEOUT=600 # API client timeout (default: 300s) ESPERANTO_LLM_TIMEOUT=180 # LLM inference timeout (default: 60s) ``` See [Advanced Configuration](advanced.md) for more timeout options. --- ### How to Debug Configuration Issues **Step 1: 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 ``` **Example good output:** ``` ✅ [Config] Runtime API URL from server: https://your-domain.com ``` **Example bad output:** ``` ❌ [Config] Failed to fetch runtime config ⚠️ [Config] Using auto-detected URL: http://localhost:5055 ``` **Step 2: Test API directly** ```bash # Should return JSON config curl https://your-domain.com/api/config # Expected output: {"status":"ok","credentials_configured":true,...} ``` **Step 3: Check Docker logs** ```bash docker logs open-notebook # Look for: # - Frontend startup: "▲ Next.js ready on http://0.0.0.0:8502" # - API startup: "INFO: Uvicorn running on http://0.0.0.0:5055" # - Connection errors or CORS issues ``` **Step 4: Verify environment variable** ```bash docker exec open-notebook env | grep API_URL # Should show: # API_URL=https://your-domain.com ``` --- ### Frontend Adds `:5055` to URL (Versions ≤ 1.0.10) **Symptoms** (only in older versions): - 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 got 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. **Verification:** Check browser console (F12) - should see: `✅ [Config] Runtime API URL from server: https://your-domain.com` **If you can't upgrade**, explicitly configure the `/config` route: ```nginx # Only needed for versions ≤ 1.0.10 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; } ``` --- ### File Upload Errors (413 Payload Too Large) **Symptoms:** ``` CORS header 'Access-Control-Allow-Origin' missing. Status code: 413. Error creating source. Please try again. ``` **Root Cause:** When uploading files, your reverse proxy may reject the request due to body size limits *before* it reaches the application. Since the error happens at the proxy level, CORS headers are not included in the response. **Version Requirement:** - **Open Notebook v1.3.2+** is required for file uploads >10MB - Uses Next.js 16+ which supports the `proxyClientMaxBodySize` configuration option - Check your version: Settings → About (bottom of settings page) **Solutions:** 1. **Nginx - Increase body size limit**: ```nginx server { # Allow larger file uploads (default is 1MB) client_max_body_size 100M; # Add CORS headers to error responses error_page 413 = @cors_error_413; location @cors_error_413 { add_header 'Access-Control-Allow-Origin' '*' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always; add_header 'Access-Control-Allow-Headers' '*' always; return 413 '{"detail": "File too large. Maximum size is 100MB."}'; } location / { # ... your existing proxy configuration } } ``` 2. **Traefik - Increase buffer size**: ```yaml # In your traefik configuration http: middlewares: large-body: buffering: maxRequestBodyBytes: 104857600 # 100MB ``` Apply middleware to your router: ```yaml labels: - "traefik.http.routers.notebook.middlewares=large-body" ``` 3. **Kubernetes Ingress (nginx-ingress)**: ```yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: open-notebook annotations: nginx.ingress.kubernetes.io/proxy-body-size: "100m" # Add CORS headers for error responses nginx.ingress.kubernetes.io/configuration-snippet: | more_set_headers "Access-Control-Allow-Origin: *"; ``` 4. **Caddy**: ```caddy notebook.example.com { request_body { max_size 100MB } reverse_proxy open-notebook:8502 { transport http { read_timeout 600s write_timeout 600s } } } ``` **Note:** Open Notebook's API includes CORS headers in error responses, but this only works for errors that reach the application. Proxy-level errors (like 413 from nginx) need to be configured at the proxy level. --- ### CORS Errors **Symptoms:** ``` Access-Control-Allow-Origin header is missing Cross-Origin Request Blocked Response to preflight request doesn't pass access control check ``` **Possible Causes:** 1. **Missing proxy headers**: ```nginx # Make sure these are set: proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; ``` 2. **API_URL protocol mismatch**: ```bash # Frontend is HTTPS, but API_URL is HTTP: API_URL=http://notebook.example.com # ❌ Wrong API_URL=https://notebook.example.com # ✅ Correct ``` 3. **Reverse proxy not forwarding `/api/*` correctly**: ```nginx # Make sure this works: location /api/ { proxy_pass http://open-notebook:5055/api/; # Note the trailing slash! } ``` --- ### Missing Authorization Header **Symptoms:** ```json {"detail": "Missing authorization header"} ``` This happens when: - You have set `OPEN_NOTEBOOK_PASSWORD` for authentication - You're trying to access `/api/config` directly without logging in first **Solution:** This is **expected behavior**! The frontend handles authentication automatically. Just: 1. Access the frontend URL (not `/api/` directly) 2. Log in through the UI 3. The frontend will handle authorization headers for all API calls **For API integrations:** Include the password in the Authorization header: ```bash curl -H "Authorization: Bearer your-password-here" \ https://your-domain.com/api/config ``` --- ### SSL/TLS Certificate Errors **Symptoms:** - Browser shows "Your connection is not private" - Certificate warnings - Mixed content errors **Solutions:** 1. **Use Let's Encrypt** (recommended): ```bash sudo certbot --nginx -d notebook.example.com ``` 2. **Check certificate paths** in nginx: ```nginx ssl_certificate /etc/nginx/ssl/fullchain.pem; # Full chain ssl_certificate_key /etc/nginx/ssl/privkey.pem; # Private key ``` 3. **Verify certificate is valid**: ```bash openssl x509 -in /etc/nginx/ssl/fullchain.pem -text -noout ``` 4. **For development**, use self-signed (not for production): ```bash openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout ssl/privkey.pem -out ssl/fullchain.pem \ -subj "/CN=localhost" ``` --- ## Best Practices 1. **Always use HTTPS** in production 2. **Set API_URL explicitly** when using reverse proxies to avoid auto-detection issues 3. **Bind to localhost** (`127.0.0.1:8502`) and let proxy handle public access for security 4. **Enable security headers** (HSTS, X-Frame-Options, X-Content-Type-Options, X-XSS-Protection) 5. **Set up certificate renewal** for Let's Encrypt (usually automatic with certbot) 6. **Keep ports 5055 and 8502 accessible** from your reverse proxy container (use Docker networks) 7. **Use environment files** (`.env` or `docker.env`) to manage configuration securely 8. **Test your configuration** before going live: - Check browser console for config messages - Test API: `curl https://your-domain.com/api/config` - Verify authentication works - Check long-running operations (podcast generation) 9. **Monitor logs** regularly: `docker logs open-notebook` 10. **Don't include `/api` in API_URL** - the system adds this automatically --- ## Legacy Configurations (Pre-v1.1) If you're running Open Notebook **version 1.0.x or earlier**, you may need to use the legacy two-port configuration where you explicitly route `/api/*` to port 5055. **Check your version:** ```bash docker exec open-notebook cat /app/package.json | grep version ``` **If version < 1.1.0**, you may need: - Explicit `/api/*` routing to port 5055 in reverse proxy - Explicit `/config` endpoint routing for versions ≤ 1.0.10 - See the "Frontend Adds `:5055` to URL" troubleshooting section above **Recommendation:** Upgrade to v1.1+ for simplified configuration and better performance. --- ## Related - **[Security Configuration](security.md)** - Password protection and hardening - **[Advanced Configuration](advanced.md)** - Ports, timeouts, and SSL settings - **[Troubleshooting](../6-TROUBLESHOOTING/connection-issues.md)** - Connection problems - **[Docker Deployment](../1-INSTALLATION/docker-compose.md)** - Complete deployment guide