All WordPress HTML Templates Forms & Webhooks AI & Tools
HTML Templates

Static Site Hosting in 2026: Netlify, Vercel, Pages and Plain Nginx

Static site hosting in 2026, compared by a studio that ships it: Netlify, Vercel, Cloudflare Pages, GitHub Pages and plain Nginx, with real config and costs.

A real working moment illustrating the theme of an article about static site hosting. Wide 16:9 banner, one strong focal point, magazine editorial quality, authentic and unstaged.

A client called last March about a marketing site we’d shipped as flat HTML two years earlier. The site had been fine. Then a product launch pushed it onto the front page of a few aggregators, traffic went from 400 visits a day to 90,000 in six hours, and the hosting bill for that month arrived at roughly forty times the usual. Nothing broke. The site stayed up, served every byte. The invoice was the incident.

That’s the real shape of static site hosting decisions now. Uptime is largely solved. What differs between Netlify, Vercel, Cloudflare Pages, GitHub Pages and a $6 VPS running Nginx is pricing geometry, how much routing logic gets locked into a vendor-specific config file, and what happens at 3am when the build pipeline that only exists in someone else’s cloud decides your lockfile is unacceptable.

We run all five across client work. Here’s how we actually choose.

Key Takeaways

  • Cloudflare Pages is the default recommendation for most brochure and marketing sites because bandwidth isn’t metered on any plan, which removes the single biggest source of surprise invoices.
  • Netlify and Vercel earn their price through preview deploys, build caching and team workflow, not through serving files. If your team is one person, you’re paying for features you won’t open.
  • Vercel’s Hobby tier forbids commercial use. Shipping a client site on it is a terms violation waiting to be enforced, and we’ve seen projects migrated under deadline because of it.
  • Plain Nginx on a small VPS costs about $5 to $12 a month, serves unlimited traffic within your transfer allowance, and takes roughly 40 lines of config to get right, including the add_header inheritance trap that silently drops your security headers.
  • Keep redirects, headers and build commands in a form you can port. A _redirects file translates to Nginx in an afternoon; 200 rewrite rules buried in vercel.json with edge middleware does not.

What counts as static in 2026

The word has drifted. Ten years ago static meant HTML, CSS, JS and images on a disk. Today “static hosting” platforms all ship serverless functions, edge middleware, image transformation, incremental revalidation and KV storage. The moment you use any of them, you’ve stopped hosting a static site and started hosting an application with a static front end.

This matters because the pricing, the portability and the failure modes are completely different. A pure static build has no cold starts, no runtime versions to upgrade, no dependency CVEs in production, and can be served by literally anything. Add one serverless function for a contact form and you’ve inherited a runtime, a billing dimension measured in invocations and GB-seconds, and a thing that can break while your HTML stays perfectly healthy.

So the first question isn’t which host. It’s whether your site is genuinely static. If you’re building with something like Canvas or any Bootstrap-based HTML build, the honest answer is usually yes, and you should fight hard to keep it that way. Every dynamic bit you add narrows your hosting options and widens your blast radius.

A real working moment that fits an article section about

The four contenders, judged on what breaks

Netlify

Still the most pleasant developer experience of the four. Deploy previews on every pull request, instant rollbacks to any prior deploy, branch subdomains, a _redirects file that’s genuinely the nicest routing syntax in the category, and form handling that works without writing a function.

The weak point is bandwidth and build minutes economics. The free tier has historically included 100 GB of bandwidth and 300 build minutes a month, and overages are billed in blocks rather than smoothly. A large media site or a popular download will chew through that fast. Netlify has improved its spend controls since the viral billing incidents of a few years ago, but set a hard cap the day you connect a domain, not the day you get an email.

Use Netlify when a small team needs review previews and non-developers need to look at a branch URL.

Vercel

Pick Vercel if you’re deploying Next.js. That’s not a slight, it’s where the value is concentrated. ISR, streaming, the image optimiser and middleware all behave as designed there, and nowhere else will match it without real effort. For a flat HTML site, Vercel is a competent CDN with an excellent CLI and a pricing model aimed at applications.

Two things to know. Hobby is non-commercial, full stop, so a client site on it is out of compliance. And Vercel’s usage-based Pro pricing bills across several dimensions at once (data transfer, function duration, edge requests, image transformations), which is fine when you understand your traffic and ugly when a crawler or a small DDoS finds you. Enable the firewall and attack challenge mode before you need them.

Cloudflare Pages

This is our default for marketing sites, documentation and anything that might get popular. Bandwidth isn’t metered on any tier. That single property is worth more than everything else in this comparison when you’re running a site with unpredictable traffic. The free plan has allowed 500 builds a month with unlimited sites and unlimited requests, and the published per-deployment limits sit around 20,000 files with a 25 MiB cap per file.

The 20,000 file limit is the one that bites. A large statically generated site with per-tag, per-author and per-page archives will cross it faster than you expect, and the error arrives at deploy time, not at build time. The build environment is also less forgiving than Netlify’s, and the dashboard is noticeably clunkier for non-technical stakeholders. Those are acceptable trades for uncapped egress.

GitHub Pages

Fine for documentation, open source project sites and personal pages. Free, custom domains with TLS, zero config if your repo is already there. The published limits are a 1 GB repository, a 100 GB per month soft bandwidth ceiling and 10 builds per hour, and the terms describe it as unsuitable for commercial operations like e-commerce.

No redirect support beyond what you can fake with meta refresh, no custom headers, no preview deploys. If you need a 301 from an old URL structure, you’re already on the wrong platform. We use it for internal docs and nothing a client pays for.

Egress is the only line item that will surprise you

Compute on a static site is zero. Storage is pennies. The variable is bytes out, and it’s the one you can’t predict because it’s set by strangers.

Run the arithmetic before you choose. A 1.8 MB page weight (perfectly normal once you have a hero image, a webfont pair and an analytics script) at 100,000 monthly pageviews is roughly 180 GB of transfer. That’s already past the free tier on Netlify, Vercel and GitHub Pages. On Cloudflare Pages it costs nothing. On a Hetzner or DigitalOcean VPS with a 1 TB to 20 TB transfer allowance, it costs nothing extra either.

Then cut the page weight anyway. We’ve taken client sites from 2.4 MB to 480 KB mostly by converting hero images to AVIF with a WebP fallback, subsetting fonts to the Latin range, and deleting two tag managers nobody could name an owner for. A fivefold reduction in the only metered resource you have, and it shows up in Largest Contentful Paint at the same time.

The plain Nginx config that actually holds up

There’s a persistent idea that running your own server for static files is a liability. For a truly static site it’s about as close to zero-maintenance as server software gets: no PHP-FPM pool to tune, no database, no application runtime to patch. Unattended upgrades plus Certbot plus roughly 40 lines of config, and you’re done for years.

server {
    listen 443 ssl;
    http2 on;                    # separate directive since nginx 1.25.1
    server_name example.com;

    root /var/www/example.com/current;
    index index.html;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    sslcertificatekey /etc/letsencrypt/live/example.com/privkey.pem;

    # Content-hashed assets: safe to cache for a year
    location ~* \.(?:css|js|woff2|avif|webp|png|jpe?g|svg)$ {
        expires 1y;
        add_header Cache-Control "public, immutable" always;
        add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
        access_log off;
    }

    # HTML: revalidate every time so a deploy is live instantly
    location / {
        try_files $uri $uri.html $uri/ =404;
        add_header Cache-Control "no-cache" always;
        add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
        add_header X-Content-Type-Options "nosniff" always;
    }

    gzip on;
    gzip_vary on;
    gzipminlength 1024;
    gzip_types text/css application/javascript application/json image/svg+xml;

    error_page 404 /404.html;
}

The non-obvious part is the repetition. Nginx’s addheader does not inherit into a block that declares its own addheader, so the HSTS line you set once at server level vanishes the moment a nested location adds a cache header. We debugged that exact thing on a production site where a security scan flagged missing headers on CSS files only. Repeat them, or use the headers-more-nginx-module if you’d rather not.

Note also try_files $uri $uri.html $uri/, which gives you clean extensionless URLs without a rewrite rule, and no-cache on HTML, which means “revalidate”, not “don’t store”. For a single-page app, swap the final fallback to /index.html instead of =404.

Atomic deploys with a symlink

#!/usr/bin/env bash
set -euo pipefail

SITE=/var/www/example.com
RELEASE="$SITE/releases/$(date +%Y%m%d%H%M%S)"

rsync -a --delete ./dist/ "$RELEASE/"

Two-step rename: ln -sfn on an existing dir symlink nests instead of replacing

ln -sfn "$RELEASE" "$SITE/current.tmp" mv -Tf "$SITE/current.tmp" "$SITE/current"

Keep the five most recent releases for instant rollback

ls -1dt "$SITE"/releases/* | tail -n +6 | xargs -r rm -rf

Rollback is then one mv away, which is faster than any dashboard. One caveat: if you’ve enabled openfilecache, Nginx can keep serving file descriptors from the old release for up to the inactive timeout. Either leave it off for static sites (the kernel page cache is doing the real work) or send a reload after the swap.

If you’d rather not touch Nginx at all, Caddy does automatic TLS with a four-line Caddyfile and is the better choice for anyone who doesn’t already know Nginx’s quirks.

Redirects, headers and the portability tax

The thing that makes migration painful is never the files. It’s the accumulated edge logic: six years of URL changes, locale detection, a legacy /blog/2019/* pattern, an A/B split, auth on a staging branch.

Keep it in the plainest form your host supports. Netlify’s redirects and Cloudflare’s redirects are nearly the same syntax and both convert mechanically to Nginx return 301 directives. Cloudflare’s headers file maps cleanly to addheader. Vercel’s vercel.json is JSON but still declarative and portable. Edge middleware written in JavaScript is not portable at all, and every rule you move into it is a rule you’ll rewrite by hand later.

# _redirects (Netlify / Cloudflare Pages)
/old-pricing          /pricing             301
/blog/2019/*          /blog/:splat         301
/docs/*               /guides/:splat       301!   # ! forces, even if a file exists

Write a migration test instead of trusting the rules. A 30-line script that curls your top 200 URLs from the old sitemap and asserts the final status code and location is the cheapest insurance in this whole process, and it catches the redirect chains that quietly cost you rankings.

Forms, search and the other things a static site still needs

Every static site eventually needs a contact form. This is the moment most teams reach for a serverless function, and it’s usually the wrong move. You’ve now got a runtime, a secrets store, a spam problem and a deliverability problem, all to accept six fields.

Use a hosted endpoint instead. Post straight to an API, let it handle validation, spam filtering and fan-out to email, Slack or your CRM. We built WebForms for exactly this, because we were tired of writing the same Lambda on every project.

<form action="https://webforms.to/submit/YOURENDPOINTID" method="POST">
  <input type="email" name="email" required autocomplete="email">
  <textarea name="message" required minlength="10"></textarea>
  <!-- Honeypot: bots fill it, humans never see it -->
  <input type="text" name="_gotcha" tabindex="-1" aria-hidden="true" hidden>
  <button type="submit">Send</button>
</form>

That form works identically on all five hosts, which is the point. For the rest: site search goes to Pagefind (builds a static index at deploy time, no service to run) or Algolia if you need typo tolerance. Comments go to a hosted service or nowhere. Analytics goes to something that doesn’t need a cookie banner. If you’re wiring submissions into multiple destinations, we’ve written up the routing patterns in routing form submissions to Slack, email and your CRM, and the local webhook testing piece covers how to debug the receiving end before you ship.

How we choose static site hosting, by project type

  • Client brochure or marketing site, unpredictable traffic: Cloudflare Pages. Unmetered bandwidth means the launch-day spike is a nice problem, not a billing one.
  • Team of 3 to 10 with a review workflow and non-technical approvers: Netlify. The preview deploy URLs pay for the plan on their own.
  • Next.js application with ISR, middleware or image optimisation: Vercel. Anywhere else and you’re fighting the framework.
  • Open source docs, personal site, internal reference: GitHub Pages. Free, already in the repo, nothing to administer.
  • You already run a VPS, need odd headers, geo rules or regulatory data residency, or you want to host an HTML site with zero vendor coupling: Nginx or Caddy. Add Cloudflare in front for free TLS termination, caching and DDoS absorption, and you’ve got most of a commercial platform for the price of a coffee.

The best static hosting for your next project is mostly a question of which failure you can least afford: a surprise invoice, a build you can’t reproduce locally, or an hour of your own time configuring a server. Price those honestly and the answer tends to be obvious.

Frequently Asked Questions

Is a VPS with Nginx really cheaper than a managed platform?

For traffic above roughly 150 GB a month, yes, usually by a wide margin, since a $6 VPS typically includes a 1 TB or larger transfer allowance. Below that, the free tiers win on cost and you’re choosing a VPS for control, not price. Factor in your own time: if you don’t already run servers, the first setup will cost you two to three hours.

Can I host an HTML site on Cloudflare Pages without using Git?

Yes. Use Wrangler with npx wrangler pages deploy ./dist and it uploads a directory straight from your machine or CI. That’s also the escape hatch when your build won’t run in Cloudflare’s build environment: build locally or in GitHub Actions, then push the artifact.

Do I still need a CDN in front of a static host?

No, all four managed platforms are already CDNs with global points of presence. You only add Cloudflare in front of a self-hosted Nginx box, where it gives you TLS, edge caching and attack mitigation for free. Stacking two CDNs on a managed host just adds a cache layer you have to purge twice.

What breaks when I migrate between these platforms?

Redirects, custom headers, environment variables and any serverless functions, roughly in that order of frequency. The HTML and assets move with a single rsync or upload. Budget a day for a mature site and verify with a scripted crawl of your top URLs against the old sitemap, checking both status codes and final destinations.

Is Vercel’s free tier really off limits for client work?

Yes. The Hobby plan has consistently prohibited commercial use, which includes a site you were paid to build or one that sells anything. Enforcement is inconsistent, but we’ve had to migrate a project mid-sprint after a notice arrived, and that’s an expensive way to save $20 a month.

If you’re starting a static project this quarter, the default worth arguing against is Cloudflare Pages plus a hosted form endpoint. It removes the two things that actually go wrong (unexpected egress bills and a half-maintained serverless function) and costs nothing until you need teams and preview workflows. Set a billing alert on whatever you pick, write the redirect test before you need it, and keep your routing rules in a file you could port in an afternoon.

atomic deploy symlink rsync static site best static hosting for client sites Cloudflare Pages unmetered bandwidth limits host html site on nginx Netlify vs Vercel vs Cloudflare Pages nginx static site config cache headers static site hosting static site hosting 2026