Skip to content
Chatvia Docs

Installation

Install on Ubuntu

Get in touch: Ahmad Mayahi, creator of Chatvia, Spectra and Shakomako

This page installs Chatvia on one Ubuntu server, step by step, without Docker: PostgreSQL with the search extensions, Redis, PHP-FPM behind nginx, an HTTPS certificate, the queue workers (Horizon), the WebSocket server (Reverb), Pulse's server stats and the scheduler. At the end, Chatvia runs at https://chat.example.com.

This is the install we recommend: one VPS (a virtual private server that you control). For now, Chatvia needs Redis and Laravel Horizon, and they must run all the time. This page sets up both. See Where to install and Redis and Horizon are required.

The commands are for Ubuntu 26.04 LTS, which has PostgreSQL 18 in its own package archive. PHP comes from the ondrej/php archive: Chatvia needs PHP 8.5.12 or newer, and Ubuntu's own php8.5 keeps the version Ubuntu shipped with. Node.js comes from NodeSource. On Ubuntu 24.04 LTS, PostgreSQL comes from another archive too: see Ubuntu 24.04.

Install with one command

On a fresh Ubuntu 24.04 LTS or 26.04 LTS server, the installer does everything on this page for you, and sets up Deployer too:

bash
curl -fsSL https://chatvia.ai/install.sh | bash

Choose Complete installation when it asks how to install Chatvia (or pass --complete). It asks for the admin e-mail and password, the domain name, whether to get an HTTPS certificate and turn on the firewall, and whether to turn on automatic updates. On a server with less than 6 GB of memory and no swap, it offers to add swap. Custom settings let you choose the embedding storage, the search index and the version. The recommended ones suit almost every install. Then it installs and sets up every part on this page, deploys the newest release with Deployer into /var/www/chatvia, creates your admin account and shows where to sign in. It also sizes PostgreSQL and the queue workers to the server's memory and processors, protects SSH with fail2ban, and takes the newest Redis from packages.redis.io when it carries that Ubuntu release.

For HTTPS, the installer puts Caddy on ports 80 and 443, in front of nginx. Caddy gets the certificate from Let's Encrypt and renews it by itself. It also gets a certificate for each agent's custom domain the first time the domain is used. nginx then listens on 127.0.0.1:8081, and only Caddy talks to it. An install from before Caddy keeps Certbot until you move it: see Custom domains. It runs as root: started as another user, it asks for your sudo password.

Only on a fresh server

The installer changes the settings of nginx, PostgreSQL, Redis, PHP and the firewall for Chatvia, and takes over ports 80 and 443. Other sites and programs on the server may stop working. Use a server that runs nothing else (PostgreSQL is fine, see below), or run Chatvia with Docker.

On a server that already runs PostgreSQL

When PostgreSQL already runs on the server, the installer uses it. It does not install a second PostgreSQL, and it does not change the settings of yours. To use a PostgreSQL server on another machine, pass --db-host db.example.com.

The installer asks for:

Question Default
PostgreSQL host 127.0.0.1 (the server itself)
PostgreSQL port The port your PostgreSQL uses, else 5432
Database name chatvia
Database user chatvia
Password None: type the password of that user

The user must exist already. ChatVia.ai uses four databases: the one you name and three next to it, named after it. With the name chatvia, they are chatvia, chatvia_analytics, chatvia_pulse and chatvia_spectra.

Before it changes anything, the installer connects and checks that:

  • PostgreSQL is version 18 or newer.
  • The server has the extensions pgvector, pg_textsearch and pg_partman. On Ubuntu, pgvector and pg_partman come from the packages postgresql-18-pgvector and postgresql-18-partman. pg_textsearch comes from its GitHub releases (see step 2).
  • pg_textsearch is in shared_preload_libraries, so PostgreSQL loads it when it starts.
  • The database user may create what is still missing. A user with the right to create databases gets the four databases made for it. Some extensions can only be created by a superuser.

If a check fails, the installer stops and says what to fix. When only a superuser can do a step, it prints the SQL for a superuser to run in psql. Run it, then start the installer again.

The database you name must be empty, or hold an earlier ChatVia.ai install. A database with another app's tables stops the install.

Run the installer again later:

  • To upgrade. It backs up the database to /var/backups/chatvia, then deploys the newest release. When the deploy fails, the running release is not touched.
  • To finish an install that stopped. It goes on where it stopped, with the answers you gave the first time. They are saved in /etc/chatvia/install.conf, and the log is in /var/log/chatvia-install.log.

Automatic updates

The installer also sets up chatvia-auto-update, a small script that keeps Chatvia up to date for you. It is on unless you say no during the install.

Once a night, at a time between 2:00 and 5:00 (server time), it checks GitHub for a newer release. When there is one, it runs the installer's upgrade: it backs up the database to /var/backups/chatvia, then deploys the new release with Deployer. If the deploy fails, the running release is not touched, and the script tries again the next night.

It only installs releases of the major version you run. 1.4.2 goes to 1.5.0, but never to 2.0.0, because a new major version may need steps of its own. When a new major version is out, the script writes that in its log. Read the changelog, then upgrade by hand: run the installer again.

To see what it does, or to turn it off or on:

bash
sudo chatvia-auto-update status
Command What it does
sudo chatvia-auto-update status Shows if automatic updates are on, the installed version and the last lines of the log.
sudo chatvia-auto-update run Looks for a newer release now, and installs it.
sudo chatvia-auto-update off Turns automatic updates off.
sudo chatvia-auto-update on Turns them on again.

Its log is /var/log/chatvia-auto-update.log. The details of each update are in /var/log/chatvia-install.log. The nightly run is in /etc/cron.d/chatvia-auto-update. An install of a branch (--version main) has no releases, so the script does nothing there.

To install without questions, pass the settings as options:

bash
curl -fsSL https://chatvia.ai/install.sh | bash -s -- --complete --yes \
  --email [email protected] --domain chat.example.com

The admin password is a random one, shown at the end, unless you set CHATVIA_ADMIN_PASSWORD. --no-https skips the certificate, --no-firewall leaves the firewall as it is, --no-auto-update leaves automatic updates off, and --force installs on a server that does not look fresh. --yes --upgrade upgrades without questions. --help lists every option, and --dry-run shows what would happen without changing anything.

With an existing PostgreSQL, set the database user's password in CHATVIA_DB_PASSWORD. --db-host, --db-port, --db-name and --db-user set the rest. Without them, the installer uses 127.0.0.1, the port of the PostgreSQL on the server, and chatvia for the database and the user:

bash
curl -fsSL https://chatvia.ai/install.sh | CHATVIA_DB_PASSWORD='the password' bash -s -- \
  --complete --yes --email [email protected] --domain chat.example.com --db-host db.example.com

At the end, the installer asks if it may send an anonymous install count to chatvia.ai. It sends only how you installed (Docker or complete) and the operating system: no address, no ID and no settings. The answer is no unless you choose yes. --count-install sends it without asking, and --no-count-install skips the question. With --yes alone, nothing is sent.

Then it asks if you want news about ChatVia.ai (new versions and other updates) at the admin e-mail address. The address is kept apart from the install count. The answer is yes unless you choose no. --news subscribes without asking, and --no-news skips the question. With --yes alone, no address is sent.

After the install, finish in the admin panel (step 13). The code runs from /var/www/chatvia/current, as with Deployer: where this page says /var/www/chatvia, use that path. Deployer is set up for the chatvia user, in /home/chatvia/deploy.

The rest of this page does the same by hand.

Want updates without downtime?

This page puts the code in one folder that you update with git. To deploy each new release into its own folder and switch over in one step, do steps 1 to 4 here, then follow Deploy with Deployer.

Before you start

You need:

  • A VPS or another server you control, with Ubuntu 26.04 LTS (or 24.04 LTS, see Ubuntu 24.04). Shared web hosting does not work. 2 CPU cores and 4 GB of memory are a good start. Building the frontend takes up to 3 GB of memory for a few seconds, next to the app that keeps running. With less than 6 GB, add swap first (see below). Plan for 20 GB of disk or more: your knowledge files and the database grow with use.
  • A domain name whose DNS record points at the server, for example chat.example.com. The HTTPS certificate in step 9 needs it.
  • A user with sudo that you sign in with over SSH. Every command below starts as that user.
  • An API key for an AI provider. One Scaleway key covers chat and embeddings with the default settings. You enter it in step 13.

Replace chat.example.com everywhere with your own domain.

Add swap

Swap lets the build finish on a server with little memory:

bash
sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

How the parts fit together

Part Runs as Listens on
nginx www-data Ports 80 and 443, open to the internet.
PHP-FPM, the chatvia pool chatvia A socket, /run/php/chatvia.sock.
Horizon (queue workers) chatvia, kept running by Supervisor Nothing. It takes jobs from Redis.
Reverb (WebSocket server) chatvia, kept running by Supervisor 127.0.0.1:8080. nginx passes /app/ to it.
Pulse (server stats) chatvia, kept running by Supervisor Nothing. It writes to its own database.
Scheduler chatvia, every minute from cron Nothing.
PostgreSQL 18 postgres 127.0.0.1:5432
Redis redis 127.0.0.1:6379, with a password.

Only nginx is reachable from outside. One system user, chatvia, owns the code and runs every part of the app, so files it writes are always readable by the next part.

1. Install the packages

bash
sudo apt update
sudo apt upgrade -y

sudo apt install -y git curl unzip ca-certificates cron software-properties-common

# PHP 8.5.12 or newer comes from ppa:ondrej/php
sudo add-apt-repository -y ppa:ondrej/php
sudo apt update

# PHP 8.5 with the extensions Chatvia needs, and Composer
sudo apt install -y php8.5-fpm php8.5-cli php8.5-pgsql php8.5-redis php8.5-intl \
  php8.5-mbstring php8.5-curl php8.5-zip php8.5-gd php8.5-xml composer

# The web server, Redis, the process manager and the SSH guard
sudo apt install -y nginx redis-server supervisor fail2ban

# Programs that read file sources, and WeasyPrint for PDF exports
sudo apt install -y poppler-utils pandoc catdoc ffmpeg weasyprint \
  fonts-noto-core fonts-noto-cjk

# Programs for backups: pg_dump and pg_restore, redis-cli and gpg
sudo apt install -y postgresql-client-18 redis-tools gnupg

The backup programs make the backups in Settings → Backups. pg_dump must be the version of your PostgreSQL server or newer, so install the client of the same version (18).

The programs that read file sources are optional: you only need the ones for the file types you plan to upload (see Programs for some file types).

Node.js 22 builds the frontend and the chat widget. Get it from NodeSource, not from Ubuntu's nodejs and npm packages: Ubuntu builds Node.js without TypeScript support, and the chat widget's build runs TypeScript files directly. The last file makes apt take NodeSource's nodejs over Ubuntu's:

bash
sudo install -d -m 755 /etc/apt/keyrings
sudo curl -fsSL -o /etc/apt/keyrings/nodesource.asc \
  https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key
echo 'deb [signed-by=/etc/apt/keyrings/nodesource.asc] https://deb.nodesource.com/node_22.x nodistro main' \
  | sudo tee /etc/apt/sources.list.d/nodesource.list
printf 'Package: nodejs\nPin: origin deb.nodesource.com\nPin-Priority: 1001\n' \
  | sudo tee /etc/apt/preferences.d/nodesource

sudo apt update
sudo apt install -y nodejs

NodeSource's nodejs includes npm. Check the versions:

bash
php -v
node -v
node -p process.features.typescript
composer --version

PHP must be 8.5.12 or newer, and Node.js 22.18 or newer. The third line prints strip: this Node.js runs TypeScript files. If it prints false, Ubuntu's nodejs is still installed. Remove it with sudo apt remove nodejs npm, then install NodeSource's again.

If apt update says the ondrej/php archive has no Release file, it does not carry your Ubuntu release yet. Use Docker until it does.

2. Set up PostgreSQL

Chatvia needs PostgreSQL 18 with two extensions: pgvector (vector) for search by meaning, and pg_textsearch for search by keywords (BM25 ranking). A third, pg_partman, is optional: it splits the analytics event log into one table per month, so old months are removed in one step. pgvector and pg_partman come from Ubuntu. pg_textsearch comes as a package from its GitHub releases:

bash
sudo apt install -y postgresql-18 postgresql-18-pgvector postgresql-18-partman

cd /tmp
arch=$(dpkg --print-architecture)
curl -fLO "https://github.com/timescale/pg_textsearch/releases/download/v1.5.1/pg-textsearch-v1.5.1-pg18-${arch}.zip"
unzip -o "pg-textsearch-v1.5.1-pg18-${arch}.zip"
sudo apt install -y "./pg-textsearch-postgresql-18_1.5.1-1_${arch}.deb"
cd ~

This is the version in the ghcr.io/chatvia/postgres image that the Docker install uses. The package works on amd64 and arm64 servers.

pg_textsearch must be loaded when PostgreSQL starts:

bash
echo "shared_preload_libraries = 'pg_textsearch'" \
  | sudo tee /etc/postgresql/18/main/conf.d/chatvia.conf
sudo systemctl restart postgresql
sudo -u postgres psql -c "SHOW shared_preload_libraries;"

The last command shows pg_textsearch. Without it, Chatvia still works, but keyword search uses PostgreSQL's built-in full-text search, which ranks results less well.

PostgreSQL's own sizes suit a machine far smaller than any server. Append sizes for yours to the same file. These are the usual numbers for a web app on SSD storage. For a server with 8 GB of memory and 4 processors that also runs Chatvia:

bash
sudo tee -a /etc/postgresql/18/main/conf.d/chatvia.conf <<'EOF'
shared_buffers = 2GB
effective_cache_size = 6GB
maintenance_work_mem = 512MB
work_mem = 10MB
checkpoint_completion_target = 0.9
random_page_cost = 1.1
effective_io_concurrency = 200
max_parallel_workers = 4
max_parallel_workers_per_gather = 2
EOF
sudo systemctl restart postgresql

A quarter of the memory for shared_buffers, three quarters as effective_cache_size, and a sixteenth for maintenance_work_mem (which the search index builds use) are good starting points. The one-command installer computes these for your server by itself.

Now create a user for Chatvia and its four databases: chatvia for the app, analytics for the numbers behind the charts and reports, and pulse and spectra for Pulse (the app's health, at /pulse) and Spectra (the log of AI calls, at /spectra), which keep their data apart. The migrations in step 7 stop when one of them is missing. Make a password first, and keep it for the .env file in step 5:

bash
openssl rand -hex 24
bash
sudo -u postgres psql
sql
CREATE ROLE chatvia LOGIN SUPERUSER PASSWORD 'the-password-you-made';
CREATE DATABASE chatvia OWNER chatvia;
CREATE DATABASE analytics OWNER chatvia;
CREATE DATABASE pulse OWNER chatvia;
CREATE DATABASE spectra OWNER chatvia;
\q

The role is a superuser only for now: the first migration (step 7) installs the extensions, which needs that right. Step 7 takes it away again.

PostgreSQL only listens on 127.0.0.1, so nobody outside the server can reach it.

3. Set up Redis

Redis holds the queues, the cache, the sessions, the rate limits, the locks and the live chat history. Chatvia needs it for now: no other cache store or queue system works yet. Ubuntu 26.04's own package is Redis 8, which is fine. For the newest Redis, add the official repository first, when it carries your Ubuntu release. . /etc/os-release && echo $VERSION_CODENAME names your release, and the repository lists its releases under packages.redis.io/deb/dists:

bash
curl -fsSL https://packages.redis.io/gpg | sudo tee /usr/share/keyrings/redis-archive-keyring.asc >/dev/null
echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.asc] https://packages.redis.io/deb $(. /etc/os-release && echo $VERSION_CODENAME) main" \
  | sudo tee /etc/apt/sources.list.d/redis.list
sudo apt update
sudo apt install -y redis-server

Ubuntu's Redis already listens only on 127.0.0.1. Give it a password, and let it write every change to disk, so queued jobs survive a restart:

bash
openssl rand -hex 24
sudo sed -i 's/^# requirepass .*/requirepass the-password-you-made/' /etc/redis/redis.conf
sudo sed -i 's/^appendonly no/appendonly yes/' /etc/redis/redis.conf
sudo systemctl restart redis-server
redis-cli -a 'the-password-you-made' --no-auth-warning ping

The last command answers PONG. Keep the password for step 5.

4. Create the chatvia user

bash
sudo adduser --system --group --shell /bin/bash --home /home/chatvia chatvia
sudo mkdir -p /var/www/chatvia
sudo chown chatvia:chatvia /var/www/chatvia

--system makes a user without a password: you switch to it with sudo, and Deployer signs in with an SSH key.

5. Get the code and create .env

Switch to the chatvia user. The commands up to step 7 run as that user:

bash
sudo -iu chatvia

Get the code, and check out the newest release. Releases are the vX.Y.Z tags, and the changelog says what changed.

bash
git clone https://github.com/chatvia/chatvia.git /var/www/chatvia
cd /var/www/chatvia
git tag --sort=-v:refname | head -n 1
git checkout v1.0.0

Use the tag the third command printed in the last one.

Laravel needs three folders that are not in the repository:

bash
mkdir -p storage/framework/cache/data storage/framework/sessions storage/framework/views

Create .env from the example, and make it readable by chatvia only:

bash
cp .env.example .env
chmod 600 .env
nano .env

.env.example has a short comment on every setting. Change these lines. Where a line starts with #, remove the #:

dotenv
APP_ENV=production
APP_DEBUG=false
APP_URL=https://chat.example.com

# One log file per day, kept for 14 days
LOG_STACK=daily
LOG_LEVEL=info

DB_PASSWORD=the-database-password-from-step-2

REDIS_PASSWORD=the-redis-password-from-step-3

# Reverb: three random values (openssl rand -hex 16)
REVERB_APP_ID=a-random-value
REVERB_APP_KEY=a-random-value
REVERB_APP_SECRET=a-random-value
# Where the app reaches Reverb: on this server, not through the internet
REVERB_HOST=127.0.0.1
REVERB_PORT=8080
REVERB_SCHEME=http
# Where Reverb listens
REVERB_SERVER_HOST=127.0.0.1
REVERB_SERVER_PORT=8080

# Empty: browsers and the chat widget connect to APP_URL,
# and nginx passes /app/ to Reverb (step 9)
VITE_REVERB_APP_KEY=
VITE_REVERB_HOST=
VITE_REVERB_PORT=
VITE_REVERB_SCHEME=

# A long random value (openssl rand -hex 32). The built-in tools stop working without it.
BUILTIN_MCP_SECRET=a-long-random-value

SESSION_SECURE_COOKIE=true

Keep the other lines as they are. The database name and user (chatvia), the Redis address and QUEUE_CONNECTION=redis already match this server.

Decide on the embedding storage now

EMBEDDING_VECTOR_TYPE and EMBEDDING_DIMENSIONS can only be set before step 7 creates the tables. The defaults suit almost every install. See Embedding storage.

E-mail, API keys and most other settings are not in .env: you set them in the admin panel in step 13.

6. Install the packages and build

Still as chatvia, in /var/www/chatvia:

bash
composer install --no-dev --optimize-autoloader
php artisan key:generate

npm ci --no-audit --no-fund
npm run build
npm run shakomako:build
Command What it does
composer install Installs the PHP packages. It stops with a clear message when a PHP extension is missing.
php artisan key:generate Writes a new APP_KEY into .env. It encrypts the secrets in the database: back up .env and never change the key.
npm ci, npm run build Build the dashboard and the chat pages into public/build.
npm run shakomako:build Downloads the newest release of Shakomako, the chat widget, and builds it into public/shakomako.

7. Create the database tables

bash
php artisan chatvia:deploy:migrate
php artisan storage:link
php artisan optimize
Command What it does
chatvia:deploy:migrate Prepares the database. The Docker image and Deployer run the same command. See the list below.
storage:link Lets nginx serve asset files (profile photos, widget logos) from /storage.
optimize Caches the configuration, routes, events and views. Run it again after every change to .env.

chatvia:deploy:migrate does these steps, in order, and stops when one fails:

  1. Checks that the analytics, Pulse and Spectra databases from step 2 are there, and creates any that are missing.
  2. Creates the tables. The first migration installs the PostgreSQL extensions.
  3. Checks that the vector columns match EMBEDDING_VECTOR_TYPE and EMBEDDING_DIMENSIONS.
  4. Rebuilds the index for search by meaning when EMBEDDING_INDEX changed. It does nothing when the index already matches.
  5. Adds the built-in tools to the tool catalog. They need your embedding API key, which you add in step 13. Until then, this step only shows a warning, and the setup in step 13 adds them.

Go back to your own user, and take the superuser right away from the database user:

bash
exit
sudo -u postgres psql -c "ALTER ROLE chatvia NOSUPERUSER;"

Chatvia only needed it to install the extensions. If a later upgrade fails with permission denied to create extension, give the right back for that one migration (ALTER ROLE chatvia SUPERUSER;), then take it away again.

8. Set up PHP-FPM

Chatvia gets two PHP-FPM pools that run as chatvia: chatvia for pages and chatvia-streams for chat answers. Create /etc/php/8.5/fpm/pool.d/chatvia.conf:

bash
sudo nano /etc/php/8.5/fpm/pool.d/chatvia.conf
ini
[chatvia]
user = chatvia
group = chatvia

listen = /run/php/chatvia.sock
listen.owner = www-data
listen.group = www-data

pm = dynamic
pm.max_children = 10
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6
pm.max_requests = 500

php_admin_value[memory_limit] = 512M
php_admin_value[max_execution_time] = 300
; Knowledge files can be up to 50 MB.
php_admin_value[upload_max_filesize] = 64M
php_admin_value[post_max_size] = 64M

; Chat answers. Each one keeps a worker busy until it ends, so they get
; workers of their own and a busy chat cannot make pages wait.
[chatvia-streams]
user = chatvia
group = chatvia

listen = /run/php/chatvia-streams.sock
listen.owner = www-data
listen.group = www-data

pm = dynamic
pm.max_children = 20
pm.start_servers = 1
pm.min_spare_servers = 1
pm.max_spare_servers = 2
pm.max_requests = 500

php_admin_value[memory_limit] = 512M
php_admin_value[max_execution_time] = 300
php_admin_value[upload_max_filesize] = 64M
php_admin_value[post_max_size] = 64M

Give OPcache room for all of Chatvia's PHP files (about 30,000):

bash
sudo tee /etc/php/8.5/fpm/conf.d/99-chatvia.ini > /dev/null <<'EOF'
opcache.memory_consumption = 256
opcache.interned_strings_buffer = 32
opcache.max_accelerated_files = 65407
EOF

Check the settings and restart PHP-FPM:

bash
sudo php-fpm8.5 -t
sudo systemctl restart php8.5-fpm

pm.max_children is how many requests a pool can answer at the same time. The chatvia pool answers pages, uploads and API calls. The chatvia-streams pool answers chat messages: in the hosted chat, the chat widget, the dashboard's analytics chat and the API. A chat answer keeps its worker until it ends, so a busy chat can fill chatvia-streams, but pages still open.

Each worker can use up to 512 MB, but most use far less. A busy worker also holds one database connection, and PostgreSQL allows 100 unless you raise max_connections. On a bigger server, raise pm.max_children of chatvia-streams when visitors wait for answers to start.

9. Set up nginx

Create the site:

bash
sudo nano /etc/nginx/sites-available/chatvia
nginx
server {
    listen 80;
    listen [::]:80;
    server_name chat.example.com;

    root /var/www/chatvia/public;
    index index.php;
    charset utf-8;

    # Knowledge files can be up to 50 MB.
    client_max_body_size 64M;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    # Live updates: the browser's WebSocket connections go to Reverb.
    location ^~ /app/ {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        proxy_set_header Host $http_host;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        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 120s;
    }

    # The chat widget. Other websites load it, so every file allows any origin.
    location ^~ /shakomako/ {
        add_header Access-Control-Allow-Origin "*" always;
        add_header Cache-Control "public, max-age=31536000, immutable" always;
        try_files $uri =404;
    }

    location = /shakomako/embed.js {
        add_header Access-Control-Allow-Origin "*" always;
        add_header Cache-Control "public, max-age=300" always;
        try_files $uri =404;
    }

    location = /shakomako/version.json {
        add_header Access-Control-Allow-Origin "*" always;
        add_header Cache-Control "no-cache" always;
        try_files $uri =404;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    error_page 404 /index.php;

    # Chat answers go to their own PHP workers (the chatvia-streams pool).
    location ~ ^/(?:chat/[^/]+/(?:stream/[^/]+|tools/[^/]+/resolve|conversations/[^/]+/messages/[^/]+/speech)|widget/stream|my/ask/stream|api/v1/agents/[^/]+/conversations/[^/]+/messages)/?$ {
        fastcgi_pass unix:/run/php/chatvia-streams.sock;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $realpath_root/index.php;
        fastcgi_param SCRIPT_NAME /index.php;
        fastcgi_param DOCUMENT_ROOT $realpath_root;
        fastcgi_hide_header X-Powered-By;
        fastcgi_read_timeout 300s;
        fastcgi_buffer_size 32k;
        fastcgi_buffers 16 32k;
    }

    location ~ ^/index\.php(/|$) {
        fastcgi_pass unix:/run/php/chatvia.sock;
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT $realpath_root;
        fastcgi_hide_header X-Powered-By;
        # Chat answers stream for a while.
        fastcgi_read_timeout 300s;
        fastcgi_buffer_size 32k;
        fastcgi_buffers 16 32k;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}

Turn it on, turn off nginx's default page, and reload:

bash
sudo ln -s /etc/nginx/sites-available/chatvia /etc/nginx/sites-enabled/chatvia
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx

Chat answers stream as server-sent events. Chatvia sends the header X-Accel-Buffering: no, so nginx passes each piece on at once. You need no extra setting for that.

HTTPS

Get a free certificate from Let's Encrypt. Certbot adds the HTTPS settings to the site, sends HTTP visitors to HTTPS, and renews the certificate by itself:

bash
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d chat.example.com

This gives your own address HTTPS. Agents' custom domains each need a certificate too, and Certbot only gets one when you run it. The installer uses Caddy instead, which gets them by itself. To set that up by hand, see Custom domains.

Firewall

Allow SSH and the web ports, then turn the firewall on:

bash
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable

PostgreSQL, Redis and Reverb listen on 127.0.0.1 only, so they need no rule.

fail2ban

fail2ban bans an address after repeated failed SSH sign-ins. Ubuntu logs SSH to the systemd journal, not to auth.log, so tell the jail to read the journal:

bash
sudo tee /etc/fail2ban/jail.d/chatvia.conf <<'EOF'
[sshd]
enabled = true
backend = systemd
EOF
sudo systemctl enable fail2ban
sudo systemctl restart fail2ban
sudo fail2ban-client status sshd

The last command shows the jail running, with the addresses it has banned.

10. Keep Horizon, Reverb and Pulse running

Supervisor starts the queue workers (Horizon), the WebSocket server (Reverb) and Pulse's server stats, and starts them again when they stop. Horizon is required for now: it is how Chatvia runs its queue workers, and the worker settings in the admin panel apply to it. Do not use php artisan queue:work in its place. Create /etc/supervisor/conf.d/chatvia.conf:

bash
sudo nano /etc/supervisor/conf.d/chatvia.conf
ini
[program:chatvia-horizon]
command=/usr/bin/php /var/www/chatvia/artisan horizon
directory=/var/www/chatvia
user=chatvia
environment=HOME="/home/chatvia"
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
stopwaitsecs=3600
redirect_stderr=true
stdout_logfile=/var/log/supervisor/chatvia-horizon.log

[program:chatvia-reverb]
command=/usr/bin/php /var/www/chatvia/artisan reverb:start
directory=/var/www/chatvia
user=chatvia
environment=HOME="/home/chatvia"
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
redirect_stderr=true
stdout_logfile=/var/log/supervisor/chatvia-reverb.log

[program:chatvia-pulse]
command=/usr/bin/php /var/www/chatvia/artisan pulse:check
directory=/var/www/chatvia
user=chatvia
environment=HOME="/home/chatvia"
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
redirect_stderr=true
stdout_logfile=/var/log/supervisor/chatvia-pulse.log

Before Supervisor starts Horizon, size the queue workers to this server. The command reads the memory and processors and saves a worker count for each kind of work, which Horizon reads when it starts:

bash
cd /var/www/chatvia
sudo -u chatvia php artisan chatvia:workers:tune

Change the numbers any time in the admin panel, under Settings → Platform → Queue workers. Now load the Supervisor programs:

bash
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl status

All three programs show RUNNING. stopwaitsecs=3600 gives a running job up to an hour to finish when you stop Horizon, so a long document is not cut off.

Process What it does
Horizon Reads files and web pages, sends replies on messaging channels, runs analytics and exports. Without it, sources stay on Pending.
Reverb Live updates in the browser: the progress of a document, human handoff, typing.
Pulse Records the server's CPU, memory and disk for the Servers card at /pulse. Without it, that card stays empty.
Many visitors at once?

Each WebSocket connection is an open file, and by default Supervisor's programs may open about 1,024 files. To allow more, set minfds=10000 in the [supervisord] section of /etc/supervisor/supervisord.conf and restart Supervisor with sudo systemctl restart supervisor.

11. Run the scheduler

The scheduler cleans up old data, analyses conversations, collects statistics and backs up the database. cron starts it every minute:

bash
echo '* * * * * chatvia cd /var/www/chatvia && php artisan schedule:run >> /dev/null 2>&1' \
  | sudo tee /etc/cron.d/chatvia

To see what it runs and when:

bash
sudo -u chatvia php /var/www/chatvia/artisan schedule:list

Backups stay on the server

The scheduler backs up the database and Redis once a day and keeps the newest 14 backups, on the private storage disk. Change this in the admin panel, under Settings → Backups. With the local disk, the backups stay on this server. Copy them somewhere else, or use object storage for the private disk (Settings → Storage). Back up .env too, or encrypt the backups with your key: the APP_KEY is needed to read the secrets in the database.

12. Create your admin account

bash
sudo -iu chatvia
cd /var/www/chatvia
php artisan chatvia:admin:create-user [email protected] "Your Name"
exit

The command asks for a password, so it does not end up in your shell history. The account is an admin with its own workspace and team.

13. Finish in the admin panel

  1. Open https://chat.example.com/admin and sign in.

  2. We recommend you set up two-factor authentication with an authenticator app, under your profile.

  3. The admin panel opens with the setup of a new install. It checks the server, then asks for:

    • AI provider: the main provider and its API key. The key is checked right away.
    • Models: the chat models agents answer with, and the embedding model, which turns text into search vectors.
    • Ingestion, Voice and Storage: optional services for reading PDF and Office files and importing websites, for speech, and for where files are kept.
    • Mailer: how Chatvia sends e-mail. You can skip it, but it must work before other people use the server: people who sign up confirm their address by e-mail, and invitations and sign-in links are sent by e-mail.
    • Observability: whether Spectra keeps a log of every AI call.

    Click Finish setup. This also adds the built-in tools (charts, forms, weather, currency, web search) to the tool catalog. You can change everything later under Settings, for example Settings → AI providers, Settings → AI models and Settings → Email.

  4. Under Settings → Workspaces, turn off open sign-up if only your own people should use the server.

Check that it works

  1. sudo supervisorctl status shows Horizon, Reverb and Pulse RUNNING.
  2. curl -I https://chat.example.com/up answers 200.
  3. Sign in at https://chat.example.com/login and create an agent.
  4. Add a short PDF or a text snippet as a source. Its status moves from Pending to Processing to Ingested without a page reload: that shows that Horizon and Reverb both work.
  5. Ask a question in the agent's Playground. The answer streams in.
  6. Open https://chat.example.com/shakomako/embed.js. The browser shows the widget's loader.

Logs

What Where
The app /var/www/chatvia/storage/logs/laravel-YYYY-MM-DD.log
Horizon, Reverb and Pulse /var/log/supervisor/chatvia-horizon.log, /var/log/supervisor/chatvia-reverb.log, /var/log/supervisor/chatvia-pulse.log
nginx /var/log/nginx/error.log, /var/log/nginx/access.log
PHP-FPM /var/log/php8.5-fpm.log
PostgreSQL /var/log/postgresql/postgresql-18-main.log

To follow the app log as it is written:

bash
sudo tail -f /var/www/chatvia/storage/logs/laravel-$(date +%F).log

Horizon has a dashboard at /horizon, and Pulse at /pulse. They open for an admin who is signed in to the admin panel.

Upgrade

Installed with the installer? Automatic updates install each new release of your major version for you. To upgrade now, or to a new major version, run the installer again: it backs up the database and deploys the newest release (see Install with one command).

For an install by hand, read the changelog of the new release first, and back up the database:

bash
sudo -u postgres pg_dump -Fc chatvia > chatvia-before-upgrade.dump

Then, as chatvia, check out the new tag, build, and migrate. The site shows a maintenance page in between:

bash
sudo -iu chatvia
cd /var/www/chatvia
php artisan down

git fetch --tags
git checkout v1.1.0

composer install --no-dev --optimize-autoloader
npm ci --no-audit --no-fund
npm run build
npm run shakomako:build

php artisan chatvia:deploy:migrate
php artisan optimize
php artisan reload

php artisan up
exit

php artisan reload restarts Horizon, Reverb and Pulse, so they run the new code. Supervisor starts them again by itself.

The site is down for a few minutes during an upgrade. To build each release in its own folder while the old one keeps running, and to roll back in one step, use Deployer.

Troubleshooting

Problem What to do
Please provide a valid cache path The storage/framework folders are missing. Create them (step 5).
could not find driver The php8.5-pgsql package is missing.
extension "vector" is not available The postgresql-18-pgvector package is not installed (step 2).
The Status page in the admin panel shows "Built-in full-text search" for keyword search pg_textsearch was not installed or not loaded when you migrated (step 2). Fix that, then, as a superuser, run CREATE EXTENSION pg_textsearch; in the chatvia database and php artisan chatvia:search:lexical-indexes.
permission denied to create extension The database user is not a superuser during the first migration. See step 7.
database "pulse" does not exist (or "analytics", "spectra") Create the analytics, Pulse and Spectra databases (step 2), then migrate again.
npm run shakomako:build fails with Unknown file extension ".ts" Node.js is Ubuntu's, which cannot run TypeScript files. Install Node.js from NodeSource (step 1).
NOAUTH Authentication required REDIS_PASSWORD in .env does not match requirepass in /etc/redis/redis.conf. Run php artisan optimize after you fix .env.
502 Bad Gateway PHP-FPM is not running, or the socket path differs. Check sudo systemctl status php8.5-fpm and ls /run/php/.
Unable to locate file in Vite manifest The frontend build is missing or broken. Run npm run build again as chatvia.
413 Request Entity Too Large A file is bigger than client_max_body_size in nginx.
Sources stay on Pending Horizon is not running. Check sudo supervisorctl status and its log.
No live progress, or the widget's live chat does not connect Reverb is not running, or nginx does not pass /app/. Check that VITE_REVERB_* are empty, and run npm run build again after changing them.
A file source fails with "program not found" Install the program for that file type (step 1), then sudo supervisorctl restart chatvia-horizon.
A change to .env does nothing The configuration is cached. Run php artisan optimize as chatvia, then php artisan reload.

Ubuntu 24.04

The installer's complete installation runs on Ubuntu 24.04 too, and does the steps below for you. To install by hand on Ubuntu 24.04 LTS, which has an older PostgreSQL, add the PostgreSQL archive first. PHP comes from ondrej/php, as in step 1:

bash
sudo apt update
sudo apt install -y curl ca-certificates gnupg

sudo install -d /usr/share/postgresql-common/pgdg
sudo curl -fsSL -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc \
  https://www.postgresql.org/media/keys/ACCC4CF8.asc
. /etc/os-release
echo "deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt ${VERSION_CODENAME}-pgdg main" \
  | sudo tee /etc/apt/sources.list.d/pgdg.list

sudo apt update

Then follow the page with one change in step 1: leave out composer, as Ubuntu 24.04's Composer package is old. Install the current Composer instead:

bash
curl -fsSL https://getcomposer.org/installer | php -- --quiet
sudo mv composer.phar /usr/local/bin/composer

Node.js comes from NodeSource as on Ubuntu 26.04, and everything else, including steps 2 to 13, is the same.

Search guides, settings and commands.