> ## Documentation Index
> Fetch the complete documentation index at: https://chatbotx.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Nginx

> Configure Nginx as a reverse proxy for ChatbotX.

ChatbotX exposes three services that need to be proxied:

| Service                       | Internal port | Recommended subdomain |
| ----------------------------- | ------------- | --------------------- |
| **Builder** (web UI + API)    | `3123`        | `app.yourdomain.com`  |
| **PartySocket** (WebSocket)   | `1999`        | `ws.yourdomain.com`   |
| **Storage** (S3 API + assets) | `9000`        | `cdn.yourdomain.com`  |

<Note>
  The steps below are written for **Ubuntu / Debian**. Package and path names differ slightly on RHEL-based distros (`dnf` instead of `apt`, `/etc/nginx/conf.d/` instead of `/etc/nginx/sites-available/`).
</Note>

***

## Installation

<Steps>
  <Step title="Install Nginx and Certbot">
    ```bash theme={null}
    sudo apt update
    sudo apt install -y nginx certbot python3-certbot-nginx
    ```
  </Step>

  <Step title="Create the webroot directory">
    ```bash theme={null}
    sudo mkdir -p /var/www/certbot
    ```
  </Step>

  <Step title="Create a temporary HTTP config">
    Create `/etc/nginx/sites-available/chatbotx` with a minimal config that lets Certbot complete the ACME challenge:

    ```nginx /etc/nginx/sites-available/chatbotx theme={null}
    server {
        listen 80;
        server_name app.yourdomain.com ws.yourdomain.com cdn.yourdomain.com;

        location /.well-known/acme-challenge/ {
            root /var/www/certbot;
        }
    }
    ```

    Enable it and reload:

    ```bash theme={null}
    sudo ln -s /etc/nginx/sites-available/chatbotx /etc/nginx/sites-enabled/chatbotx
    sudo nginx -t && sudo systemctl reload nginx
    ```
  </Step>

  <Step title="Obtain TLS certificates">
    ```bash theme={null}
    sudo certbot certonly --webroot -w /var/www/certbot \
      -d app.yourdomain.com \
      -d ws.yourdomain.com \
      -d cdn.yourdomain.com \
      --email you@yourdomain.com \
      --agree-tos --non-interactive
    ```

    Certificates are saved to `/etc/letsencrypt/live/<domain>/`.
  </Step>

  <Step title="Replace with the full HTTPS config">
    Overwrite `/etc/nginx/sites-available/chatbotx` with the configuration below.

    <CodeGroup>
      ```nginx Secure (recommended) theme={null}
      # ── Builder ────────────────────────────────────────────────
      server {
          listen 80;
          server_name app.yourdomain.com;
          return 301 https://$host$request_uri;
      }

      server {
          listen 443 ssl;
          server_name app.yourdomain.com;

          ssl_certificate     /etc/letsencrypt/live/app.yourdomain.com/fullchain.pem;
          ssl_certificate_key /etc/letsencrypt/live/app.yourdomain.com/privkey.pem;
          ssl_protocols       TLSv1.2 TLSv1.3;
          ssl_ciphers         HIGH:!aNULL:!MD5;

          client_max_body_size 100M;

          add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
          add_header X-Content-Type-Options nosniff always;
          add_header X-Frame-Options DENY always;

          location / {
              proxy_pass         http://localhost:3123;
              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;
          }
      }

      # ── PartySocket ────────────────────────────────────────────
      server {
          listen 80;
          server_name ws.yourdomain.com;
          return 301 https://$host$request_uri;
      }

      server {
          listen 443 ssl;
          server_name ws.yourdomain.com;

          ssl_certificate     /etc/letsencrypt/live/ws.yourdomain.com/fullchain.pem;
          ssl_certificate_key /etc/letsencrypt/live/ws.yourdomain.com/privkey.pem;
          ssl_protocols       TLSv1.2 TLSv1.3;
          ssl_ciphers         HIGH:!aNULL:!MD5;

          location / {
              proxy_pass          http://localhost:1999;
              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_read_timeout  3600s;
              proxy_send_timeout  3600s;
          }
      }

      # ── Storage ────────────────────────────────────────────────
      server {
          listen 80;
          server_name cdn.yourdomain.com;
          return 301 https://$host$request_uri;
      }

      server {
          listen 443 ssl;
          server_name cdn.yourdomain.com;

          ssl_certificate     /etc/letsencrypt/live/cdn.yourdomain.com/fullchain.pem;
          ssl_certificate_key /etc/letsencrypt/live/cdn.yourdomain.com/privkey.pem;
          ssl_protocols       TLSv1.2 TLSv1.3;
          ssl_ciphers         HIGH:!aNULL:!MD5;

          client_max_body_size 1G;

          location / {
              proxy_pass         http://localhost:9000;
              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;
          }
      }
      ```

      ```nginx Simple theme={null}
      server {
          listen 80;
          server_name app.yourdomain.com ws.yourdomain.com cdn.yourdomain.com;
          return 301 https://$host$request_uri;
      }

      server {
          listen 443 ssl;
          server_name app.yourdomain.com;

          ssl_certificate     /etc/letsencrypt/live/app.yourdomain.com/fullchain.pem;
          ssl_certificate_key /etc/letsencrypt/live/app.yourdomain.com/privkey.pem;

          client_max_body_size 100M;

          location / {
              proxy_pass http://localhost:3123;
              proxy_set_header Host $host;
              proxy_set_header X-Forwarded-Proto $scheme;
          }
      }

      server {
          listen 443 ssl;
          server_name ws.yourdomain.com;

          ssl_certificate     /etc/letsencrypt/live/ws.yourdomain.com/fullchain.pem;
          ssl_certificate_key /etc/letsencrypt/live/ws.yourdomain.com/privkey.pem;

          location / {
              proxy_pass         http://localhost:1999;
              proxy_http_version 1.1;
              proxy_set_header   Upgrade    $http_upgrade;
              proxy_set_header   Connection "upgrade";
              proxy_set_header   Host       $host;
          }
      }

      server {
          listen 443 ssl;
          server_name cdn.yourdomain.com;

          ssl_certificate     /etc/letsencrypt/live/cdn.yourdomain.com/fullchain.pem;
          ssl_certificate_key /etc/letsencrypt/live/cdn.yourdomain.com/privkey.pem;

          client_max_body_size 1G;

          location / {
              proxy_pass http://localhost:9000;
              proxy_set_header Host $host;
              proxy_set_header X-Forwarded-Proto $scheme;
          }
      }
      ```
    </CodeGroup>
  </Step>

  <Step title="Test and reload Nginx">
    ```bash theme={null}
    sudo nginx -t && sudo systemctl reload nginx
    ```
  </Step>

  <Step title="Enable automatic certificate renewal">
    Certbot installs a systemd timer by default. Verify it is active:

    ```bash theme={null}
    sudo systemctl status certbot.timer
    ```

    Add a reload hook so Nginx picks up renewed certificates automatically:

    ```bash theme={null}
    echo -e '#!/bin/sh\nsystemctl reload nginx' | sudo tee /etc/letsencrypt/renewal-hooks/post/reload-nginx.sh
    sudo chmod +x /etc/letsencrypt/renewal-hooks/post/reload-nginx.sh
    ```
  </Step>
</Steps>

***

## ChatbotX configuration

Update your `.env` to use the public HTTPS URLs and restart the services:

```bash .env theme={null}
NEXT_PUBLIC_BUILDER_URL=https://app.yourdomain.com
NEXT_PUBLIC_PARTYSOCKET_URL=https://ws.yourdomain.com
BETTER_AUTH_URL=https://app.yourdomain.com
NEXT_PUBLIC_ASSET_URL=https://cdn.yourdomain.com/chatbotx/public/
S3_ENDPOINT=https://cdn.yourdomain.com
```

<Warning>
  `NEXT_PUBLIC_BUILDER_URL` and `BETTER_AUTH_URL` must match exactly. A mismatch causes authentication callbacks to fail.
</Warning>

***

## WebSocket support

PartySocket requires the `Upgrade` and `Connection` headers to be forwarded. The secure config above includes these on the `ws.yourdomain.com` server block. The extended `proxy_read_timeout` and `proxy_send_timeout` values (3600s) prevent Nginx from closing long-lived WebSocket connections.

***

## Storage (RustFS / S3)

The storage service runs on port `9000`. Proxying it through Nginx gives you HTTPS asset URLs. Set `client_max_body_size` to a value larger than your largest expected upload — the example uses `1G`.

<Note>
  The storage console (port `9001`) does not need to be publicly exposed. Access it directly on the server via an SSH tunnel or restrict it to a local interface.
</Note>
