Open WebUI Nginx Proxy: Fix WebSocket and Signup Defaults

Open WebUINginxSelf-HostingSecurityTutorial

TL;DR: Put Nginx in front of Open WebUI only after binding the container port to 127.0.0.1, enabling proxy_http_version 1.1 with WebSocket upgrade headers and proxy_buffering off, and setting ENABLE_SIGNUP=false once your admin account exists. Leaving Nginx on its HTTP/1.0 proxy default breaks live token updates, and leaving Open WebUI on its signup default lets anyone who reaches the proxy register an account.

What you’ll be able to do after this guide:

  • Verify Open WebUI’s license terms and authentication defaults at a pinned upstream commit.
  • Configure Nginx 1.26 with HTTP/1.1 WebSocket upgrade headers and unbuffered streaming.
  • Run a local Python 3 and nginx -t preflight before exposing a reverse proxy on your network.

Honest take: Use Open WebUI’s built-in authentication with ENABLE_SIGNUP=false and a loopback-bound container port first; slapping a basic proxy_pass block onto 0.0.0.0:3000 leaves both your streaming sockets and your user registration wide open.

What license and auth defaults does Open WebUI actually use?

Before deploying a shared reverse proxy, check the license and default configuration in the upstream repository. On November 6, 2026, we inspected open-webui/open-webui at commit c38885a1a737da4968f157092f304f3b5481d0bf. Its pinned LICENSE file is the Open WebUI License, a modified BSD-style license whose Clause 4 prohibits altering, removing, or replacing “Open WebUI” branding in deployments exceeding 50 users within a rolling 30-day period (without separate written permission or an enterprise license). Nginx itself is distributed under the 2-clause BSD license. If you self-host Open WebUI unmodified for a personal lab or small team, Clause 4 is satisfied by keeping the original branding intact.

Next, inspect the authentication and socket defaults in Open WebUI’s pinned backend code:

  • In backend/open_webui/env.py, WEBUI_AUTH defaults to True (os.getenv('WEBUI_AUTH', 'True').lower() == 'true'), and ENABLE_WEBSOCKET_SUPPORT also defaults to True.
  • In backend/open_webui/config.py, ENABLE_SIGNUP defaults to True whenever WEBUI_AUTH is enabled (False if not WEBUI_AUTH else os.getenv('ENABLE_SIGNUP', 'True').lower() == 'true').
Setting or layerUpstream defaultRisk when placed behind a reverse proxyRecommended production setting
WEBUI_AUTHTrueDisabling it (False) gives every visitor unauthenticated accessKeep true
ENABLE_SIGNUPTrue (when WEBUI_AUTH=True)Any visitor reaching the proxy can register a new account after the admin is createdSet false after creating the initial admin account
WEBUI_SECRET_KEYAuto-generated or empty if unsetContainer restarts invalidate active login sessions if not persistedSet an explicit random secret string
Docker port mappingOften copied as -p 3000:8080 (0.0.0.0)Callers on the LAN or public IP can bypass Nginx TLS on port 3000Bind -p 127.0.0.1:3000:8080
Nginx proxy_http_version1.0Breaks WebSocket /ws/socket.io upgrades and HTTP/1.1 streamingSet proxy_http_version 1.1;
Nginx proxy_bufferingonToken streams stall until buffers fill instead of rendering token by tokenSet proxy_buffering off;

Why does a minimal Nginx proxy_pass break Open WebUI streaming?

A one-line proxy_pass http://127.0.0.1:3000; block loads the initial HTML page, which tricks many administrators into thinking the proxy works. The failure appears when you start a chat completion or when Open WebUI opens its /ws/socket.io/ real-time channel.

Per the official Nginx ngx_http_proxy_module documentation, Nginx defaults proxy_http_version to 1.0 and enables response buffering (proxy_buffering on;). Furthermore, as explained in the Nginx WebSocket proxying guide, hop-by-hop headers including Upgrade and Connection are not forwarded from the client to the proxied server unless you set them explicitly.

Without those three directives, two symptoms appear immediately:

  1. WebSocket handshakes fail, causing Open WebUI (ENABLE_WEBSOCKET_SUPPORT=True) to lose live session state synchronization or fall back to noisy polling errors in the browser console.
  2. Server-sent token streams arrive in Delayed Chunks instead of smooth token-by-token output because Nginx buffers upstream chunks in memory before flushing them to the browser.

Step 1: bind Open WebUI to loopback and lock registration

Start by making sure the Open WebUI container listens only on the host’s loopback interface (127.0.0.1:3000) while Ollama remains bound to loopback as well. If you have already created your initial administrator account in Open WebUI, set ENABLE_SIGNUP=false and supply a persistent WEBUI_SECRET_KEY:

services:
  open-webui:
    image: ghcr.io/open-webui/open-webui:main
    container_name: open-webui
    ports:
      - "127.0.0.1:3000:8080"
    environment:
      - OLLAMA_BASE_URL=http://host.docker.internal:11434
      - WEBUI_AUTH=true
      - ENABLE_SIGNUP=false
      - ENABLE_WEBSOCKET_SUPPORT=true
      - WEBUI_SECRET_KEY=replace-with-32-byte-random-hex
    extra_hosts:
      - "host.docker.internal:host-gateway"
    volumes:
      - open-webui:/app/backend/data
    restart: unless-stopped

volumes:
  open-webui:

If this is a brand-new installation with zero users in /app/backend/data, start once with ENABLE_SIGNUP=true, create the first account (which becomes the administrator), and immediately flip ENABLE_SIGNUP=false and recreate the container. For the underlying security rationale on why AI ports must never listen on 0.0.0.0, see our exposed Ollama instances security guide. If your Open WebUI container cannot reach Ollama on the host bridge, troubleshoot that layer first with our sister site’s Open WebUI cannot connect to Ollama fix.

Step 2: write the Nginx WebSocket and streaming server block

Use Nginx’s documented map $http_upgrade $connection_upgrade pattern inside the http context so requests with an Upgrade header set Connection: upgrade and ordinary HTTP requests set Connection: close. On November 6, 2026, we verified the following configuration structure against nginx/1.26.3 on Linux:

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 443 ssl;
    server_name chat.example.internal;

    ssl_certificate     /etc/ssl/private/chat.example.internal.crt;
    ssl_certificate_key /etc/ssl/private/chat.example.internal.key;

    client_max_body_size 25M;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_buffering off;
        proxy_read_timeout 300s;
    }
}

Why keep authentication inside Open WebUI instead of adding auth_basic at the Nginx layer? Open WebUI uses the Authorization: Bearer ... / session header space for its own multi-user login, role management, and /api/ endpoints. Adding Nginx HTTP Basic Auth (Authorization: Basic ...) on / overwrites or collides with the Authorization header unless you maintain separate location blocks and header rewriting. Using Open WebUI’s native authentication (WEBUI_AUTH=true, ENABLE_SIGNUP=false) avoids header collisions while preserving per-user chat histories.

Step 3: run the local preflight script and nginx -t

Before reloading Nginx, validate both your Nginx proxy directives and your Open WebUI environment map. Save this standard-library script as check_openwebui_proxy_preflight.py:

import re

def check_openwebui_nginx_conf(conf_text):
    issues = []
    if not re.search(r"proxy_pass\s+http://127\.0\.0\.1:\d+", conf_text):
        issues.append("proxy_pass must target a loopback upstream (127.0.0.1)")
    if not re.search(r"proxy_http_version\s+1\.1\s*;", conf_text):
        issues.append("missing proxy_http_version 1.1; (Nginx defaults to 1.0)")
    if not re.search(r"proxy_set_header\s+Upgrade\s+\$http_upgrade\s*;", conf_text):
        issues.append("missing proxy_set_header Upgrade $http_upgrade;")
    if not re.search(r"proxy_set_header\s+Connection\s+", conf_text):
        issues.append("missing proxy_set_header Connection header for WebSocket upgrade")
    if not re.search(r"proxy_buffering\s+off\s*;", conf_text):
        issues.append("missing proxy_buffering off; (token streaming will buffer)")
    return issues

def check_env_map(env_map):
    issues = []
    if env_map.get("WEBUI_AUTH", "true").lower() != "true":
        issues.append("WEBUI_AUTH must remain true on reverse-proxied deployments")
    if env_map.get("ENABLE_SIGNUP", "true").lower() != "false":
        issues.append("ENABLE_SIGNUP defaults to true; set ENABLE_SIGNUP=false after creating admin")
    if not env_map.get("WEBUI_SECRET_KEY", "").strip():
        issues.append("WEBUI_SECRET_KEY is empty; sessions reset across container restarts")
    bind = env_map.get("HOST_PORT_BIND", "")
    if not bind.startswith("127.0.0.1:"):
        issues.append("container port mapping should bind to 127.0.0.1, not 0.0.0.0")
    return issues

On November 6, 2026, we ran this preflight helper on Python 3.13.5 against both a broken default snippet (proxy_pass http://0.0.0.0:3000; with default environment variables) and our hardened loopback configuration, then validated the complete standalone config with nginx -t -c on nginx/1.26.3:

Valid conf issues: []
Broken conf issues: ['proxy_pass must target a loopback upstream (127.0.0.1)', 'missing proxy_http_version 1.1; (Nginx defaults to 1.0)', 'missing proxy_set_header Upgrade $http_upgrade;', 'missing proxy_set_header Connection header for WebSocket upgrade', 'missing proxy_buffering off; (token streaming will buffer)']
Default env issues: ['ENABLE_SIGNUP defaults to true; set ENABLE_SIGNUP=false after creating admin', 'WEBUI_SECRET_KEY is empty; sessions reset across container restarts', 'container port mapping should bind to 127.0.0.1, not 0.0.0.0']
Hardened env issues: []
nginx -t exit: 0
nginx: the configuration file /tmp/tmpo4ftqtff/nginx.conf syntax is ok
nginx: configuration file /tmp/tmpo4ftqtff/nginx.conf test is successful

Note what this local verification proves and what it does not: nginx -t and the Python preflight verify configuration syntax and required directives on CPU without starting a public TLS listener or running model inference. After deploying on your own host, verify with ss -tlnp that port 3000 is bound only to 127.0.0.1.

When should you skip a public Nginx reverse proxy altogether?

Exposing an Nginx port to the public internet is not always the right architecture for a home lab or small team:

  • You only access Open WebUI from your own laptop and phone: Use a private mesh VPN (such as WireGuard or Tailscale) and bind Nginx or Open WebUI to the private interface instead of opening port 443 on your router.
  • You need an OpenAI-compatible API gateway for coding tools rather than a browser chat UI: Do not expose Open WebUI just to route IDE requests. Use a loopback-bound proxy as shown in our LiteLLM and Ollama local-only proxy setup, and keep your coding agent edits constrained with our sister site’s AI coding file-scope guard tutorial.
  • You are evaluating whether to upgrade or customize Open WebUI: Read our Open WebUI review and Open WebUI 0.11 self-hosted guide first, and remember Clause 4 of the Open WebUI License if your deployment grows past 50 users in 30 days and you plan to alter branding.

Honest take: fix the loopback bind and ENABLE_SIGNUP before touching TLS

Most broken or compromised self-hosted Open WebUI setups fail on two defaults: Nginx’s HTTP/1.0 buffered proxy behavior and Open WebUI’s ENABLE_SIGNUP=true registration default. Binding the container to 127.0.0.1:3000:8080, adding proxy_http_version 1.1, Upgrade/Connection, and proxy_buffering off in Nginx, and flipping ENABLE_SIGNUP=false after creating your admin account solves both problems in five minutes with zero external services. Always run nginx -t and inspect active listening sockets after every container or proxy update.

FAQ

Why does Open WebUI token streaming freeze behind Nginx?

By default, Nginx uses proxy_http_version 1.0 and proxy_buffering on, which buffers upstream HTTP chunks and drops WebSocket upgrade headers. Adding proxy_http_version 1.1;, the Upgrade and Connection headers, and proxy_buffering off; fixes both live token streaming and WebSocket connections.

Can strangers create accounts on my proxied Open WebUI instance?

Yes, if you leave the default settings untouched. In Open WebUI’s backend/open_webui/config.py, ENABLE_SIGNUP defaults to True whenever WEBUI_AUTH is enabled. After registering your initial admin account, set ENABLE_SIGNUP=false and restart the container.

Is Open WebUI still MIT-licensed in 2026?

No. Current main uses the Open WebUI License, which adds Clause 4 restricting removal or modification of “Open WebUI” branding for deployments exceeding 50 users within a rolling 30-day period unless you hold a separate commercial agreement.

Sources and verification

Last updated November 6, 2026. Open WebUI source inspected at commit c38885a1a737da4968f157092f304f3b5481d0bf and configuration syntax verified locally with nginx/1.26.3 and Python 3.13.5 on this date.

Was this article helpful?

What self-hosting actually costs

Real cost breakdowns for self-hosted AI: hardware floors, power, maintenance hours, and the honest comparison against paying for it. No spam, unsubscribe anytime.