DEV Community

Peon Sh
Peon Sh

Posted on Originally published at peon.sh

Host a Static Site on Your Own Server with Free SSL

Serve Astro, Hugo or plain HTML from a $4 VPS with automatic HTTPS and CDN caching. Cheaper and faster than you think.

Why self-host static sites at all
Static hosting SaaS is easy right up until the pricing cliff: bandwidth overages, per-seat team plans, build-minute quotas. Meanwhile a static site is the cheapest workload in computing, files behind a web server, and a $4 VPS serves thousands of requests per second and terabytes a month. If you already run a VPS for apps, static sites ride along for free; agencies routinely consolidate 30-plus client sites on one small machine.

The build-and-serve pattern
Build the site in one stage, serve the output with nginx in a tiny final image. Works for Astro, Hugo, Eleventy, Vite, Jekyll, Next.js export, anything that emits a folder of files:

FROM node:22-alpine AS build
WORKDIR /site
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build # emits ./dist
FROM nginx:alpine
COPY --from=build /site/dist /usr/share/nginx/html
# optional: custom nginx.conf for SPA fallback / cache headers

Or skip the Dockerfile entirely
A static-site service in Peon asks for three things: the repository, the build command and the publish directory. It builds on the server, serves the output behind the proxy, and issues the HTTPS certificate. Every push to the branch rebuilds and atomically swaps the content, so a broken build never half-deploys.

Cache headers and the CDN layer
Two rules cover static caching: hashed assets (main.abc123.js) are immutable, cache them for a year; HTML is the entry point, cache it briefly or not at all. Modern generators hash assets by default, so the config is small.

For global reach, put Cloudflare’s free tier in front: your DNS moves to Cloudflare, the orange-cloud proxy caches assets at 300+ edge locations, absorbs abusive traffic and gives you analytics. Origin bandwidth drops to cache-miss traffic only, and worldwide latency becomes competitive with any dedicated static host.

nginx: long cache for hashed assets, short for HTML

location /assets/ { add_header Cache-Control "public, max-age=31536000, immutable"; }
location / { add_header Cache-Control "public, max-age=300"; }
Enter fullscreen mode Exit fullscreen mode

SPA routing and previews
Single-page apps need a fallback so /dashboard/settings serves index.html: try_files $uri /index.html; in nginx
Preview environments: deploy feature branches as separate services on subdomains (pr-42.preview.example.com), a wildcard DNS record makes this zero-config
Forms and functions: a small API container on the same server replaces serverless form handlers, without submission caps

Top comments (0)