Skip to content

Deploy to VPS

A Virtual Private Server (VPS) is a virtual machine rented from a hosting provider. You get root access, full control over the operating system, and the ability to install any software you need.

For Astro Docus, a VPS is a powerful choice because:

  • Full control — you configure everything exactly how you want it
  • Cost-effective — a $4–6/month VPS can serve thousands of requests per second
  • No vendor lock-in — your site is just files on your own server
  • Scalable — add more resources as your traffic grows

The trade-off is complexity. You manage the server, the web server, and the SSL certificates yourself. This guide walks through every step.


Before starting, you need:

  • A VPS from any provider (DigitalOcean, Linode, Vultr, Hetzner, etc.)
  • A domain name pointed to your VPS IP address
  • SSH access to your server (root or sudo user)
  • Your Astro Docus project built locally (npm run build works)

User → Nginx (port 443) → Static files in /var/www/your-domain/dist/
  • Nginx serves your static files directly. No Node.js runtime needed.
  • Let’s Encrypt provides free SSL certificates.
  • Certbot automates certificate installation and renewal.

This is the standard architecture for static sites on a VPS. Nginx is fast, reliable, and uses minimal resources .


Open your terminal and connect via SSH:

Terminal window
ssh root@YOUR_VPS_IP

Replace YOUR_VPS_IP with your server’s public IP address. On Windows, use PowerShell or an SSH client like PuTTY .

Once connected, update the package list:

Terminal window
apt update && apt upgrade -y

Astro requires a modern Node.js version. Ubuntu’s default repository often has an older version, so install from NodeSource :

Terminal window
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
apt install -y nodejs

Verify the installation:

Terminal window
node -v # should show v22.x.x
npm -v

If node -v shows a version below 22, rerun the NodeSource script as root .


Terminal window
apt install -y nginx
systemctl enable nginx
systemctl start nginx

Verify Nginx is running:

Terminal window
curl http://localhost

You should see the default Nginx welcome page HTML .


Install UFW (Uncomplicated Firewall) and open the necessary ports:

Terminal window
apt install -y ufw
ufw allow OpenSSH
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable

Check the rules:

Terminal window
ufw status

Expected output:

OpenSSH ALLOW Anywhere
80/tcp ALLOW Anywhere
443/tcp ALLOW Anywhere

Port 80 is needed for Let’s Encrypt validation. Port 443 is for HTTPS traffic. SSH must remain open or you will lock yourself out .


Step 5 — Upload Your Project to the Server

Section titled “Step 5 — Upload Your Project to the Server”

There are two ways to get your Astro Docus project onto the server.

If your project is on GitHub:

Terminal window
cd /var/www
git clone https://github.com/your-username/astrodocus.git
cd astrodocus
npm install

From your local machine (not the server):

Terminal window
rsync -avz ./your-astro-project root@YOUR_VPS_IP:/var/www/astrodocus

Then on the server:

Terminal window
cd /var/www/astrodocus
npm install

Terminal window
npm run build

This generates the dist/ folder containing your static HTML, CSS, and JavaScript files .

Verify the build:

Terminal window
ls dist/

You should see index.html, _astro/, and other files.


Step 7 — Configure Nginx for Static Serving

Section titled “Step 7 — Configure Nginx for Static Serving”

Create a new Nginx configuration file:

Terminal window
nano /etc/nginx/sites-available/astrodocus

Paste this configuration :

server {
listen 80;
listen [::]:80;
server_name your-domain.com www.your-domain.com;
root /var/www/astrodocus/dist;
index index.html;
# Gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml;
gzip_min_length 256;
# Cache hashed assets aggressively
location /_astro/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Cache other static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 30d;
add_header Cache-Control "public, no-transform";
}
# Handle routing
location / {
try_files $uri $uri/ =404;
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
}

Replace your-domain.com with your actual domain, and /var/www/astrodocus/dist with the path to your dist folder.

Why try_files $uri $uri/ =404? For static sites with multiple HTML files, this serves the exact file requested. Unlike SPA fallback (/index.html), it returns a 404 for missing pages instead of silently serving the homepage .

Enable the site and test:

Terminal window
ln -s /etc/nginx/sites-available/astrodocus /etc/nginx/sites-enabled/
nginx -t
systemctl reload nginx

Your site should now be accessible via HTTP at your domain.


Step 8 — Install SSL with Let’s Encrypt

Section titled “Step 8 — Install SSL with Let’s Encrypt”

Install Certbot and the Nginx plugin :

Terminal window
apt install -y certbot python3-certbot-nginx

Obtain a certificate:

Terminal window
certbot --nginx -d your-domain.com -d www.your-domain.com

Certbot will ask for your email address and whether to redirect HTTP to HTTPS. Choose Redirect (option 2) for automatic HTTPS .

After completion, Certbot automatically modifies your Nginx configuration to use SSL and sets up HTTP-to-HTTPS redirection.

Verify:

Terminal window
curl -I https://your-domain.com

You should see HTTP/2 200 and a valid SSL certificate.


Let’s Encrypt certificates are valid for 90 days. Certbot sets up automatic renewal via a systemd timer :

Terminal window
systemctl status certbot.timer

Test the renewal process:

Terminal window
certbot renew --dry-run

If no errors appear, renewal is working correctly.

Important: Keep port 80 open permanently. Let’s Encrypt validates your domain over HTTP on every renewal. Closing port 80 will cause certificates to expire silently .


Once the server is set up, you can automate deployments so every push to main updates the live site.

Option A — GitHub Actions with Self-Hosted Runner

Section titled “Option A — GitHub Actions with Self-Hosted Runner”

Run a GitHub Actions runner on your VPS. The workflow builds Astro and rsyncs dist/ to the web root :

name: Deploy to VPS
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: self-hosted
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm run build
- run: |
rsync -av --delete dist/ /var/www/astrodocus/dist/

Run a small Node.js webhook listener on the VPS. When GitHub pushes, the webhook pulls the latest code, rebuilds, and updates the Nginx site . This requires additional setup but avoids a self-hosted runner.


ProblemLikely CauseFix
403 ForbiddenFile permissionsEnsure Nginx user can read dist/ files
502 Bad GatewayNginx proxying to a stopped serviceRemove any proxy_pass — serve files directly
SSL certificate failsPort 80 blockedOpen port 80 in UFW and any cloud firewall
DNS not resolvingA record missingAdd an A record pointing to your VPS IP
Changes not showingBrowser cacheHard refresh (Ctrl+Shift+R)
Build fails on serverNode version mismatchVerify node -v shows v22+

1. SSH into VPS: ssh root@YOUR_VPS_IP
2. apt update && apt upgrade -y
3. Install Node.js 22 from NodeSource
4. apt install -y nginx && systemctl start nginx
5. Configure UFW: allow 22, 80, 443
6. Upload project: git clone or rsync
7. npm install && npm run build
8. Create Nginx config in /etc/nginx/sites-available/
9. Enable site and reload Nginx
10. Install Certbot: apt install certbot python3-certbot-nginx
11. Obtain SSL: certbot --nginx -d your-domain.com
12. Verify renewal: certbot renew --dry-run

A VPS gives you full control at low cost. Nginx serves static files efficiently, Certbot handles SSL automatically, and the total monthly cost is typically $4–6 for a modest server .

👉 Next: docs/deploy-hosting/cpanel-plesk.md — deploy to cPanel or Plesk shared hosting.