# Sustainable websites: fewer moving parts, less consumption

> Static sites, small web servers like Static Web Server, caching and static export for WordPress: ways to save power, hardware, maintenance and money.

Source: https://www.jpkc.com/db/en/blog/nachhaltige-websites/

This knowledge base is made of finished files. Over a thousand HTML pages, almost as many Markdown twins, a search index — all of it is created by the build and then sits as a folder on an Apache server. For this site, the server runs no PHP, no database, no application process. Before I switched away from WordPress in 2020, I spent a long time optimising, caching and weeding out plugins. What convinces me most about the result today is not even the speed. It is **how little is running**.

That is what this article is about: websites with few moving parts. Fewer processes mean less power, less hardware, fewer updates and, in the end, a smaller bill. This is not a measurement report but a collection of approaches. I present three of them: the static site together with a matching lean web server, aggressive caching for systems that have to stay dynamic, and static export for WordPress. Plus a checklist that applies to each of the three.

If you are short on time: read [the section on Static Web Server](#a-web-server-with-few-moving-parts-static-web-server) and [the checklist](#checklist-savings-whichever-path-you-take).

## What a website consumes — and where

A website uses power in three places: in the data centre, in the network and on your visitors' devices. Version 4 of the [Sustainable Web Design Model](https://sustainablewebdesign.org/estimating-digital-emissions/), which the well-known [Website Carbon Calculator](https://www.websitecarbon.com/how-does-it-work/) also builds on, splits the energy like this: **22 % data centres, 24 % networks, 54 % user devices**. Its authors state themselves that the model estimates top-down, is likely to overestimate emissions and is hardly suited to judging individual parts of a system. So I take the numbers as a direction, not as a measurement.

The direction is clear, though. There are two levers: **transfer less** (which relieves the network and the device) and **compute less** (which relieves the data centre). There is plenty of room on the first one. According to the [Web Almanac 2025](https://almanac.httparchive.org/en/2025/page-weight), the median home page weighs 2.56 MB on mobile and 2.86 MB on desktop, 8.4 % more on mobile than a year earlier. The [2024 sustainability chapter](https://almanac.httparchive.org/en/2024/sustainability) found about half of all text resources uncompressed and roughly a quarter of mobile websites using no cache headers at all.

And the bigger picture keeps growing: the [International Energy Agency](https://www.iea.org/reports/energy-and-ai/energy-demand-from-ai) estimates that all data centres used around 415 TWh in 2024, about 1.5 % of global electricity consumption, and expects around 945 TWh by 2030. For Germany, the industry association [Bitkom](https://www.bitkom.org/Presse/Presseinformation/Rechenzentren-Deutschland-verliert-Anschluss) expects 20 billion kWh for 2024. A single website changes none of that. But every response that needs no computation is one that adds nothing to that curve — and that multiplies with every request.

If you want a systematic approach, the W3C's [Web Sustainability Guidelines](https://www.w3.org/TR/web-sustainability-guidelines/) offer a catalogue. They are a Group Note Draft (as of 24 September 2026), so not a W3C Recommendation, but well structured. I refer to the matching numbers below.

## Fewer moving parts: what a static site drops

A classic CMS page computes every page anew on every request: PHP starts, queries the database, renders the theme, loads plugins. A static site did that work once, at build time. Afterwards the server only reads a file and sends it.

| | Dynamic (PHP + database) | Dynamic with page cache | Static |
| --- | --- | --- | --- |
| Work per request | script + queries + rendering | hit: read file; miss: everything | read file |
| Always running | web server, PHP processes, database server | the same + cache | web server |
| Data to back up | files + database | the same | files (for me: the repository) |
| Compression | per response | hit: once per entry, depending on setup | possible once at build time |
| Versions you have to maintain | web server, PHP, database, CMS, plugins, theme | the same + cache layer | web server |
| Attack surface | application, admin area, database | the same | web server |

Most people underestimate the second-to-last row. Every runtime has an expiry date, and that forces work — whether the site's content changes or not. As of today, [PHP 8.2](https://www.php.net/supported-versions.php) only gets security updates until 31 December 2026. [MySQL 8.0](https://www.mysql.com/support/eol-notice.html) has received no new fixes since 21 April 2026 (Oracle "Sustaining Support" only), and [MariaDB 10.6](https://mariadb.org/about/) no community updates since July 2026. WordPress itself [recommends](https://wordpress.org/about/requirements/) PHP 8.3 or greater and "MariaDB version 10.11 or greater OR MySQL version 8.0 or greater" — including, among others, a database version that has just dropped out of maintenance. So if you run a WordPress site, you plan upgrades for three or four components that have nothing to do with your content. On the server, a static site needs none of them.

That reaches all the way into hardware. An application with a database wants memory reserved, even at night when nobody visits. Finished files run on the smallest shared hosting, on a small virtual server or on a single-board computer. Smaller plans are cheaper, and older or weaker hardware stays usable for longer. That counts twice, because the model includes not only operation but also the manufacture of the devices.

The honest downside: the work does not disappear, it moves to the build. I have to maintain my toolchain with Node.js and npm, and how I secure it is described in [Securing Node.js and npm](https://www.jpkc.com/db/en/blog/nodejs-npm-absichern/). The difference: that toolchain runs for a few minutes when I change something — locally or on a CI runner — not around the clock, publicly reachable, on a server. Why I chose 11ty back then and how much maintenance time it has saved is covered in detail in [Why 11ty](https://www.jpkc.com/db/en/blog/warum-11ty/).

## A web server with few moving parts: Static Web Server

A static site needs a web server, and that can be small or large too. I use Apache because the site lives in an existing document root. But if you want to run your static site yourself — on a small virtual server, in a container, on an ARM board — **[Static Web Server](https://static-web-server.net/)** (SWS for short) is a tool built exactly for that.

### What SWS is

SWS is written in Rust and built on the Hyper and Tokio libraries. The project describes itself as a "single ~4MB binary that runs everywhere". That figure is closer to the download: unpacked, the 2.44.0 Linux binary is just under 8.4 MB. The Linux musl builds are statically linked with no runtime dependencies, there is no garbage collector, and the licence is MIT or Apache 2.0. Pre-built binaries exist for Linux, macOS, Windows, FreeBSD, NetBSD, illumos and Android among others, for x86, x86_64, ARM and ARM64. According to Docker Hub, the `scratch`-based Docker image is under 4 MB compressed. The project gives no figure for memory use, only "low CPU and RAM overhead" — so I do not give one either.

What convinces me is its scope. Most of what a static site needs from a server is built in and available via a flag or the config file, with no modules to load:

- **Pre-compressed files**: if an `index.html.br`, `.gz` or `.zst` sits next to `index.html`, SWS serves it — according to the docs "with zero CPU cost". Compression happens once at build time instead of on every request. If you do not pre-compress, you get gzip, Brotli or zstd at runtime.
- **Caching**: `Cache-Control` headers and `Last-Modified` with conditional requests, so unchanged files only cost a `304`. ETag only arrives with v3.
- **Operations**: HTTP/2 and TLS, security headers, redirects and rewrites, custom 404 and 50x pages, Basic Auth, a `/health` endpoint, Prometheus metrics, graceful shutdown and socket activation via systemd.
- **Configuration**: everything as a command-line flag, environment variable or in an `sws.toml`.

### v2 is LTS, v3 is beta

The version situation is worth a close look. The stable line is **v2**, currently [2.44.0 from 31 July 2026](https://github.com/static-web-server/static-web-server/releases/tag/v2.44.0). With that release v2 moved to long-term support: only bug and security fixes from now on, and development has moved to **v3**. So far there is one pre-release, [3.0.0-beta.1 from 21 July 2026](https://github.com/static-web-server/static-web-server/releases/tag/v3.0.0-beta.1); more betas are announced, a date for the final release is not. For production, the project itself points to v2.

v3 is still exciting, because its defaults move in the right direction. According to the [3.0.0-beta.1 release notes](https://github.com/static-web-server/static-web-server/releases/tag/v3.0.0-beta.1) and the [migration guide](https://static-web-server.net/v3/migration-guide.html):

- Pre-compressed files and security headers are **on by default** (the security headers apply as soon as TLS is active).
- Versioned, permanently cacheable files get a one-year `max-age`, feeds one hour, everything else — HTML included — `Cache-Control: no-cache`. That is exactly the split you want for browser caching (more on that [below](#path-2-aggressive-caching-for-systems-that-stay-dynamic)).
- The new HTTP stack is Hyper 1, TLS and HTTP/2 have separate flags, logs come as JSON, and the built-in memory cache is considered stable.
- The default port in the beta is 8787 instead of 80. The development branch has changed that again since, so the defaults may still shift before the final release.
- v3 does not support WebAssembly for now.

If you set up today, take v2 and try v3 on a test system. One note on maintenance: even a small server needs updates. [2.44.0](https://github.com/static-web-server/static-web-server/releases/tag/v2.44.0) shipped four security fixes, two rated "moderate": pre-compressed files could escape the web root via symlinks, and `/metrics` was open despite Basic Auth. There was also a path traversal flaw ("low") in Markdown negotiation from 2.40.0. The difference from a CMS: the update is a single file.

### Markdown for AI systems, without rewrite rules

For this site's experiment, one feature is particularly interesting: [Markdown content negotiation](https://static-web-server.net/v3/features/markdown-content-negotiation.html). If a client — an AI agent, for example — sends `Accept: text/markdown`, SWS serves the Markdown version instead of the HTML page, if one exists. The feature is documented under v3 but is not new: it arrived with [v2.40.0 in November 2025](https://github.com/static-web-server/static-web-server/releases/tag/v2.40.0) and is usable in v2.

You switch it on with a flag:

```bash
static-web-server --root ./public --port 8080 --accept-markdown
curl -H "Accept: text/markdown" http://localhost:8080/article
```

Alternatively use the environment variable `SERVER_ACCEPT_MARKDOWN`, or `accept-markdown = true` in the `[general]` section of `sws.toml`. SWS then looks for `path.md`, `path.html.md` and `path/index.html.md` in turn and responds with `Content-Type: text/markdown; charset=utf-8`. The feature only kicks in for an explicit `text/markdown` in the Accept header. `Accept: */*` or `text/*` do not trigger it, so a normal browser keeps getting HTML.

Why I like that shows in my own setup. On Apache, the same negotiation runs through rewrite rules in the main site's `.htaccess`. The `/db/` start page itself needed an extra special rule, because the inherited rule looked for the wrong file there — and I only found that after a deploy. It works, but it is exactly the kind of moving part this article is about. With SWS it is one flag. Whether that fits this site's layout (`/foo/index.html` with the twin `/foo.md` next to it), I recreated with 2.44.0 on a test folder: `/foo/` and `/foo` return `foo.md` with `Accept: text/markdown`, and still the HTML without that header. Check how your generator places the Markdown files all the same.

There is one point you should know before you put a cache or CDN in front: SWS does **not set a `Vary: Accept` header** for this feature — neither the docs nor the [source code](https://github.com/static-web-server/static-web-server/blob/master/src/markdown.rs) do. A cache then does not know that two versions live under the same URL and could hand a browser the Markdown or an agent the HTML. Without a cache in front, that does not matter. With one, set the header yourself via [custom headers](https://static-web-server.net/v2/features/custom-http-headers.html) in `sws.toml` (`[[advanced.headers]]`). Careful: a custom `Vary` replaces the one SWS sets for compression, so use `Vary = "Accept, Accept-Encoding"`. My Apache configuration therefore sends `Vary: Accept` with HTML and Markdown.

How the Markdown offering of this site fits together as a whole, with `llms.txt` and a `.md` twin for every page, is covered in [Writing for AI](https://www.jpkc.com/db/en/blog/schreiben-fuer-ki/) and [GEO: visibility in AI answers](https://www.jpkc.com/db/en/blog/was-ist-geo/).

## Path 2: aggressive caching for systems that stay dynamic

Not every site can become static. Then caching is the second-best path: the page is computed once and then served as a finished copy until something changes. The layers build on each other.

**In the CMS.** WordPress has page cache plugins that write finished HTML files to disk. In expert mode, [WP Super Cache](https://wordpress.org/plugins/wp-super-cache/) can have Apache serve those files directly via `mod_rewrite` — so PHP does not even start for a hit. [Cache Enabler](https://wordpress.org/plugins/cache-enabler/) and [W3 Total Cache](https://wordpress.org/plugins/w3-total-cache/) work similarly, W3 Total Cache additionally with Redis or Memcached as storage. [LiteSpeed Cache](https://wordpress.org/plugins/litespeed-cache/) is a special case: its automatic page caching requires a LiteSpeed server or the QUIC.cloud CDN.

**On the server.** nginx brings its own cache in front of PHP with [`fastcgi_cache`](https://nginx.org/en/docs/http/ngx_http_fastcgi_module.html). With `fastcgi_cache_use_stale updating` and `fastcgi_cache_background_update`, it keeps serving the old version while fetching the new one in the background. Purging individual entries (`fastcgi_cache_purge`), however, is only available with the commercial subscription. Apache has [`mod_cache`](https://httpd.apache.org/docs/2.4/caching.html); in its quick handler mode, which is on by default (`CacheQuickHandler on`), it also skips authentication and authorisation, so protected content does not belong in it.

**As a reverse proxy.** The classic goes by a different name now: the open source project Varnish Cache has been renamed **[Vinyl Cache](https://vinyl-cache.org/)**. According to [Poul-Henning Kamp's announcement](https://vinyl-cache.org/organization/20-years.html), the reason is trademark law: Varnish Software's lawyers claim the name "Varnish Cache" for themselves. The first release under the new name was 9.0.0 in March 2026, the current one is 9.1.0; `varnishd` became `vinyld`. Under the old name, Varnish Software ships its own distribution built on Vinyl Cache. Functionally it is still the same thing: an HTTP cache you put in front of any server and control with the VCL language, including purge and ban for targeted invalidation.

**At the edge of the network.** A CDN keeps copies close to visitors. Cloudflare, for example, can also cache HTML with ["Cache Everything"](https://developers.cloudflare.com/cache/how-to/cache-rules/examples/cache-everything/) and warns itself that visitors may then see information not intended for them if the page has personalised parts. For WordPress there is [Automatic Platform Optimization](https://developers.cloudflare.com/automatic-platform-optimization/), which routes logged-in users around the cache.

### Where the cache leaks

Caching aggressively mostly means knowing when the cache does not apply. With WordPress it is the [cookies](https://developer.wordpress.org/advanced-administration/wordpress/cookies/). Anyone logged in (`wordpress_logged_in_…`) or who has commented once (`comment_author_…`) gets a freshly computed page with the common solutions. Add URLs with parameters, forms with one-time tokens and anything personalised. A cache that is bypassed on every other request saves correspondingly little.

The second problem is invalidation. Change a menu and every page is stale at once. And according to [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Caching), once a cache you do not control — a proxy along the way or the browser — holds a response with a long lifetime, there is no way to take it back. You can only purge caches you run yourself.

### Browser caching: the rule that always fits

That leads to the one rule that applies to all three paths (WSG 4.2): **short for HTML, long for assets.** HTML gets `Cache-Control: no-cache` — which does not mean "do not store" but "check before use", and thanks to ETag that usually costs only a `304`. CSS, JavaScript, fonts and images get a one-year lifetime and [`immutable`](https://www.rfc-editor.org/rfc/rfc8246.html), and every change gets a new file name with a version or hash. On this site it looks like this: HTML and Markdown expire immediately (`max-age=0`), CSS, JavaScript, fonts and images after a year. CSS and JavaScript get a new version number appended to their URL on every build, so a change arrives at once. This site does not set `immutable` at the moment.

For all the praise of caching: it **moves** the work instead of removing it. Behind the cache, PHP, the database and every update from the table above keep running. If you have already reached the point where almost everything comes from the cache, path 3 is worth a look.

## Path 3: going static after the fact — WordPress stays the editor

You do not have to give up WordPress to serve static files. Two plugins turn a WordPress installation into a static copy: the editors keep working in the familiar back end, visitors get finished files.

**[Staatic](https://wordpress.org/plugins/staatic/)** (version 1.13.2, more than 2,000 active installations) crawls the site, as its description puts it, "like a visitor" — starting from the front page plus any URLs you add — and stores the result as a publication. Even the free version deploys to a local directory, to Amazon S3 or compatible storage, to GitHub, Netlify, via SFTP or as a ZIP. Redirects, custom 404 pages and HTTP headers are included, as are origins behind Basic Auth. Forms, search, scheduled and change-based publications come with the premium version.

**[Simply Static](https://wordpress.org/plugins/simply-static/)** (version 3.8.16, more than 30,000 active installations) exports as a ZIP or to a local directory in the free version. The Pro version (according to the [pricing page](https://simplystatic.com/pricing/) 99 US dollars per year and site) deploys to GitHub, AWS S3, BunnyCDN and via SFTP among others, exports only changed pages and adds forms, a static search with Fuse.js or Algolia and even comments. Simply Static passes the comments on to the WordPress installation, which has to stay reachable for that.

Both are open about where the limit is: shops with cart and checkout, membership areas, logins, personalised content and anything that fetches data from the server at runtime. Contact forms and search need a replacement — an external form service, a webhook or a search that runs in the browser. This site shows that the latter holds up: you search its nearly one thousand pages in two languages with [Pagefind](https://pagefind.app/), an index the build creates as static files. No search server runs for it.

The biggest gain lies elsewhere, though. If visitors only see the copy, WordPress no longer has to be publicly reachable. Staatic explicitly recommends a "separate, preferably protected address" for it, and Simply Static [describes](https://docs.simplystatic.com/article/67-how-to-set-up-basic-auth) protection via Basic Auth, partly to avoid duplicate content in search engines. The WordPress behind it still needs its updates and its PHP version, but the attack surface on the public internet shrinks to the web server with the finished files. And that can be a small one — such as Static Web Server from above.

## Checklist: savings whichever path you take

These points apply to the static site just as much as to the cached CMS. The matching WSG guideline is in brackets.

- **Images first** (2.9). According to the Web Almanac, they are the largest single item on the mobile home page. Modern formats like AVIF and WebP, matching sizes via `srcset`, `loading="lazy"` below the visible area. This site generates AVIF and WebP at build time.
- **Self-host and subset fonts** (2.11). WOFF2, only the styles you really use, and only the characters you need. For the text diagrams here, the browser loads a dedicated font of just under 24 KB, only on pages that contain one. It is trimmed to the characters the diagrams use — box-drawing, block and shape characters plus the basics — instead of bringing a complete second font family.
- **Go easy on JavaScript and dependencies** (3.2, 3.5, 3.11, 3.12). Every script is transferred and executed on the device, and according to the model the device is the largest consumer. Scrutinise third-party embeds especially.
- **Compress once instead of every time** (4.3). Generate Brotli or zstd at build time and serve pre-compressed. How the tools work is shown in the cheat sheets for [gzip](https://www.jpkc.com/db/en/cheatsheets/archives/gzip/) and [zstd](https://www.jpkc.com/db/en/cheatsheets/archives/zstd/).
- **Set cache headers** (4.2): short for HTML, long for fingerprinted assets, see above.
- **Check for green hosting** (4.1). The Green Web Foundation's [Green Web Check](https://www.thegreenwebfoundation.org/green-web-check/) shows whether your provider proves green power; since 1 October 2026 the Green Web Foundation [no longer accepts carbon offsets](https://www.thegreenwebfoundation.org/support/are-offsets-allowed-as-evidence-for-verification/) as proof. In Germany, the [Energy Efficiency Act](https://www.gesetze-im-internet.de/enefg/__11.html) (EnEfG) requires data centres from 300 kW of connected load to cover 100 % of their electricity from renewables on balance from 2027. An amendment approved by the cabinet in June 2026 would push that deadline to 2030 and raise the threshold; the Bundestag has not decided yet.
- **Keep runtimes current** (3.15), wherever there are any. A supported version gets security fixes, and an upgrade you plan is cheaper than one under time pressure after support ends.
- **Clean up.** Old media, orphaned pages, plugins nobody uses any more: what is not there does not have to be backed up, maintained or served.

And dark mode? It saves power, but less than often claimed. A [2021 Purdue study](https://www.purdue.edu/newsroom/releases/2021/Q3/dark-mode-may-not-save-your-phones-battery-life-as-much-as-you-think,-but-there-are-a-few-silver-linings.html) measured only 3–9 % on OLED displays at typical brightness, and 39–47 % only at full brightness. On LCDs, whose backlight is always on, it does practically nothing. A nice side effect, not a lever.

## What ends up in your wallet

The environment and your bank account benefit in the same places. Fewer running processes mean smaller plans or smaller servers. Less database means less backup effort. Less transferred data means less traffic, if your provider bills by it. And static files can be placed on practically any shared hosting or object storage, which leaves you free to choose your provider.

The largest item, though, shows up on no invoice: your time. No update window for PHP and the database, no chasing plugins, no emergency because a vulnerability is being exploited. In [Why 11ty](https://www.jpkc.com/db/en/blog/warum-11ty/) I described how that effort has gone towards zero for me over six years.

## Where static does not fit

A shop with a cart, a customer portal, a community with accounts, anything with real-time data: these need a server that computes. That does not mean everything has to be dynamic, though. Splitting often pays off: the pages everyone sees — home page, product descriptions, blog, help — become static or aggressively cached, and only the small part that is genuinely personalised runs dynamically. Most requests then land on the cheap side.

## FAQ

### Is a static website automatically sustainable?

No. It saves computation on the server, but a static page with 5 MB of images and three tracking scripts still burdens the network and the device. The architecture is the good starting point; the checklist above decides the rest.

### How do I measure my website's carbon footprint?

The [Website Carbon Calculator](https://www.websitecarbon.com/) and the [CO2.js](https://developers.thegreenwebfoundation.org/co2js/overview/) library estimate it based on the Sustainable Web Design Model v4. These are estimates based on the amount of data transferred, not measurements — good for comparing before and after, not as an absolute number.

### Should I already run Static Web Server v3 in production?

Not yet. As of October 2026, v3 is a beta, and the defaults may still change before the final release. For production, the project recommends v2, which keeps receiving bug and security fixes as an LTS version. Markdown content negotiation is available there too, since 2.40.0.

### Can I keep WordPress and still serve static files?

Yes, with Staatic or Simply Static. WordPress remains your editing system, ideally not publicly reachable, and visitors get a static copy. Forms, search and comments then need a replacement; shops and membership areas do not work this way.

### What happened to Varnish?

Since the 9.0.0 release in March 2026, the open source project has been called Vinyl Cache. Varnish Software offers its own distribution built on it under the name "Varnish Cache". Technically it is the same reverse proxy cache with VCL.

## Conclusion

Sustainability on the web is largely a question of architecture: what does not run consumes nothing, needs no updates and cannot be attacked. The static site is the most consistent answer, and a small server like Static Web Server is the fitting complement if you host yourself. Where that is not possible, aggressive caching gets a lot out of it, and for WordPress, static export is a path that does not turn the editorial workflow upside down. The checklist pays off either way — for power use, for hardware and for your wallet.

## Further reading

Why this site is built with 11ty and how much maintenance that has saved is in [Why 11ty](https://www.jpkc.com/db/en/blog/warum-11ty/). How lightweight pages also score on Core Web Vitals is shown in [Core Web Vitals & performance](https://www.jpkc.com/db/en/blog/core-web-vitals/). The technical foundation for search engines is described in [Technical SEO: the foundation](https://www.jpkc.com/db/en/blog/technical-seo/). And why each page's Markdown twin matters for AI systems is explained in [Writing for AI](https://www.jpkc.com/db/en/blog/schreiben-fuer-ki/).

