← All articles
infrastructure 7 min read

Self-Hosting AI Agents on a VPS — Complete Setup Guide

A practical guide to deploying AI agents on a VPS with Node.js, nginx, SSL, firewall configuration, and channel integrations.

Why Self-Host?

Cloud-hosted agent platforms are convenient, but they come with constraints: vendor lock-in, limited customization, usage-based pricing that scales unpredictably, and data leaving your infrastructure. Self-hosting on a VPS gives you full control over your agent runtime, data residency, and cost structure.

A basic VPS ($10-20/month) can comfortably run multiple AI agent instances, a reverse proxy, and monitoring — all you need for a production-grade setup.

Prerequisites

  • A VPS running Ubuntu 22.04 or 24.04 (Hetzner, DigitalOcean, Linode — any provider works)
  • A domain name pointed at your VPS IP (for SSL)
  • SSH access with key-based authentication (disable password auth)
  • API keys for your LLM providers (OpenAI, Anthropic, etc.)

Step 1: Base System Setup

SSH into your server and update everything:

sudo apt update && sudo apt upgrade -y
sudo apt install -y curl git build-essential

Install Node.js 22 via nvm

Using nvm (Node Version Manager) makes it easy to switch Node versions and avoids the outdated versions in Ubuntu’s default repositories:

# Install nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash

# Reload shell
source ~/.bashrc

# Install and use Node.js 22
nvm install 22
nvm alias default 22

# Verify
node --version  # v22.x.x
npm --version   # 10.x.x

Step 2: Install Your Agent

Clone and set up your agent application. This example uses a generic structure — adapt the paths to your specific agent:

# Create a dedicated directory
sudo mkdir -p /opt/agents
sudo chown $USER:$USER /opt/agents

# Clone your agent repository
git clone https://github.com/ohara-systems/your-agent.git /opt/agents/main

cd /opt/agents/main
npm install

# Create the environment file
cp .env.example .env

Edit the .env file with your actual credentials:

# /opt/agents/main/.env
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
AGENT_PORT=8080
NODE_ENV=production
LOG_LEVEL=info

Step 3: Create a systemd Service

Running the agent as a systemd service ensures it starts on boot and restarts on crashes:

# /etc/systemd/system/ai-agent.service
[Unit]
Description=AI Agent Service
After=network.target

[Service]
Type=simple
User=agent
Group=agent
WorkingDirectory=/opt/agents/main
ExecStart=/home/agent/.nvm/versions/node/v22.*/bin/node src/index.js
Restart=on-failure
RestartSec=10
EnvironmentFile=/opt/agents/main/.env

# Security hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ReadWritePaths=/opt/agents/main

[Install]
WantedBy=multi-user.target
# Create a dedicated system user
sudo useradd -r -s /usr/sbin/nologin agent
sudo chown -R agent:agent /opt/agents

# Enable and start the service
sudo systemctl daemon-reload
sudo systemctl enable ai-agent
sudo systemctl start ai-agent

# Check status
sudo systemctl status ai-agent

Step 4: Firewall with ufw

Lock down your server to only the ports you need:

# Enable ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing

# Allow SSH (critical — do this before enabling)
sudo ufw allow 22/tcp

# Allow HTTP and HTTPS
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# Enable the firewall
sudo ufw enable

# Verify
sudo ufw status verbose

Do not expose port 8080 (or whatever port your agent runs on) directly. All traffic goes through nginx.

Step 5: Nginx Reverse Proxy with SSL

Install nginx

sudo apt install -y nginx

Configure the reverse proxy

# /etc/nginx/sites-available/agent
server {
    listen 80;
    server_name agent.yourdomain.com;

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

        # Timeouts for long-running agent requests
        proxy_read_timeout 300s;
        proxy_send_timeout 300s;
    }
}
# Enable the site
sudo ln -s /etc/nginx/sites-available/agent /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Add SSL with Certbot

# Install Certbot
sudo apt install -y certbot python3-certbot-nginx

# Obtain and install certificate
sudo certbot --nginx -d agent.yourdomain.com

# Verify auto-renewal
sudo certbot renew --dry-run

Certbot modifies your nginx config automatically to handle HTTPS and redirect HTTP traffic.

Step 6: Health Checks

Add a health check endpoint to your agent application and monitor it:

# Simple health check script — /opt/agents/healthcheck.sh
#!/bin/bash
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8080/health)

if [ "$RESPONSE" != "200" ]; then
  echo "$(date): Health check failed with status $RESPONSE" >> /var/log/agent-health.log
  systemctl restart ai-agent
fi
# Add to crontab — run every 5 minutes
chmod +x /opt/agents/healthcheck.sh
(crontab -l 2>/dev/null; echo "*/5 * * * * /opt/agents/healthcheck.sh") | crontab -

For more robust monitoring, consider setting up a lightweight solution like Uptime Kuma alongside your agent.

Step 7: Connecting Channels

Telegram

# 1. Create a bot via @BotFather on Telegram
# 2. Add the bot token to your .env
echo "TELEGRAM_BOT_TOKEN=123456:ABC-DEF..." >> /opt/agents/main/.env

# 3. Set the webhook to your domain
curl -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/setWebhook" \
  -d "url=https://agent.yourdomain.com/webhook/telegram"

Discord

# 1. Create an application at https://discord.com/developers/applications
# 2. Add a bot and copy the token
echo "DISCORD_BOT_TOKEN=MTIz..." >> /opt/agents/main/.env

# 3. Invite the bot to your server using the OAuth2 URL generator
# Required permissions: Send Messages, Read Message History, Embed Links

After adding new environment variables, restart the service:

sudo systemctl restart ai-agent

Security Best Practices

Keep the system updated. Enable unattended security updates:

sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades

Use SSH keys only. Disable password authentication in /etc/ssh/sshd_config:

PasswordAuthentication no
PubkeyAuthentication yes

Rate limit your endpoints. Add rate limiting in nginx to prevent abuse:

# In the http block of /etc/nginx/nginx.conf
limit_req_zone $binary_remote_addr zone=agent:10m rate=10r/s;

# In your server block
location / {
    limit_req zone=agent burst=20 nodelay;
    proxy_pass http://127.0.0.1:8080;
    # ... rest of proxy config
}

Rotate API keys regularly. Set a calendar reminder. When you rotate, update .env and restart the service.

Back up your configuration. Your agent configs, .env files (encrypted), and nginx configs should be backed up. A simple rsync to a separate location works.

Troubleshooting

Agent won’t start: Check logs with journalctl -u ai-agent -f. Common issues: missing environment variables, port already in use, permission errors.

502 Bad Gateway from nginx: The agent is not running or not listening on the expected port. Verify with curl http://127.0.0.1:8080/health.

SSL certificate not renewing: Check that Certbot’s timer is active: systemctl status certbot.timer. Run sudo certbot renew --dry-run to diagnose.

High memory usage: Node.js agents can leak memory on long-running sessions. Set a memory limit in systemd: MemoryMax=512M in the [Service] section. The service will restart automatically when the limit is hit.

Slow responses: Check if you are hitting LLM API rate limits. Add request queuing in your agent or use Model Prism to distribute requests across multiple provider keys.

A well-configured VPS is a reliable, cost-effective foundation for running AI agents. Once the initial setup is done, maintenance is minimal — mostly keeping the system updated and monitoring health checks.

O
ohara.systems Team
ohara.systems