How to Host a Static Website on a VPS with Nginx (Hugo, Jekyll, Next.js)

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:

  1. Add your domain to Cloudflare (free account)
  2. Set the A record to your VPS IP with orange cloud (proxied)
  3. In Cloudflare → Caching → Cache Rules → Cache Everything for static assets
  4. 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.

Fast • Reliable • Affordable VPS - DO It Now!

Get top VPS hosting with VPS.DO’s fast, low-cost plans. Try risk-free with our 7-day no-questions-asked refund and start today!