TutorialsAugust 4, 20264 min read

How to Host a Static Website with Nginx

Serve a fast static site — plain HTML or a built React/Vue/Hugo bundle — with Nginx on Ubuntu 24.04, with correct caching and HTTPS.

NBy Nxeon

Static sites — plain HTML, or the built output of React, Vue, Hugo, or Astro — are the fastest and cheapest thing to host. There is no application process, just files served directly by Nginx. This guide sets up a static site on Ubuntu 24.04 with sensible caching, single-page-app routing, and free HTTPS.

Why static hosting?

A static site has no database and no backend process, so it is fast, secure by default (nothing to exploit server-side), and light on resources — a small VPS can serve a huge amount of static traffic. If you are choosing between hosting options, our overview of how to host a website puts static hosting in context.

Install Nginx and place your files

Install Nginx if you have not already (full walkthrough in installing Nginx on Ubuntu 24.04):

sudo apt update && sudo apt install nginx -y
sudo ufw allow 'Nginx Full'

Create a web root and copy your site into it. If you built a site locally, upload the dist/build/public folder:

sudo mkdir -p /var/www/example.com
sudo chown -R $USER:$USER /var/www/example.com
# from your machine:  scp -r ./dist/* youruser@SERVER_IP:/var/www/example.com/

Create the server block

Create /etc/nginx/sites-available/example.com:

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;
    root /var/www/example.com;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }

    # Long cache for fingerprinted assets
    location ~* \.(?:css|js|woff2|png|jpg|jpeg|gif|svg|ico)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
    }
}

Enable it and drop the default:

sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginx

The asset caching block tells browsers to cache CSS, JS, and images for 30 days — a big speed win. Build tools that fingerprint filenames (like app.abc123.js) make immutable caching completely safe.

The Nxeon game-server control panel — live console, player slots, and TPS
The Nxeon game-server control panel — live console, player slots, and TPS

Single-page apps: fix the routing

React, Vue, and other SPAs handle routing in the browser, so a hard refresh on /about returns a 404 unless you rewrite unknown paths to index.html. Change the main location block:

    location / {
        try_files $uri $uri/ /index.html;
    }

Now any path that is not a real file falls back to index.html, letting the app's router take over. This is the single most common static-hosting gotcha.

Enable gzip compression

Nginx's default config usually enables gzip, but confirm it for text assets. In /etc/nginx/nginx.conf ensure:

    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml;

Reload after changes. Compression dramatically shrinks HTML, CSS, and JS transfer sizes.

Add HTTPS

Point your domain at the server (how to point a domain to your VPS) and get a free certificate:

sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d example.com -d www.example.com

The Certbot guide covers renewal. If you would rather have HTTPS handled automatically, Caddy is an even simpler option for static sites.

Deploying updates

Rebuild locally and re-upload, or automate it with git-based deploys whose hook runs your build and copies the output into the web root.

Add security headers and a custom 404

Even a static site benefits from a few response headers. Add them to your server block so every file is served with them:

    add_header X-Content-Type-Options nosniff always;
    add_header X-Frame-Options SAMEORIGIN always;
    add_header Referrer-Policy strict-origin-when-cross-origin always;

A custom error page is a nice touch too. Create /var/www/example.com/404.html, then point Nginx at it:

    error_page 404 /404.html;

Reload with sudo nginx -t && sudo systemctl reload nginx. Visitors who hit a dead link now see your styled page instead of the bare default.

Test performance

Static sites should be fast, so confirm it. From the server, time a request and check the headers you set are present:

curl -s -o /dev/null -w '%{time_total}s\n' https://example.com
curl -I https://example.com

With gzip on, long cache headers, and HTTP/2 (which Nginx enables automatically over TLS), a static site on a modest VPS loads quickly for visitors anywhere.

FAQ

Do I need Node running to host a React site?

No — if you build it to static files (npm run build), Nginx serves the output directly with no Node process. You only need a running server for SSR or API routes; see deploying a Next.js app for that case.

Why does my SPA 404 on refresh?

Nginx is looking for a real file at that path. Add the try_files $uri $uri/ /index.html; fallback so client-side routes resolve to the app shell.

How do I host multiple static sites on one VPS?

Create a separate server block per domain, each with its own root and server_name. One Nginx instance happily serves many sites — see running staging and production on one VPS.

Is a VPS overkill for a static site?

Not if you want full control, custom headers, multiple sites, and no per-request pricing. A small VPS serves enormous static traffic cheaply.

Nxeon web hosting on a VPS gives you full root and fast NVMe to serve static sites with total control — with free migration help to move an existing site across.

#nginx#static site#web hosting#ubuntu#vps#seobatch

Deploy your first server in under a minute

Creating an account is free and takes no card details. You pay when you deploy — choose a billing term and pay from your wallet or by card at checkout.