Skip to content

Select language

Current language: English

OmniRoute — Deployment Guide on VM with Cloudflare

Complete guide to install and configure OmniRoute on a VM (VPS) with domain managed via Cloudflare.


Item Minimum Recommended
CPU 1 vCPU 2 vCPU
RAM 1 GB 2 GB
Disk 10 GB SSD 25 GB SSD
OS Ubuntu 22.04 LTS Ubuntu 24.04 LTS
Domain Registered on Cloudflare —
Docker Docker Engine 24+ Docker 27+

Tested providers: Akamai (Linode), DigitalOcean, Vultr, Hetzner, AWS Lightsail.


On your preferred VPS provider:

  • Choose Ubuntu 24.04 LTS
  • Select the minimum plan (1 vCPU / 1 GB RAM)
  • Set a strong root password or configure SSH key
  • Note the public IP (e.g., 203.0.113.10)
Terminal window
ssh root@203.0.113.10
Terminal window
apt update && apt upgrade -y
Terminal window
# Install dependencies
apt install -y ca-certificates curl gnupg
# Add official Docker repository
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $ (. /etc/os-release && echo "$VERSION_CODENAME") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
apt update
apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
Terminal window
apt install -y nginx
Terminal window
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp # SSH
ufw allow 80/tcp # HTTP (redirect)
ufw allow 443/tcp # HTTPS
ufw enable

Tip: For maximum security, restrict ports 80 and 443 to Cloudflare IPs only. See the Advanced Security section.


Terminal window
mkdir -p /opt/omniroute
Terminal window
cat > /opt/omniroute/.env << 'EOF'
# === Security ===
JWT_SECRET=CHANGE-TO-A-UNIQUE-64-CHAR-SECRET-KEY
INITIAL_PASSWORD=YourSecurePassword123!
API_KEY_SECRET=REPLACE-WITH-ANOTHER-SECRET-KEY
STORAGE_ENCRYPTION_KEY=REPLACE-WITH-THIRD-SECRET-KEY
STORAGE_ENCRYPTION_KEY_VERSION=v1
MACHINE_ID_SALT=CHANGE-TO-A-UNIQUE-SALT
OMNIROUTE_WS_BRIDGE_SECRET=REPLACE-WITH-WS-BRIDGE-SECRET # REQUIRED em produção: usado pelo Codex Responses WS bridge
# === App ===
PORT=20128
NODE_ENV=production
HOSTNAME=0.0.0.0
DATA_DIR=/app/data
APP_LOG_TO_FILE=true
AUTH_COOKIE_SECURE=true
REQUIRE_API_KEY=false
# === URLs (change to your domain) ===
# Internal server-to-server base URL for scheduled jobs / self-fetches.
BASE_URL=http://127.0.0.1:20128
# Browser-facing URL used for OAuth callbacks, dashboard links, and generated public URLs.
NEXT_PUBLIC_BASE_URL=https://llms.seudominio.com
# Optional explicit public origin override for generated public asset URLs.
# OMNIROUTE_PUBLIC_BASE_URL=https://llms.seudominio.com
# === Cloud Sync (optional) ===
# CLOUD_URL=https://cloud.omniroute.online
# NEXT_PUBLIC_CLOUD_URL=https://cloud.omniroute.online
EOF

⚠️ IMPORTANT: Generate unique secret keys! Use openssl rand -hex 32 for each key.

Terminal window
docker pull diegosouzapw/omniroute:latest
docker run -d \
--name omniroute \
--restart unless-stopped \
--env-file /opt/omniroute/.env \
-p 20128:20128 \
-v omniroute-data:/app/data \
diegosouzapw/omniroute:latest
Terminal window
docker ps | grep omniroute
docker logs omniroute --tail 20

It should display: [DB] SQLite database ready and listening on port 20128.


3.1 Generate SSL certificate (Cloudflare Origin)

Section titled “3.1 Generate SSL certificate (Cloudflare Origin)”

In the Cloudflare dashboard:

  1. Go to SSL/TLS → Origin Server
  2. Click Create Certificate
  3. Keep the defaults (15 years, *.yourdomain.com)
  4. Copy the Origin Certificate and the Private Key
Terminal window
mkdir -p /etc/nginx/ssl
# Paste the certificate
nano /etc/nginx/ssl/origin.crt
# Paste the private key
nano /etc/nginx/ssl/origin.key
chmod 600 /etc/nginx/ssl/origin.key
Terminal window
cat > /etc/nginx/sites-available/omniroute << 'NGINX'
# Default server — blocks direct access via IP
server {
listen 80 default_server;
listen [::]:80 default_server;
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
ssl_certificate /etc/nginx/ssl/origin.crt;
ssl_certificate_key /etc/nginx/ssl/origin.key;
server_name _;
return 444;
}
# OmniRoute — HTTPS
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name llms.yourdomain.com; # Change to your domain
ssl_certificate /etc/nginx/ssl/origin.crt;
ssl_certificate_key /etc/nginx/ssl/origin.key;
ssl_protocols TLSv1.2 TLSv1.3;
client_max_body_size 100M;
location / {
proxy_pass http://127.0.0.1:20128;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-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;
# WebSocket support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# SSE (Server-Sent Events) — streaming AI responses
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
}
}
# HTTP → HTTPS redirect
server {
listen 80;
listen [::]:80;
server_name llms.yourdomain.com;
return 301 https://$server_name$request_uri;
}
NGINX

Keep reverse-proxy stream timeouts aligned with your OmniRoute timeout env vars. If you raise FETCH_TIMEOUT_MS / STREAM_IDLE_TIMEOUT_MS, raise proxy_read_timeout / proxy_send_timeout above the same threshold.

OmniRoute uses NEXT_PUBLIC_BASE_URL as the canonical browser-facing origin for OAuth callbacks and generated public links. Authenticated dashboard writes use same-origin requests plus session-bound CSRF protection, so they do not require a static public base URL. The X-Forwarded-* headers above are still useful routing metadata, but they are not a replacement for setting the explicit public URL when OAuth or generated browser links need one. Only enable OMNIROUTE_TRUST_PROXY if OmniRoute is not directly reachable by clients and your proxy strips/rebuilds incoming forwarded headers.

Terminal window
# Remove default configuration
rm -f /etc/nginx/sites-enabled/default
# Enable OmniRoute
ln -sf /etc/nginx/sites-available/omniroute /etc/nginx/sites-enabled/omniroute
# Test and reload
nginx -t && systemctl reload nginx

In the Cloudflare dashboard → DNS:

Type Name Content Proxy
A llms 203.0.113.10 (VM IP) ✅ Proxied

Under SSL/TLS → Overview:

  • Mode: Full (Strict)

Under SSL/TLS → Edge Certificates:

  • Always Use HTTPS: ✅ On
  • Minimum TLS Version: TLS 1.2
  • Automatic HTTPS Rewrites: ✅ On
Terminal window
curl -sI https://llms.seudominio.com/health
# Should return HTTP/2 200

Terminal window
docker pull diegosouzapw/omniroute:latest
docker stop omniroute && docker rm omniroute
docker run -d --name omniroute --restart unless-stopped \
--env-file /opt/omniroute/.env \
-p 20128:20128 \
-v omniroute-data:/app/data \
diegosouzapw/omniroute:latest
Terminal window
docker logs -f omniroute # Real-time stream
docker logs omniroute --tail 50 # Last 50 lines
Terminal window
# Copy data from the volume to the host
docker cp omniroute:/app/data ./backup-$(date +%F)
# Or compress the entire volume
docker run --rm -v omniroute-data:/data -v $(pwd):/backup \
alpine tar czf /backup/omniroute-data-$(date +%F).tar.gz /data
Terminal window
docker stop omniroute
docker run --rm -v omniroute-data:/data -v $(pwd):/backup \
alpine sh -c "rm -rf /data/* && tar xzf /backup/omniroute-data-YYYY-MM-DD.tar.gz -C /"
docker start omniroute

Terminal window
cat > /etc/nginx/cloudflare-ips.conf << 'CF'
# Cloudflare IPv4 ranges — update periodically
# https://www.cloudflare.com/ips-v4/
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 131.0.72.0/22;
real_ip_header CF-Connecting-IP;
CF

Add the following to nginx.conf inside the http {} block:

include /etc/nginx/cloudflare-ips.conf;
Terminal window
apt install -y fail2ban
systemctl enable fail2ban
systemctl start fail2ban
# Check status
fail2ban-client status sshd
Terminal window
# Prevent direct external access to port 20128
iptables -I DOCKER-USER -p tcp --dport 20128 -j DROP
iptables -I DOCKER-USER -i lo -p tcp --dport 20128 -j ACCEPT
# Persist the rules
apt install -y iptables-persistent
netfilter-persistent save

7. Deploy to Cloudflare Workers (Optional)

Section titled “7. Deploy to Cloudflare Workers (Optional)”

For remote access via Cloudflare Workers (without exposing the VM directly):

Terminal window
# In the local repository
cd omnirouteCloud
npm install
npx wrangler login
npx wrangler deploy

See also TUNNELS_GUIDE.md for the in-repo Cloudflare Tunnel walkthrough. The standalone omnirouteCloud/ worker lives in a separate companion repo.


Port Service Access
22 SSH Public (with fail2ban)
80 nginx HTTP Redirect → HTTPS
443 nginx HTTPS Via Cloudflare Proxy
20128 OmniRoute Localhost only (via nginx)

For deployments on small VPS instances (1 GB RAM or less):

  • Disable background services — set OMNIROUTE_DISABLE_BACKGROUND_SERVICES=1 to skip scheduler, MCP server, and periodic maintenance tasks. See docs/reference/ENVIRONMENT.md.
  • Use SQLite WAL mode — enabled by default, reduces peak memory during concurrent reads.
  • Cap the V8 heap — set OMNIROUTE_MEMORY_MB (e.g. 512) so the runtime does not calibrate a ceiling larger than the VM. See docs/reference/ENVIRONMENT.md.
  • Heavyweight admission auto-scales with the heap cap – once OMNIROUTE_MEMORY_MB is set above, the ingest byte budget (OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES) derives itself from that same ceiling, so a memory-constrained VM already gets a smaller concurrent-request budget with no extra tuning; excess requests get a retryable 503 with Retry-After instead of competing for memory. Set the legacy OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT request-count cap only if you need a hard ceiling on top of that.
  • Avoid next build on the VPS — build locally and deploy the standalone output (.next/standalone/).
  • Monitor with top / free -m — OmniRoute typically uses 200-400 MB RSS at idle on a 1 GB VM.

undefined (a58000c7685f)

HagiCode

HagiCode is an agentic coding workspace. Rich, convenient UI interactions make planning, agent execution, and project work approachable and easy to navigate, creating an engaging development experience.

Explore an engaging development experience through rich, convenient UI interactions.

Visit HagiCodeRead the documentation
  • SmartOpenSpec workflows turn intent into an executable path, from proposal to design, tasks, and a shipped change.
  • EfficientMulti-agent execution keeps research, implementation, and review moving in parallel, across several repositories and the Agent CLIs you already use.
  • InterestingRich, convenient UI interactions make planning, agent execution, and project work approachable and easy to navigate.

Download for Windows

Get it from Microsoft StoreAll downloads

Turn ideas into shipped software.

HagiCode is an agentic coding workspace. OpenSpec workflows plan the change, multi-agent execution carries it out, and Hero Scenario makes the long run visible, so ideas become shipped software.

Smart
OpenSpec workflows turn intent into a path from proposal to shipped change.
Efficient
Multi-agent execution runs research, build, and review in parallel.
Fun
Rich UI interactions make planning, agent work, and projects easier to follow.