Want to run your own Perplexity-style AI search engine instead of paying per query? This guide walks through deploying [Gemini Search](https://github.com/ammaarreshi/Gemini-Search) — an open-source clone built on Gemini 2.0 Flash with Google Search Grounding — to a production Ubuntu VPS using [DeployHQ](https://www.deployhq.com), 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](https://www.deployhq.com) before, our [automatic deployment from Git](https://www.deployhq.com/features/automatic-deployments) 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](https://www.deployhq.com/blog/deploy-nextjs-on-vps) uses an almost identical pipeline with a different framework.

## Table of Contents

1. [Prerequisites](#prerequisites)
2. [Server Setup](#server-setup)
3. [DeployHQ Configuration](#deployhq-configuration)
4. [Deployment Process](#deployment-process)
5. [Post-Deployment: SSL, Monitoring, Backups](#post-deployment-ssl-monitoring-backups)
6. [Troubleshooting](#troubleshooting)
7. [Maintenance](#maintenance)

## Prerequisites

- A GitHub account (and your own fork of the upstream Gemini Search repo)
- A [DeployHQ](https://www.deployhq.com) account — start with the [free trial on the pricing page](https://www.deployhq.com/pricing); the Basic plan is enough for a single project
- A [Google AI Studio](https://aistudio.google.com/app/apikey) 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](https://www.deployhq.com) 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](https://www.deployhq.com) 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](https://www.deployhq.com) 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](https://www.deployhq.com/deploy-from-github) (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](https://www.deployhq.com) 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](https://www.deployhq.com) 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](https://www.deployhq.com/blog/using-nodejs-and-npm-with-deployhq-build)[DeployHQ](https://www.deployhq.com) build pipeline guide.

### 4. Config File: Environment Variables

Never commit API keys. Use a [DeployHQ config file](https://www.deployhq.com/features) 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](https://www.deployhq.com) 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](https://www.deployhq.com/features/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](https://www.deployhq.com) 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](https://www.deployhq.com/features/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](https://www.deployhq.com) 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](https://www.deployhq.com) 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](https://www.deployhq.com/features/one-click-rollback) in [DeployHQ](https://www.deployhq.com) — 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](https://nodejs.org/en/about/previous-releases). 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](https://www.deployhq.com) pipeline scales:

- **Multiple environments** — staging, production, preview deploys, all with their own config files
- **Multiple servers** — [DeployHQ](https://www.deployhq.com) deploys to every server in parallel
- **Docker** — if you containerize, [Docker builds](https://www.deployhq.com/features/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](https://www.deployhq.com/blog/getting-started-with-google-gemini-cli-open-source-ai-agent-for-your-terminal) covers the terminal-based version of Gemini, and our [Claude Code vs Codex CLI vs Gemini CLI comparison](https://www.deployhq.com/blog/comparing-claude-code-openai-codex-and-google-gemini-cli-which-ai-coding-assistant-is-right-for-your-deployment-workflow) 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](https://www.deployhq.com/blog/how-to-install-deepseek-on-your-cloud-server-with-ollama-llm).

* * *

Questions or stuck on a step? Email us at **[support@deployhq.com](mailto:support@deployhq.com)** — a real engineer answers, usually within a few hours. You can also follow [@deployhq on X](https://x.com/deployhq) for product updates and deployment war stories.

