A static site is a directory of files that a web server hands out unchanged. There is no application to keep running, no database to back up, and nothing to patch on a Tuesday. Hosting one well comes down to four decisions: which directory you upload, how URLs map onto files, what the cache headers say, and what happens to the URLs you retire. Get those right and a 1 GB plan will serve a site that would have needed a small fleet fifteen years ago.
This is the whole job, from the build output of the common generators to the things a static site genuinely cannot do on its own.
What static hosting is, and what it costs you#
The server does almost nothing: a request arrives for /about/, it finds about/index.html on disk and sends it. No PHP process starts, no query runs. That is why static sites are fast by default and why the time to first byte is limited only by the network and the disk.
What you give up is anything that has to happen on the server at request time:
- Forms. A form needs somewhere to post to. A static host has nowhere.
- Anything per-user. Logins, carts, a dashboard. You can do it client-side against an API, but the API has to run somewhere.
- Server-side search. The pages are files, so search either happens in the visitor's browser or at a third party.
- Content editing by non-technical people. Without a CMS, a change is a rebuild, unless you add a headless CMS and a build hook.
What you keep is the ability to move the site anywhere in ten minutes, and a site that cannot be compromised through a plugin nobody updated. For a marketing site, documentation, a portfolio or a blog, that trade is nearly always correct. For a shop it is not: see WooCommerce hosting requirements or run a CMS properly instead.
A useful middle position is a static site on a plan that also runs PHP. The whole site is files, and one small PHP script handles the contact form. That is the shape most small business sites should be, and it needs no framework at all.
What to upload: build output by generator#
The single most common mistake is uploading the project instead of the build. Your repository contains sources, configuration, node_modules and history. The web server should see none of it. Every generator writes a finished directory; that directory, and only its contents, goes on the server.
| Generator | Build command | Output directory |
|---|---|---|
| Hugo | hugo | public/ |
| Jekyll | jekyll build | _site/ |
| Eleventy | npx @11ty/eleventy | _site/ |
| Astro | npm run build | dist/ |
| Vite (plain) | npm run build | dist/ |
| SvelteKit (static adapter) | npm run build | build/ |
Next.js (output: 'export') | npm run build | out/ |
Nuxt (nuxt generate) | npm run generate | .output/public/ |
| Gatsby | gatsby build | public/ |
| Docusaurus | npm run build | build/ |
| MkDocs | mkdocs build | site/ |
Check the output before you upload it. Two things are worth confirming every time: that index.html sits at the top level rather than one directory down, and that asset URLs are root-relative. A site built with a base path of /my-project/ and deployed to the root of a domain gives you a page with no styling and a browser console full of 404s. Every generator has a setting for this - baseURL in Hugo, base in Astro and Vite, baseurl in Jekyll - and it must match where the site actually lives.
Build on your own machine or in CI rather than on the web server. A 1 GB plan can run a build, but a large JavaScript project will hit the memory limit, and a host that stops the container at its limit will stop it mid-build. There is no reason to install Node on a server whose entire job is sending files.
Getting the files onto the server#
Three ways, in order of how much you will enjoy them.
rsync, if you have SSH. It copies only what changed and can delete what you removed, which matters because stale files are how a deleted page stays online for a year.
$ rsync -avz --delete --exclude '.DS_Store' dist/ user@example.com:/var/www/site/SFTP, which every host has. Use a client that can mirror a local directory, and remember that most will not delete remote files you no longer have locally unless you ask. Upload to a temporary directory and swap if you want the change to be atomic.
An archive, which is the fastest option on panel hosting. Zip the build, upload the one file, unpack it in place. On RE:NODE the file manager unpacks archives where they sit and has an editor in the browser for the file you inevitably need to fix afterwards, and SFTP credentials are issued per server - SFTP and the file manager has the connection details.
Whatever you use, exclude node_modules, .git, src and your environment files. Uploading .git publishes your entire history, including anything you committed and removed later, and automated scanners look for /.git/config on every site they find.
URLs: clean paths, 404 pages and SPA fallback#
Most generators write about/index.html rather than about.html, because a server given /about/ looks for index.html inside that directory. That is the default behaviour of nginx with index index.html; and of essentially every other web server, so a well-built static site needs no configuration to get clean URLs.
Where you control the server block, three lines cover almost everything:
root /var/www/site;index index.html;location / { try_files $uri $uri.html $uri/ =404;}error_page 404 /404.html;try_files checks each candidate in turn: the exact file, the same path with .html appended - which makes /about work for a generator that wrote about.html - then the directory, then a 404. The error_page line is what makes your designed 404 page appear instead of the server's grey one. Your generator almost certainly builds 404.html already.
A single-page application is different: every path has to return the same HTML and let the router sort it out.
location / { try_files $uri $uri/ /index.html;}That fallback is convenient and hides mistakes, because a typo in an asset path returns your HTML with a 200 instead of a 404, and the browser tries to parse it as JavaScript. If you see "Unexpected token '<'" in a console, that is what happened.
If you cannot edit the server configuration, build the site so it does not need rules. Directory-per-page output with real index.html files works everywhere, needs no try_files, and survives a move to any host. Note that .htaccess files do nothing on nginx, which is what most modern hosting runs - instructions you find for Apache will silently have no effect.
Caching and compression#
This is where a static site is won or lost, and it is two rules.
Fingerprinted assets get cached forever. Modern build tools write files with a content hash in the name - app.9f2c1b.css. Change the file, the name changes. Because the name is unique to the content, the browser never needs to check back:
location ~* \.(css|js|woff2|avif|webp|png|jpg|svg)$ { expires 1y; add_header Cache-Control "public, max-age=31536000, immutable";}HTML is never cached for long. It is the file that points at all the others, so it has to be fresh or a deploy takes hours to appear:
location ~* \.html$ { add_header Cache-Control "public, no-cache";}no-cache does not mean "do not store". It means "store it, but ask before reusing it", which gets you a cheap 304 response most of the time and an instant update when you publish. HTTP caching headers explained goes through every directive and what proxies do with them.
Compression is the other half. Text compresses by roughly 70 to 80 per cent, so a 200 KB HTML file becomes about 50 KB on the wire. nginx compresses on the fly with gzip on; and the right gzip_types, and can serve pre-compressed files with gzip_static on; if your build writes .gz alongside each asset, which costs the server nothing at request time. Do not compress images or video: they are already compressed, and re-compressing wastes CPU for nothing.
For images, the file format is worth more than any server setting. A hero image exported as a 2 MB PNG and shown at 800 pixels wide is the single most common cause of a slow "fast" site. Export at the size it is displayed, use WebP or AVIF, and always set width and height attributes so the browser reserves the space and the layout does not jump.
Redirects, and changing a URL without losing the traffic#
Every URL you have ever published is a promise. When you restructure a site, the old paths have to keep working or the links pointing at them stop being worth anything.
location = /old-page.html { return 301 /new-page/; }location ^~ /blog/2019/ { return 301 /archive/; }Use 301 for a move that is permanent, which is what you almost always mean. Browsers cache a 301 aggressively, so test with curl -I before you deploy one, and never use a 301 for something you might change back. 302 is temporary and is the safe choice when you are unsure.
Where you have no access to the server configuration, a <meta http-equiv="refresh"> page at the old path is the fallback. It works for humans, it passes most link value eventually, and it is worse than a 301 in every way. Use it only when the alternative is a dead URL.
The other redirect every site needs is between the apex and the www form: pick one canonical host and send the other there with a 301, so the two versions do not compete. www vs apex domains and redirects covers the DNS side, including why an apex CNAME is not a thing your registrar can necessarily do.
Forms, search and comments without a backend#
The three features people miss, and the honest options for each.
Forms. Either post to a third-party form service, or add a small PHP script on a plan that runs PHP. The script is about twenty lines: validate the fields, send the message, redirect to a thank-you page. One warning that catches everybody: PHP's mail() function needs a local mail transfer agent, and a hosting container generally does not have one. Send through an SMTP provider's API instead, from a domain whose records you have set up - why mail from your domain lands in spam explains SPF, DKIM and DMARC, which you will need regardless of who sends the mail. RE:NODE does not host email, so mail for your domain is always somebody else's service.
Search. For a site up to a few thousand pages, a client-side index is the right answer: Pagefind builds one at build time and loads fragments on demand, Lunr and Fuse do the indexing in the browser. Beyond that, a hosted search service. Nothing on the web server changes either way.
Comments. A third-party widget, a link to a discussion thread elsewhere, or nothing. "Nothing" is a defensible choice and the only one with no privacy consequences.
Adding any of these does not stop the site being static. The rule of thumb: if it needs to write to disk on the server, it is no longer a static site, and you should decide deliberately rather than by accident.
Domain, certificate and the launch checklist#
Pointing a domain at static files is the same as pointing it anywhere else: an A record for the apex and a record for www, then a certificate for both names. On a host with a reverse proxy in front, you point the record at the address it gives you and the certificate is issued once the name resolves there - on RE:NODE that renewal happens automatically inside a 21-day window, with no annual task. Pointing a domain at your server covers the order of operations, and HTTPS and Let's Encrypt explained covers why issuance fails when DNS is not ready yet.
Before you announce anything:
- Load the site over
https://and check the browser shows no mixed-content warning. - Check
/404returns your 404 page with a 404 status, not a 200. - Check
robots.txtandsitemap.xmlexist and list the canonical host. - Follow your own redirects with
curl -Iand confirm each is a single hop. - Load the site on a phone on mobile data, not on your office wifi.
Troubleshooting#
The page loads with no styling. The base path is wrong, or the CSS is 404ing. Open the network tab: if /assets/app.css returns HTML, your SPA fallback is catching it and the real file is somewhere else.
A directory listing instead of the site. There is no index.html where the server is looking, and directory indexes are switched on. Check you uploaded the contents of the build directory rather than the directory itself, which is how everything ends up one level too deep.
Changes do not appear. Something between you and the file is caching. Reload bypassing the cache, check the Cache-Control header the server sends for HTML, and if you are behind a CDN, purge it. This is the cost of a long max-age on HTML and the reason not to set one.
403 Forbidden on every page. File permissions. Files need to be readable by the web server user, and directories need the execute bit to be traversable: 644 for files, 755 for directories.
The site works at `www` but not the apex, or the reverse. Only one name is in the certificate or only one has a DNS record. Both need both.
Everything is fast for you and slow for visitors. You are near the server and they are not. A static site's remaining latency is distance, which a bigger plan does not change - see TTFB, Core Web Vitals and what hosting changes, and put a CDN in front if your audience is spread across continents.
FAQ#
How much hosting does a static site actually need?
Very little. Most static sites are a few megabytes of HTML and a few tens of megabytes of images, and serving a file costs almost no CPU. The smallest plan on any host is normally correct, and you upgrade for storage or for a traffic level far above what a small site sees.
Do I need Node.js on the server?
No. Node builds the site; it does not serve it. Build locally or in CI and upload the output. If you find yourself wanting Node running on the server, what you have is an application rather than a static site, and it belongs on an app plan.
Can I host several static sites on one plan?
Within the storage and the proxy limits of the plan, yes. Each site needs its own directory and its own hostname pointed at the server. Bigger tiers carry more storage and more of the proxy capacity.
Is a CDN necessary?
Only if your visitors are far from your server or you serve large media. A CDN caches copies near the visitor, which removes distance from the equation for static assets. It does nothing for a slow origin serving uncached HTML, and it adds a layer to debug - Cloudflare for websites and game servers covers what the proxy does and does not do.
How do I deploy automatically when I push?
Build in CI and have the job upload the output over SFTP or rsync. Keep the deploy to a single step that replaces the directory contents, so a half-finished upload never becomes a half-broken site.
What backs up a static site?
Your repository is the backup of the source, but not of the build or of anything uploaded afterwards. Take a backup of the served directory as well, on a schedule, and restore it once to prove it works - backups that actually restore explains why that second half matters.




Comments
Completely anonymous: no account, no email, no cookie. We store the name you type, the text and the time - nothing else. Links are limited and markup is not rendered.