IPv4 & IPv6 Leasing - Any RIR, Any LocationOrder Now
Hostperl

Run a Private AI Support Bot on VPS in 2026

By Raman Kumar

Share:

Updated on Jul 29, 2026

Run a Private AI Support Bot on VPS in 2026

Start with the right deployment shape

A private AI support bot on VPS gives you control over latency, data residency, and support visibility. It also keeps the stack simple enough that your team can maintain it without guessing what broke.

If you are choosing between hosted serverless tools and a VPS, read Hostperl’s AI hosting for support bots and RAG apps on VPS first. For many teams, a small Hostperl VPS is the cleaner starting point: one box for the app, one database or vector store, and one support path when something needs attention.

Use this tutorial as a fresh-server runbook. We will install Docker, run a simple private AI support bot stack with Node.js, Redis, and Postgres with pgvector, place it behind Nginx, add SSL, and verify that webhooks and background jobs still work after a reboot.

  • SERVER_IP: your VPS public IP, such as 203.0.113.10
  • DOMAIN_NAME: the bot hostname, such as bot.example.com
  • ADMIN_USER: your sudo account, such as hostperladmin
  • APP_USER: the service account for the bot, such as aiapp
  • APP_DIR: the app directory, such as /opt/ai-support-bot
  • APP_PORT: the app port, such as 3000

Connect to the VPS and identify the OS

Run the first SSH command from your local computer. If Hostperl gave you a non-root login, use that instead of root.

ssh root@203.0.113.10

Or, if your server already has a default admin user:

ssh ADMIN_USER@203.0.113.10

After you log in, detect the operating system before you install anything.

cat /etc/os-release

You should see either Ubuntu/Debian fields or AlmaLinux/Rocky fields. The commands below are split for that reason. Do not mix them.

Create a non-root admin account and keep root open

Use the root session only long enough to create your normal login. Keep this root terminal open until the new account works in a second terminal.

Ubuntu and Debian

adduser ADMIN_USER
usermod -aG sudo ADMIN_USER
mkdir -p /home/ADMIN_USER/.ssh
chmod 700 /home/ADMIN_USER/.ssh
cp /root/.ssh/authorized_keys /home/ADMIN_USER/.ssh/authorized_keys 2>/dev/null || true
chown -R ADMIN_USER:ADMIN_USER /home/ADMIN_USER/.ssh
chmod 600 /home/ADMIN_USER/.ssh/authorized_keys

This creates the admin user, grants sudo access, and copies an existing SSH key if one is already present. If you do not have an authorized key yet, add your public key manually before proceeding.

AlmaLinux and Rocky Linux

useradd -m ADMIN_USER
passwd ADMIN_USER
usermod -aG wheel ADMIN_USER
mkdir -p /home/ADMIN_USER/.ssh
chmod 700 /home/ADMIN_USER/.ssh
cp /root/.ssh/authorized_keys /home/ADMIN_USER/.ssh/authorized_keys 2>/dev/null || true
chown -R ADMIN_USER:ADMIN_USER /home/ADMIN_USER/.ssh
chmod 600 /home/ADMIN_USER/.ssh/authorized_keys

Here, wheel is the sudo-equivalent group. If you need passwordless SSH only, set the password now but expect to use keys for daily login.

Open a second terminal on your local computer and test the new account before changing root access.

ssh ADMIN_USER@203.0.113.10
sudo -v
whoami

You want to see ADMIN_USER and a successful sudo credential check. Leave the original root session open until this works.

Install Docker, Nginx, and support tools

This stack uses Docker Compose for the bot, Nginx as the reverse proxy, and a small set of system packages for firewall and certificate work. If you need a broader reference for VPS app setup, Hostperl’s Next.js deployment tutorial shows the same host-side pattern used here.

Ubuntu and Debian

sudo apt update
sudo apt install -y ca-certificates curl gnupg nginx ufw
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release; echo $VERSION_CODENAME) stable" | sudo tee /etc/apt/sources.list.d/docker.list >/dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo systemctl enable --now nginx docker

After this, confirm the versions and that both services are running.

nginx -v
docker --version
docker compose version
systemctl status nginx --no-pager
systemctl status docker --no-pager

AlmaLinux and Rocky Linux

sudo dnf install -y dnf-plugins-core ca-certificates curl nginx firewalld
sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo systemctl enable --now nginx docker firewalld

Confirm the installation and active services.

nginx -v
docker --version
docker compose version
systemctl status nginx --no-pager
systemctl status docker --no-pager

If SELinux is enforcing, keep it enabled. We will use standard paths and an HTTP proxy, which avoids risky policy changes.

Open the firewall before publishing the app

Add the new rules first, then test them. Do not remove SSH access until you know the new login works.

Ubuntu and Debian with UFW

sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw --force enable
sudo ufw status verbose

You should see SSH and Nginx allowed. If you are already using custom SSH ports, add that port before enabling UFW.

AlmaLinux and Rocky Linux with firewalld

sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
sudo firewall-cmd --list-all

That opens port 80 and 443 for the bot, plus SSH for administration.

Prepare the app directory, secrets, and Docker Compose stack

We will build a small stack that looks like a real customer-service deployment: a Node.js bot API, Postgres with pgvector for retrieval, Redis for jobs and rate limiting, and Nginx in front. For an alternate vector-store approach, Hostperl’s pgvector and Docker Compose guide covers the database side in more detail.

Run these commands as your non-root sudo user.

sudo mkdir -p /opt/ai-support-bot
sudo chown -R ADMIN_USER:ADMIN_USER /opt/ai-support-bot
cd /opt/ai-support-bot
pwd

Now create the environment file. Replace the sample secrets with your own values.

cat > .env <<'EOF'
APP_PORT=3000
DOMAIN_NAME=bot.example.com
POSTGRES_DB=aisupport
POSTGRES_USER=aisupport
POSTGRES_PASSWORD=replace-with-a-long-random-password
REDIS_URL=redis://redis:6379
OPENAI_API_KEY=replace-with-your-api-key
JWT_SECRET=replace-with-a-32-byte-random-string
BOT_TOKEN=replace-with-your-bot-token
EOF
chmod 600 .env

File permissions matter here. Your API keys and webhook token should not be readable by other local users.

Create the Compose file.

cat > docker-compose.yml <<'EOF'
services:
  postgres:
    image: pgvector/pgvector:pg16
    restart: unless-stopped
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7.4-alpine
    restart: unless-stopped
    command: ["redis-server", "--appendonly", "yes"]
    volumes:
      - redisdata:/data

  app:
    image: node:22-alpine
    restart: unless-stopped
    working_dir: /app
    env_file: .env
    command: sh -c "npm install && npm run start"
    ports:
      - "127.0.0.1:${APP_PORT}:3000"
    volumes:
      - ./app:/app
    depends_on:
      - postgres
      - redis

volumes:
  pgdata:
  redisdata:
EOF

This example assumes your application code lives in /opt/ai-support-bot/app. If you already have a bot project, copy it there before starting the stack.

Validate the Compose file before bringing anything up.

docker compose config

Successful output means the YAML and environment references are valid.

Put a simple support bot app in place

If your bot already exists, adapt the same file structure. The goal here is to show a working private AI support bot on VPS from a clean start.

mkdir -p app
cat > app/package.json <<'EOF'
{
  "name": "ai-support-bot",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "start": "node server.js"
  },
  "dependencies": {
    "express": "^4.21.2",
    "pg": "^8.13.1",
    "redis": "^4.7.0"
  }
}
EOF
cat > app/server.js <<'EOF'
import express from 'express';
import { Pool } from 'pg';
import { createClient } from 'redis';

const app = express();
app.use(express.json());

const pool = new Pool({
  host: 'postgres',
  port: 5432,
  database: process.env.POSTGRES_DB,
  user: process.env.POSTGRES_USER,
  password: process.env.POSTGRES_PASSWORD,
});

const redis = createClient({ url: process.env.REDIS_URL });
await redis.connect();

app.get('/health', async (_req, res) => {
  const db = await pool.query('SELECT 1 AS ok');
  const cache = await redis.ping();
  res.json({ ok: true, db: db.rows[0].ok === 1, cache });
});

app.post('/webhook', async (req, res) => {
  const event = req.body?.event || 'unknown';
  await redis.lPush('webhook-events', JSON.stringify({ event, at: new Date().toISOString() }));
  res.json({ accepted: true });
});

app.listen(3000, '0.0.0.0', () => {
  console.log('AI support bot listening on 3000');
});
EOF

That app gives you a health endpoint and a webhook endpoint. It is enough to prove the deployment path before you connect an actual LLM provider or CRM.

Start the stack and check the containers.

docker compose up -d
docker compose ps

You should see three running services. If the app container exits, inspect logs before moving on.

docker compose logs --tail=100 app

Route traffic through Nginx and add SSL

Use Nginx as the public entry point, then keep the app bound to localhost. That reduces accidental exposure and makes rollback easier.

Create the reverse proxy configuration.

sudo tee /etc/nginx/conf.d/ai-support-bot.conf >/dev/null <<'EOF'
server {
    listen 80;
    server_name bot.example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        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;
    }
}
EOF

Replace bot.example.com with your real domain. Check the syntax before reloading Nginx.

sudo nginx -t
sudo systemctl reload nginx

Next, issue a TLS certificate with Certbot.

Ubuntu and Debian

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

AlmaLinux and Rocky Linux

sudo dnf install -y certbot python3-certbot-nginx
sudo certbot --nginx -d bot.example.com

Certbot will rewrite the server block for HTTPS and set up renewals. Confirm the timer or cron job exists afterward.

sudo certbot renew --dry-run

For broader deployment options, compare this with OpenClaw, Coolify, Dokploy, or CapRover. If you want an open-source PaaS layer later, Hostperl’s OpenClaw hosting tutorial is a useful follow-up.

Test the bot from the server and from your browser

Now verify every layer in order: local port, Nginx, HTTPS, and the application endpoints.

On the VPS, confirm the app listens only on localhost.

ss -lntp | grep 3000
curl -s http://127.0.0.1:3000/health | jq .

If you do not have jq, install it or read the raw JSON. You want ok: true, a working database check, and a Redis ping.

From your local computer, test the public site and webhook.

curl -I https://bot.example.com
curl -s -X POST https://bot.example.com/webhook -H 'Content-Type: application/json' -d '{"event":"test"}'

The first command should return 200 or 301/302 redirecting to HTTPS. The second should return {"accepted":true}.

Check the Nginx and app logs if either request fails.

sudo tail -n 50 /var/log/nginx/error.log
docker compose logs --tail=50 app

Control cost, latency, and exposure

A private AI support bot on VPS works best when the plan matches the workload. Smaller models, caching, and fewer retries usually save more than buying a larger server too early.

  • Keep the bot on a right-sized Hostperl VPS for the first release, then scale only when CPU, memory, or queue depth justify it.
  • Cache repeated retrieval results in Redis for FAQ-heavy chats and repeated sales queries.
  • Store embeddings in pgvector if you want fewer moving parts and easier backups.
  • Use environment variables for API keys and webhook tokens, never in source files.
  • Limit bot permissions to the channels, inboxes, and tool calls it truly needs.

For data-sensitive deployments, the combination of a New Zealand or nearby APAC VPS, private networking choices, and local backups can make procurement and compliance conversations simpler. That is one reason many teams choose Hostperl VPS instead of a serverless patchwork.

Back up, restart, and prove persistence

Before you call the rollout finished, confirm that the stack survives a reboot and that your data is still there.

docker compose exec postgres pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"
docker compose restart
sudo reboot

After the server comes back, reconnect and check the service list again.

ssh ADMIN_USER@203.0.113.10
cd /opt/ai-support-bot
docker compose ps
systemctl status nginx --no-pager
docker compose logs --tail=30 app

You should see Nginx active, Docker active, and the app container running again. If the bot uses scheduled jobs or workers, verify those containers separately.

Troubleshooting the most likely failures

These are the issues that usually show up in support tickets after the first launch.

  • 502 Bad Gateway: run docker compose logs --tail=100 app. If the app crashed, fix the Node error and start it again with docker compose up -d.
  • Nginx syntax error: run sudo nginx -t. The output usually points to the exact line in /etc/nginx/conf.d/ai-support-bot.conf.
  • Database connection refused: run docker compose ps and docker compose logs --tail=100 postgres. If the volume is empty or the password changed, update .env and restart the stack.
  • Webhook does not arrive: run curl -v https://bot.example.com/webhook and inspect any provider-side retry logs. Confirm your firewall allows 443 and the DNS record points to the right IP.
  • SSL renewal failure: run sudo certbot renew --dry-run. If validation fails, confirm the domain resolves to this server before the renewal window.

Hostperl gives you the kind of VPS setup that fits private AI work: predictable access, straightforward support, and room to run your bot, database, and worker jobs on your terms. If you are planning a production rollout, start with Hostperl VPS hosting and keep your AI app behind your own reverse proxy.

If your team needs a more durable layout for customer-service automations or retrieval-heavy workflows, this is also a good place to compare your stack with private AI app hosting guidance before you go live.

FAQ

Should I run a private AI support bot on VPS or use serverless?

Use a VPS when you need stable latency, tighter data control, and easier inspection of logs, queues, and backups. Serverless is convenient, but private support workflows usually need more consistent behavior.

Is pgvector enough for a small RAG-backed bot?

Yes, for many customer-support and knowledge-base use cases. Start there before adding a separate vector database unless you have a clear scale or search requirement.

How do I keep secrets out of the repo?

Store them in a root-owned or user-owned .env file with chmod 600, or move to a secret manager later. Do not commit API keys, tokens, or database passwords.

What should I check after a migration?

Check the health endpoint, webhook delivery, database reads and writes, SSL renewal, and reboot persistence. Those five checks catch most launch problems early.

Can Hostperl help if I need a bigger plan later?

Yes. If your bot grows into a multi-tenant app, a heavier VPS or dedicated server is often easier to manage than stitching together multiple managed services.

For teams that want a practical, supportable setup in 2026, this private AI support bot on VPS pattern stays easy to explain, back up, and recover. When you are ready to expand, keep the same layout and move up to a larger managed VPS hosting plan instead of redesigning the whole stack.