Deploy Gemini Search to a VPS: A Complete Step-by-Step Guide

AI, Node, Open Source, and Tutorials

Deploy Gemini Search to a VPS: A Complete Step-by-Step Guide

Want to run your own Perplexity-style AI search engine instead of paying per query? This guide walks through deploying Gemini Search — an open-source clone built on Gemini 2.0 Flash with Google Search Grounding — to a production Ubuntu VPS using DeployHQ, Nginx, and PM2. By the end you'll have an automated deployment pipeline that ships every push to main to your server with zero downtime.

If you've never used DeployHQ before, our automatic deployment from Git feature handles the heavy lifting: every commit you push triggers a build, runs your install/build commands on our infrastructure, then atomically swaps the new release into place on your server.

What you'll build

A Node.js + Vite app stack running behind Nginx on Ubuntu 22.04 LTS, with the following deployment flow:

flowchart LR
    A[git push origin main] --> B[DeployHQ build]
    B --> C[npm install + npm run build]
    C --> D[SFTP atomic upload]
    D --> E[SSH: pm2 restart]
    E --> F[Nginx reverse proxy → :3000]

This is the same pattern we recommend for any Node.js application — the Next.js on a VPS guide uses an almost identical pipeline with a different framework.

Table of Contents

  1. Prerequisites
  2. Server Setup
  3. DeployHQ Configuration
  4. Deployment Process
  5. Post-Deployment: SSL, Monitoring, Backups
  6. Troubleshooting
  7. Maintenance

Prerequisites

  • A GitHub account (and your own fork of the upstream Gemini Search repo)
  • A DeployHQ account — start with the free trial on the pricing page; the Basic plan is enough for a single project
  • A Google AI Studio API key for Gemini 2.0 Flash
  • A VPS or cloud server running Ubuntu 22.04 LTS (Hetzner, DigitalOcean, Linode, IONOS, OVH, or any provider — DeployHQ ships SSH/SFTP so the host doesn't matter)
  • A domain name (optional, but required for HTTPS via Let's Encrypt)
  • Minimum server specs: 1 vCPU, 1 GB RAM, 20 GB disk. Gemini Search itself is light, but npm install and npm run build are memory-hungry — if you're on 512 MB RAM, run the build on DeployHQ instead of the server (covered below).

Server Setup

1. Initial Server Configuration

SSH in as root and update packages:

ssh root@your_server_ip
apt update && apt upgrade -y

2. Create a Non-Root Deploy User

Running deployments as root is the single most common mistake we see. Create a dedicated deploy user with sudo and switch to it for the rest of the setup:

adduser deploy
usermod -aG sudo deploy
su - deploy

3. Install Node.js 22 LTS

Gemini Search uses Vite, which requires Node.js 18 or newer. As of mid-2026, Node.js 22 LTS is the long-term support release — Node 18 entered maintenance mode in October 2025 and reaches end-of-life in April 2026, so don't start a new project on it.

# Add the NodeSource Node.js 22 LTS repository
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -

# Install Node.js (npm bundled)
sudo apt install -y nodejs

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

If you specifically need to pin to Node 18 to match the upstream repo, swap setup_22.x for setup_18.x — but be aware Node 18 stops receiving security patches in April 2026.

Install Nginx as a reverse proxy:

sudo apt install nginx -y

4. Configure Nginx

Gemini Search runs on port 3000. We'll put Nginx in front of it so the app listens on localhost only and Nginx handles ports 80/443, gzip, and (later) HTTPS termination.

sudo nano /etc/nginx/sites-available/gemini-search

Paste this config (the Upgrade/Connection headers are required because Vite's dev server and the production preview both use WebSockets for hot reload and SSE):

server {
    listen 80;
    server_name your_domain.com;  # or your server IP

    # Logging
    access_log /var/log/nginx/gemini-search.access.log;
    error_log  /var/log/nginx/gemini-search.error.log;

    # Reasonable defaults for an AI search app — Gemini grounding
    # responses can take 10–20s on long queries, so bump the timeout.
    proxy_read_timeout 60s;
    proxy_send_timeout 60s;

    location / {
        proxy_pass http://localhost:3000;
        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_cache_bypass $http_upgrade;
    }
}

Enable the site and reload:

sudo ln -s /etc/nginx/sites-available/gemini-search /etc/nginx/sites-enabled/
sudo nginx -t           # check syntax
sudo systemctl reload nginx

5. Install PM2

PM2 keeps the Node process running, restarts it on crashes, and survives server reboots. It's the de-facto standard for Node.js process management in production.

sudo npm install -g pm2

6. Create the Deploy Path

sudo mkdir -p /var/www/gemini-search
sudo chown -R deploy:deploy /var/www/gemini-search

DeployHQ Configuration

DeployHQ connects your Git repository to your server: it watches for pushes, runs your build pipeline, then SFTPs the resulting artifacts and runs your SSH commands. There's nothing for the server to pull — the build runs on DeployHQ's hardware, which keeps your VPS lean.

1. Create the Project

  1. From your dashboard, click New Project
  2. Authorize GitHub via the GitHub integration (or pick GitLab, Bitbucket, Azure DevOps, or self-hosted Git)
  3. Select your fork of ammaarreshi/Gemini-Search and the main branch

2. Add the Server

In Servers → Add Server, configure SFTP:

Field Value
Protocol SFTP
Hostname your_server_ip
Port 22
Username deploy
Authentication SSH Key (recommended over password)
Deploy Path /var/www/gemini-search
Branch main
Auto-deploy ✅ Enabled

DeployHQ will display its public SSH key — paste it into ~/.ssh/authorized_keys on your server (as the deploy user), then click Test Connection.

3. Build Pipeline

This is where running builds on DeployHQ instead of on your VPS pays off. Under Settings → Build Pipeline, add:

npm ci
npm run build

npm ci is faster and more deterministic than npm install for CI/CD — it installs exact versions from package-lock.json and fails if the lockfile is out of sync. Use it everywhere builds run from a clean state.

DeployHQ's build pipeline caches node_modules between builds, so subsequent deploys take seconds rather than minutes. We cover the Node-specific build settings — including npm cache, environment-specific install commands, and Yarn / pnpm — in our dedicated Node.js with the DeployHQ build pipeline guide.

4. Config File: Environment Variables

Never commit API keys. Use a DeployHQ config file so secrets live in your project settings, not in Git. Under Config Files → Add, create /var/www/gemini-search/.env:

NODE_ENV=production
PORT=3000
GOOGLE_API_KEY=your_actual_gemini_api_key

DeployHQ writes this file on every deploy, on the server, atomically — it never touches your repository.

5. SSH Commands

Add a post-deployment SSH command under SSH Commands:

cd /var/www/gemini-search/current && pm2 reload gemini-search --update-env || pm2 start npm --name gemini-search -- start

Two important details:

  • pm2 reload (not restart) does a zero-downtime reload when the app supports it — PM2 spawns the new process, waits for it to be ready, then kills the old one
  • --update-env forces PM2 to re-read your .env after each deploy (otherwise it keeps the old environment in memory)

This pairs naturally with DeployHQ's zero downtime deployments feature, which uses atomic symlink swaps so requests never hit a half-uploaded build.

Deployment Process

1. First Deploy

In the DeployHQ dashboard, click Deploy Project and select main. Watch the live log — you'll see clone → build pipeline → SFTP upload → SSH commands. First deploy typically takes 90–180 seconds; subsequent cached deploys are 20–40 seconds.

2. PM2 Startup Hook

So PM2 survives a server reboot, register it as a systemd service:

pm2 startup systemd
# Copy and run the sudo command PM2 prints
sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u deploy --hp /home/deploy

pm2 save

Now systemctl status pm2-deploy will show the service running, and PM2 (with your app) will auto-start on every boot.

Post-Deployment: SSL, Monitoring, Backups

1. Free HTTPS via Let's Encrypt

If you pointed a domain at your server, Certbot adds HTTPS in one command:

sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d your_domain.com

Certbot edits your Nginx config in place, adds an HTTP→HTTPS redirect, and registers a renewal cron. Verify with:

sudo certbot renew --dry-run

2. Verify the App

pm2 status                 # process should be "online"
pm2 logs gemini-search     # tail logs in real time
curl -I http://localhost:3000   # should return 200

Open https://your_domain.com and run a search query. If you see the AI response with grounded citations, you're live. If the request hangs or returns 500, check pm2 logs first — 9 times out of 10 it's a missing or invalid GOOGLE_API_KEY.

3. The 3-2-1 Backup Rule

The industry-standard backup strategy is 3 copies, 2 different media, 1 offsite. For a small Node app:

  1. Copy 1: the live /var/www/gemini-search on the server
  2. Copy 2: a daily local snapshot
  3. Copy 3: weekly upload to S3 / Backblaze B2 / your provider's snapshot service

A minimal cron-friendly backup script (/usr/local/bin/backup-gemini.sh):

#!/bin/bash
set -euo pipefail

backup_dir="/var/backups/gemini-search"
date=$(date +%Y%m%d-%H%M)
mkdir -p "$backup_dir"

# App + Nginx config
tar -czf "$backup_dir/app-$date.tar.gz" /var/www/gemini-search
cp /etc/nginx/sites-available/gemini-search "$backup_dir/nginx-$date.conf"

# Keep last 7 days only
find "$backup_dir" -type f -mtime +7 -delete

Make it executable and register it in cron:

sudo chmod +x /usr/local/bin/backup-gemini.sh
sudo crontab -e
# Add: 0 3 * * * /usr/local/bin/backup-gemini.sh

For the offsite copy, point aws s3 sync or rclone at $backup_dir after the local backup completes. Define your RPO (Recovery Point Objective — how much data loss is acceptable; daily backups means up to 24 hours) and RTO (Recovery Time Objective — how fast you need to be back online) up front, and size the backup frequency accordingly.

4. Monitoring

pm2 monit          # interactive CPU/memory dashboard
pm2 plus           # optional: web dashboard at app.pm2.io (free tier available)

For uptime alerts, point a free service like UptimeRobot or BetterStack at your domain. If you need synthetic checks specifically tied to deploy events, DeployHQ's notifications integrations post deploy results to Slack, Discord, Microsoft Teams, or any webhook.

Troubleshooting

Application won't start

pm2 logs gemini-search --err --lines 100   # last 100 error lines
cat /var/www/gemini-search/current/.env    # confirm env vars are written
node /var/www/gemini-search/current/dist/index.js   # try running it directly

The most common causes, in order: (1) missing GOOGLE_API_KEY, (2) wrong Node version (Vite needs ≥ 18), (3) dist/ didn't build because npm ci skipped a peer dep.

Nginx returns 502 Bad Gateway

That means Nginx is up but can't reach the Node process:

sudo systemctl status nginx
sudo tail -f /var/log/nginx/gemini-search.error.log
ss -tlnp | grep 3000     # confirm Node is actually listening on :3000
pm2 status

Permission errors on deploy

sudo chown -R deploy:deploy /var/www/gemini-search
sudo chmod -R u+rwX /var/www/gemini-search

DeployHQ uploads as the user you configured (deploy), so the entire deploy path needs to be writable by that user.

Builds fail with JavaScript heap out of memory

If your build pipeline OOMs (common on small VPS providers), let DeployHQ handle it — your server only ever receives the built artifacts, never the raw node_modules. If you have to build on the server, raise Node's heap with NODE_OPTIONS="--max-old-space-size=2048" npm run build.

Maintenance

Routine updates

# OS packages
sudo apt update && sudo apt upgrade -y

# App: just push to main — DeployHQ handles the rest
git push origin main

That's the whole point of automated deployments — you stop SSHing in to update code. The only time you touch the server is for OS-level patches.

Rolling back a bad deploy

If a deploy breaks production, don't try to fix forward in panic. Use one-click rollback in DeployHQ — it re-runs SFTP from the previous successful deploy and runs your post-deploy SSH commands again. Total time: under a minute.

When to upgrade Node

Track the official Node.js release schedule. For a production app: stay on the active LTS line, upgrade to the next LTS within 6 months of its release, and never run an EOL'd version (no security patches = audit failure).

Where this fits in your stack

This guide covers a single VPS deployment. As your app grows, the same DeployHQ pipeline scales:

  • Multiple environments — staging, production, preview deploys, all with their own config files
  • Multiple serversDeployHQ deploys to every server in parallel
  • Docker — if you containerize, Docker builds handle image build + push as part of the pipeline

If you're considering AI tooling more broadly, our Gemini CLI getting-started guide covers the terminal-based version of Gemini, and our Claude Code vs Codex CLI vs Gemini CLI comparison breaks down which AI assistant fits which workflow. For self-hosted LLM inference instead of API-based, see How to install DeepSeek with Ollama on a cloud server.


Questions or stuck on a step? Email us at support@deployhq.com — a real engineer answers, usually within a few hours. You can also follow @deployhq on X for product updates and deployment war stories.