How to Host a Static Website on a VPS with Nginx (Hugo, Jekyll, Next.js)
Static websites — generated HTML, CSS, and JavaScript with no server-side processing — are the fastest, most secure, and most cost-effective way to deliver web content. Platforms like Vercel and Netlify handle static hosting well, but a VPS gives you something they don’t: complete control, no bandwidth limits, no function execution caps, and the ability to host unlimited sites for a flat monthly fee.
This guide covers serving static websites built with Hugo, Jekyll, or Next.js static export on a VPS using Nginx — with SSL, caching headers, Brotli compression, and optional CDN integration.
Why Host Static Sites on a VPS?
- Unlimited sites, flat cost — Host 50 static sites on one VPS for $20/month vs paying per-site on managed platforms
- No vendor lock-in — Your files, your server, your deployment pipeline
- Maximum performance — Nginx serving static files has near-zero CPU overhead and can handle thousands of concurrent connections
- Custom headers and rules — Set any HTTP header, redirect, or rewrite rule you need
- Private repositories — Deploy from private Git repos without granting third-party access
Performance Benchmark: Nginx Static vs Dynamic
| Setup | Requests/sec (1 vCPU) | TTFB |
|---|---|---|
| Nginx serving static HTML | 50,000+ | 1–5ms |
| Nginx + PHP-FPM (WordPress) | 200–500 | 100–300ms |
| Nginx + Redis cache (WordPress) | 2,000–5,000 | 20–50ms |
A single VPS serving static HTML can handle traffic levels that would require a cluster of WordPress servers with expensive caching infrastructure.
Part 1: Deploy a Hugo Site
Install Hugo on your local machine
# On Linux/Mac
wget https://github.com/gohugoio/hugo/releases/latest/download/hugo_extended_Linux_amd64.tar.gz
tar -xzf hugo_extended_Linux_amd64.tar.gz
sudo mv hugo /usr/local/bin/
# Verify
hugo version
Create and build your Hugo site
hugo new site mysite
cd mysite
# Add a theme (example: PaperMod)
git init
git submodule add https://github.com/adityatelange/hugo-PaperMod themes/PaperMod
echo 'theme = "PaperMod"' >> hugo.toml
# Build the site
hugo --minify
# Output is in the ./public/ directory
Deploy to VPS
# Push the built site to your VPS
rsync -avz --delete ./public/ user@YOUR_VPS_IP:/var/www/mysite.com/
Automate deployment with a script
nano ~/deploy-hugo.sh
#!/bin/bash
set -e
echo "Building Hugo site..."
hugo --minify
echo "Deploying to VPS..."
rsync -avz --delete ./public/ user@YOUR_VPS_IP:/var/www/mysite.com/
echo "✅ Deployed successfully!"
Part 2: Deploy a Jekyll Site
# Install Ruby and Jekyll (on your local machine)
sudo apt install ruby-full build-essential -y
gem install jekyll bundler
# Create new site
jekyll new mysite
cd mysite
# Build
bundle exec jekyll build
# Output is in ./_site/
# Deploy to VPS
rsync -avz --delete ./_site/ user@YOUR_VPS_IP:/var/www/mysite.com/
Part 3: Deploy a Next.js Static Export
# In next.config.js, enable static export
module.exports = {
output: 'export',
trailingSlash: true, // Better compatibility with Nginx
images: {
unoptimized: true // Required for static export
}
}
# Build and export
npm run build
# Output is in ./out/
# Deploy to VPS
rsync -avz --delete ./out/ user@YOUR_VPS_IP:/var/www/mysite.com/
Part 4: Configure Nginx for Static Sites
Install Nginx on VPS
sudo apt update && sudo apt install nginx -y
sudo systemctl enable nginx
Create web root and deploy files
sudo mkdir -p /var/www/mysite.com
sudo chown -R $USER:www-data /var/www/mysite.com
Production Nginx config for static sites
sudo nano /etc/nginx/sites-available/mysite.com
server {
listen 80;
server_name mysite.com www.mysite.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
http2 on;
server_name mysite.com www.mysite.com;
root /var/www/mysite.com;
index index.html;
ssl_certificate /etc/letsencrypt/live/mysite.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mysite.com/privkey.pem;
include snippets/ssl-params.conf; # If you created the shared SSL snippet
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
server_tokens off;
# ── Static file serving ───────────────────────────────
location / {
try_files $uri $uri/ $uri.html =404;
# For Next.js with trailingSlash: try index.html in directory
# try_files $uri $uri/ /index.html;
}
# ── Aggressive caching for assets with content hashes ─
# Hugo/Jekyll/Next.js generate hashed filenames like: main.a3f4b2.css
location ~* \.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# ── Short cache for HTML (so updates propagate quickly) ─
location ~* \.html$ {
expires 1h;
add_header Cache-Control "public, must-revalidate";
}
# ── Gzip compression ──────────────────────────────────
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json
application/javascript application/xml+rss
application/atom+xml image/svg+xml;
# ── Custom 404 page ───────────────────────────────────
error_page 404 /404.html;
location = /404.html {
internal;
}
# ── Deny access to hidden files ───────────────────────
location ~ /\. {
deny all;
}
access_log /var/log/nginx/mysite.com.access.log;
error_log /var/log/nginx/mysite.com.error.log;
}
sudo ln -s /etc/nginx/sites-available/mysite.com /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
# SSL
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d mysite.com -d www.mysite.com
Part 5: Hosting Multiple Static Sites
One VPS can host dozens of static sites. Each site gets its own Nginx config:
# Create directories for each site
sudo mkdir -p /var/www/{site1.com,site2.com,site3.com}
# Create Nginx configs
for SITE in site1.com site2.com site3.com; do
sudo cp /etc/nginx/sites-available/mysite.com \
/etc/nginx/sites-available/$SITE
sudo sed -i "s/mysite.com/$SITE/g" /etc/nginx/sites-available/$SITE
sudo ln -s /etc/nginx/sites-available/$SITE \
/etc/nginx/sites-enabled/$SITE
done
sudo nginx -t && sudo systemctl reload nginx
# Issue SSL for all domains at once
sudo certbot --nginx \
-d site1.com -d www.site1.com \
-d site2.com -d www.site2.com \
-d site3.com -d www.site3.com
Part 6: Automated Deployment with GitHub Actions
mkdir -p .github/workflows
nano .github/workflows/deploy.yml
name: Deploy Static Site
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Hugo build
- name: Setup Hugo
uses: peaceiris/actions-hugo@v3
with:
hugo-version: 'latest'
extended: true
- name: Build Hugo site
run: hugo --minify
# Deploy to VPS
- name: Deploy to VPS
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_SSH_KEY }}
port: ${{ secrets.VPS_PORT }}
source: "public/*"
target: "/var/www/mysite.com/"
strip_components: 1
Now every push to main automatically rebuilds and deploys your site. ✅
Part 7: Add Cloudflare CDN (Optional)
For global audiences, add Cloudflare in front of your Nginx server:
- Add your domain to Cloudflare (free account)
- Set the A record to your VPS IP with orange cloud (proxied)
- In Cloudflare → Caching → Cache Rules → Cache Everything for static assets
- Set Browser Cache TTL to 1 year for hashed assets
With Cloudflare caching static assets in 300+ global PoPs, your site loads in under 50ms for users worldwide — from a single $20/month VPS.
Performance Checklist
- ✅ Gzip compression enabled (reduces HTML/CSS/JS by 60–80%)
- ✅ Long cache headers for hashed assets (1 year)
- ✅ Short cache for HTML (1 hour)
- ✅ HTTP/2 enabled (multiplexes assets over single connection)
- ✅ SSL configured with OCSP stapling
- ✅ Images optimized before deploy (WebP format where possible)
- ✅ CDN in front for global distribution (optional)
Final Thoughts
Static sites on Nginx represent the intersection of simplicity and performance. A VPS serving pre-built HTML can handle traffic volumes that would require expensive infrastructure for dynamic sites — and the deployment pipeline is as simple as an rsync command or a GitHub Action.
VPS.DO’s USA VPS 500SSD plan gives you 500 GB of SSD storage for static file serving — enough for hundreds of static sites — at $20/month with a 1 Gbps port.