# Lwan: an experimental C web server you build from source

> Lwan is a high-performance HTTP server written in C, distributed as source and a container image rather than a drop-in package. It is aimed at engineers who want to embed an HTTP server in a C program or study a small server's internals, not at teams replacing nginx tomorrow.

**lpereira/lwan** — Experimental, scalable, high performance HTTP server

- Repository: https://github.com/lpereira/lwan
- Website: https://lwan.ws
- Stars: 6,037 · Forks: 550
- Language: C
- License: GPL-2.0
- Published: 2026-09-22 · Updated: 2026-09-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/lpereira-lwan

## What Lwan is for, and what it is not

Lwan is described in its README as a high-performance and scalable web server, and the repository tags it as experimental. The primary language is C, the licence is GPL-2.0, and the project lives at lwan.ws. That combination tells you the intended audience: people who are comfortable reading C, running CMake, and linking a server library into their own binary rather than configuring a packaged daemon.

The build produces more than one executable. Alongside src/bin/lwan/lwan, the main binary, the build generates a testrunner, several sample servers under src/samples (freegeoip, techempower, clock, forthsalon), and a set of build-time tools under src/bin/tools. The presence of a TechEmpower benchmark suite and a bundled weighttp rewrite signals that performance measurement is part of the project's own workflow, not an afterthought. The README is explicit that -DCMAKE_BUILD_TYPE=Release should be used when benchmarking, and warns that the default Debug build logs every request to standard output while holding a lock.

What it is not: the README does not present Lwan as a configuration-driven replacement for a general-purpose web server. There is a lwan.conf at the repository root and a wwwroot directory, but the documentation shown here stops at building and running the samples. Anyone expecting a documented config reference, a plugin ecosystem or a release cadence tied to a distribution will not find it in the README.

## How the build and runtime are put together

The dependency list is small and mostly standard. CMake 2.8 or later is required, together with one compression library: libdeflate, zlib-ng or ZLib. Everything else is optional and detected at configure time. Lua 5.4, Valgrind, Brotli, ZSTD and mbedTLS are linked only if present, and Brotli and ZSTD can be forced off with -DENABLE_BROTLI=NO and -DENABLE_ZSTD=NO. On Linux, TLS support via mbedTLS is on by default through -DENABLE_TLS=ON.

The allocator is a configure-time choice rather than a runtime one. Passing -DUSE_ALTERNATIVE_MALLOC to CMake accepts mimalloc, jemalloc, tcmalloc, or auto, which autodetects among them and falls back to libc malloc when none is found. That is a build-time decision baked into the binary, so switching allocators means rebuilding.

The container image shows the smallest supported shape of a deployment. The Dockerfile builds on Alpine 3.14.2, installs the compiler and library set, runs CMake with -DCMAKE_BUILD_TYPE=Release and -DMTUNE_NATIVE=OFF, then copies only the lwan binary and lwan.conf into a second Alpine stage. It exposes port 8080, declares /wwwroot as a volume, and uses /lwan as the entry point. Two details matter: MTUNE_NATIVE is disabled so the image is portable across hosts, and the runtime stage installs luajit, sqlite, zlib, brotli and zstd-dev, which tells you those libraries are needed at run time and not only when compiling.

## Building Lwan and serving a first request

The README gives a clone, a build directory, a build type and make. The clone command uses the git protocol, which some networks block; the https form of the same URL is the usual workaround, but the README does not mention it.

```bash
git clone git://github.com/lpereira/lwan
cd lwan
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make
```

The result is a set of binaries under the build tree. The main one is src/bin/lwan/lwan, and the README says it can be run with --help for guidance. That flag is the only usage information the README offers, so the first real step after a successful build is to run it and read the output.

```bash
./src/bin/lwan/lwan --help
```

If you would rather not build at all, the container route avoids the toolchain entirely. The published image exposes 8080 and reads /wwwroot as a volume, so the server serves whatever you mount there.

```bash
docker build -t lwan .
docker run --rm -p 8080:8080 -v "$PWD/wwwroot:/wwwroot" lwan
```

After that, a request to http://localhost:8080/ should be answered by files from the mounted wwwroot directory. The README does not document the expected response body, so treat the first successful HTTP response as the confirmation that the build and the mount are correct.

## Where Lwan gets in your way

The most concrete limitation is documentation depth. The README covers dependencies, package names per distribution, build commands and the list of produced binaries. It does not document configuration file syntax, does not explain how to add a handler, and does not describe TLS certificate or key handling even though mbedTLS is an optional dependency and TLS is enabled by default on Linux. For a server whose selling point is embedding, the absence of an application-facing guide is the gap that will cost the most time.

Operating it in production has a similar shape. There is no documented rollback procedure, no described upgrade path between the v0.5, v0.6 and v0.7 releases, and no stated support window. The release list shows v0.7 in June 2025 and v0.6 in September 2024, so releases are infrequent; the README does not promise a cadence.

The debug build is a trap worth naming. The README warns in a callout that the default build type is Debug, that it logs all requests to standard output, and that it does so while holding a lock. A server left on the default build type will behave very differently from the release build under load, and the README's instruction to use Release when benchmarking exists precisely because of that. Anyone who builds with plain cmake .. and then wonders why throughput looks wrong has hit a documented, avoidable problem.

Finally, platform support is narrower than the dependency list suggests. CI covers Linux x86_64, FreeBSD 15 x86_64 and OpenBSD 7.9 x86_64, with static analysis and tests on Linux. Nothing in the README indicates Windows support.

## Lwan against a general-purpose web server

The natural comparison is with a mature, configuration-driven server such as nginx. The difference is architectural intent rather than feature count. nginx is a standalone daemon you configure with files, extend with modules compiled against a stable interface, and operate through a documented set of directives and signals. Lwan's README instead leads with building the server yourself and lists sample binaries that embed it, including a FreeGeoIP implementation and a TechEmpower benchmark entry. The unit of adoption is a C program that links the server, not a config file you edit.

That has consequences. With nginx you get a separate process, a reload signal and a documented module ABI. With Lwan the HTTP layer is part of your binary, so the build flags you choose for the server (allocator, TLS, Brotli, ZSTD) are the flags of your application. The trade is control and footprint against operational familiarity. If your team already runs nginx and has runbooks for it, Lwan replaces that knowledge with C build tooling.

The bundled weighttp is a second, smaller point of contrast. Rather than depending on an external benchmarking client, the project builds a rewrite of weighttp alongside the server. That keeps the measurement loop inside the repository, which is convenient for contributors and irrelevant for operators.

## Maintenance, licence and upgrade cost

The repository is not archived, and the last push was on 2026-09-22. Releases are tagged v0.5 (2022-05-28), v0.6 (2024-09-21) and v0.7 (2025-06-17), with the v0.5 tag described as the fifth experimental release. The gap between v0.5 and v0.6 is roughly two years and four months, and between v0.6 and v0.7 roughly nine months. Anyone planning upgrades should assume they will be doing them by hand from the source tree, because the README does not describe a package upgrade path and the release notes are not reproduced in the README.

Upgrade cost is also a function of the build flags. Because the allocator, TLS, Brotli and ZSTD choices are all configure-time, an upgrade means re-running CMake with the same arguments and confirming that the optional libraries are still detected. The Dockerfile records one working combination: Alpine 3.14.2, Release build, MTUNE_NATIVE off, luajit, sqlite, zlib, brotli and zstd-dev at run time.

The licence is GPL-2.0. That is a copyleft licence, and the practical question for anyone embedding Lwan in a larger program is how the combined work would be distributed. This article cannot give legal advice; the point is that GPL-2.0 is a materially different starting position from a permissive licence, and it should be reviewed before Lwan is linked into a product rather than after.

## Conclusion

Adopt Lwan if you are writing a C service that needs an embedded HTTP layer, or if you want to read and modify a small server's request path. Do not adopt it if you need a documented production operations story, because the README covers building and samples but not deployment, TLS key management or rollback. Before committing, verify three things yourself: that your platform is one of the CI targets listed (Linux x86_64, FreeBSD 15, OpenBSD 7.9), that the optional TLS path with mbedTLS builds on your toolchain, and that the GPL-2.0 terms fit how you intend to link and distribute the result.

## FAQ

### How do I build Lwan from source?

Clone the repository, create a build directory, run cmake with -DCMAKE_BUILD_TYPE=Release, then make. The README lists CMake 2.8 or later and libdeflate, zlib-ng or ZLib as the only required dependencies.

### Does Lwan need Lua, Brotli or ZSTD to build?

No. Lua 5.4, Brotli, ZSTD, Valgrind and mbedTLS are optional and enabled only if the build system finds them. Brotli and ZSTD can be turned off explicitly with -DENABLE_BROTLI=NO and -DENABLE_ZSTD=NO.

### What port does the Lwan container image use?

The Dockerfile exposes port 8080, declares /wwwroot as a volume, and runs /lwan as the entry point. The image is built in two Alpine 3.14.2 stages and runs cmake with -DCMAKE_BUILD_TYPE=Release and -DMTUNE_NATIVE=OFF.

### Which platforms does Lwan support?

The README's build status table covers Linux x86_64, FreeBSD 15 x86_64 and OpenBSD 7.9 x86_64. Static analysis and the test suite run on Linux, and no Windows target appears in the README.

## Sources

- [License: GPL-2.0](https://github.com/lpereira/lwan/blob/master/LICENSE)
- [lpereira/lwan on GitHub](https://github.com/lpereira/lwan)
- [Project website](https://lwan.ws)
- [README](https://github.com/lpereira/lwan/blob/master/README.md)
- [Releases](https://github.com/lpereira/lwan/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/lpereira-lwan
