Comparing betas of Static Web Server and Ferron

Published on: Author: Dorian Niemiec

I was thinking about a baseline for comparing the web server I was testing to. So I visited Static Web Server’s website and noticed the updated website design, and focus on a new v3 beta version.

This interested me, so in this blog post, I’m going to compare two web servers:

  • Static Web Server (SWS) v3.0.0-beta.1
  • Ferron 3.0.0-beta.11 (wait… is this post in the Ferron blog?)

What are they?

Static Web Server v3 is according to its documentation “a fast and efficient, open-source web server for serving static files and web assets”. SWS is designed specifically for fast static file serving and is very lightweight (~4MB binary).

Ferron 3 in the other hand is according to its documentation “a configurable web server for static sites, reverse proxying, TLS, and more”, and it’s designed for various use cases, including reverse proxying too.

For this blog post though, I’m going to focus on static file serving features of both servers.

Configuration

Static Web Server v3 can be configured either via command-line arguments, environment variables, or a TOML configuration file that looks like this:

[general]
host = "::"
port = 80
root = "./public"

Ferron 3 in the other hand can be configured via a configuration file that looks like this:

*:80 {
  root "./public"
}

They are both easy to configure (Ferron’s configuration format, and Static Web Server’s -d wwwroot CLI arguments). Though Ferron 3 supports automatic TLS certificate management, while Static Web Server v3 doesn’t seem to support it. Though certbot can be used (just like with any web server that supports HTTPS), and it can make sense given SWS’s lightweight footprint.

Observability

From https://ferron.sh/blog/web-infrastructure-observability:

Yes, even static file serving benefits from observability, although much of it would be delegated to CDNs…

With observability, operators know the 404 error rate was increasing after some time, and this can be used to determine this is due to a failed deployment that ended up breaking the frontend. Also, they would know what path caused the 404 errors.

When it comes to observability, both Ferron and SWS offers options for observing the web server.

SWS v3 supports basic RED metrics via Prometheus (such as sws_http_requests_total or sws_http_response_bytes_total), which can be enabled like this:

[general]
metrics = true

The metrics can then be used with Grafana (SWS even provides an example dashboard) to monitor the infrastructure and set up alerting rules for monitoring.

SWS can be also configured to emit structured newline-delimited JSON logs to either stderr or a log file, which then can be used with various log shippers (like Fluent Bit, Vector or Filebeat).

Ferron 3 in the other hand offers full OpenTelemetry, Prometheus and log files for observability (it’s literally designed for less debugging friction). For example, OpenTelemetry can be enabled in Ferron 3 like this:

example.com {
    observability {
        provider otlp

        logs https://collector:4318/v1/logs
        metrics https://collector:4318/v1/metrics
        traces https://collector:4317/v1/traces

        service_name "my-service"
    }
}

Even with CLF-style log files, they have trace IDs by default for improved correlation. Ferron 3 supports metrics, logs, traces, and correlation between them (trace IDs, even exemplars).

I would say Ferron 3 offers far more comprehensive observability than SWS v3, although for basic static file serving, log files and maybe Prometheus metrics can be good enough (which SWS supports too).

Performance

Now comes the exciting part.

For the performance testing, I used a laptop with Intel Core Ultra 7 258V for both client and server, which ran Linux 7.1 (Omarchy, based on Arch Linux). I configured both servers to use HTTP/2 (if applicable), HTTPS, custom webroot and custom port. Everything else was left default.

Below are the results:

❯ # Ferron
❯ wrk -c 1000 -d 10 -t 8 https://localhost:8443 # ~3.4KB static file, HTTPS + HTTP/1.1
Running 10s test @ https://localhost:8443
  8 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    43.40ms  193.61ms   2.00s    95.15%
    Req/Sec    38.47k    15.08k   89.02k    69.44%
  3048502 requests in 10.09s, 10.65GB read
  Socket errors: connect 0, read 0, write 0, timeout 44
Requests/sec: 301998.52
Transfer/sec:      1.05GB
❯ h2load --duration 10s -c 250 -t 8 -m 8 https://localhost:8443/ # ~3.4KB static file, HTTPS + HTTP/2
[skipped for brevity]

finished in 10.03s, 480886.40 req/s, 1.53GB/s
requests: 4808864 total, 4810864 started, 4808864 done, 4808864 succeeded, 0 failed, 0 errored, 0 timeout
status codes: 4808864 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 15.33GB (16456046435) total, 45.96MB (48191967) headers (space savings 96.78%), 15.20GB (16321284416) data
                 min         max         median     p95        p99        mean         sd        +/- sd
request     :       80us    652.75ms      3.44ms     9.29ms    12.24ms     4.10ms      7.17ms    98.45%
connect     :    12.89ms    654.09ms    102.82ms   435.68ms   607.19ms   144.29ms    130.75ms    85.60%
TTFB        :    71.20ms    701.45ms    259.13ms   692.94ms   701.40ms   308.41ms    207.50ms    63.60%
req/s       :     815.01     4866.68     1679.05    4423.14    4752.07    1923.31     1000.25    63.60%
❯ wrk -c 1000 -d 10 -t 8 http://localhost:8080 # ~3.4KB static file, HTTP/1.1
Running 10s test @ http://localhost:8080
  8 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     2.82ms    2.51ms  26.43ms   68.52%
    Req/Sec    48.92k     8.13k   79.14k    65.25%
  3910772 requests in 10.09s, 13.53GB read
Requests/sec: 387542.58
Transfer/sec:      1.34GB
❯ wrk -c 1000 -d 10 -t 8 http://localhost:8080/favicon.ico # ~111KB static file, HTTP/1.1
Running 10s test @ http://localhost:8080/favicon.ico
  8 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     6.07ms    6.10ms  46.60ms   85.38%
    Req/Sec    24.47k     5.87k   45.58k    69.80%
  1955354 requests in 10.10s, 203.60GB read
Requests/sec: 193626.26
Transfer/sec:     20.16GB
❯ # SWS
❯ wrk -c 1000 -d 10 -t 8 https://localhost:8443 # ~3.4KB static file, HTTPS + HTTP/1.1
Running 10s test @ https://localhost:8443
  8 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     6.41ms   29.06ms 566.11ms   98.02%
    Req/Sec    37.04k     9.94k   50.96k    85.86%
  2825186 requests in 10.08s, 10.23GB read
Requests/sec: 280219.57
Transfer/sec:      1.01GB
❯ h2load --duration 10s -c 250 -t 8 -m 8 https://localhost:8443/ # ~3.4KB static file, HTTPS + HTTP/2
[skipped for brevity]

finished in 10.03s, 263606.00 req/s, 869.84MB/s
requests: 2636060 total, 2638060 started, 2636060 done, 2636060 succeeded, 0 failed, 0 errored, 0 timeout
status codes: 2636060 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 8.49GB (9120895309) total, 98.15MB (102920299) headers (space savings 90.98%), 8.33GB (8946787640) data
                 min         max         median     p95        p99        mean         sd        +/- sd
request     :       21us    132.19ms      6.24ms    16.19ms    20.99ms     7.10ms      4.90ms    68.99%
connect     :     4.40ms       1.18s     87.27ms      1.13s      1.17s   342.11ms    460.81ms    73.20%
TTFB        :    15.24ms       1.19s     96.55ms      1.14s      1.17s   351.15ms    458.38ms    73.20%
req/s       :     926.23     1141.65     1082.77    1113.14    1127.57    1054.16       60.98    69.20%
❯ wrk -c 1000 -d 10 -t 8 http://localhost:8080 # ~3.4KB static file, HTTP/1.1
Running 10s test @ http://localhost:8080
  8 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     2.45ms    6.47ms 212.78ms   99.66%
    Req/Sec    53.65k     6.03k   77.52k    82.12%
  4277226 requests in 10.04s, 15.49GB read
Requests/sec: 425884.86
Transfer/sec:      1.54GB
❯ wrk -c 1000 -d 10 -t 8 http://localhost:8080/favicon.ico # ~111KB static file, HTTP/1.1
Running 10s test @ http://localhost:8080/favicon.ico
  8 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     8.96ms   16.18ms 442.51ms   98.95%
    Req/Sec    14.66k     1.37k   20.91k    72.47%
  1173433 requests in 10.10s, 122.37GB read
Requests/sec: 116200.25
Transfer/sec:     12.12GB

Both Ferron 3 and Static Web Server v3 show good performance, although in my scenarios, Ferron 3 had higher throughput (although generally higher latency too) than Static Web Server (well, except plaintext HTTP scenario for small file, while SWS had higher throughput, due to sendfile in Ferron 3 not having that much advantage here).

Ferron 3 went through lots of optimizations and is also using sendfile optimization for plaintext HTTP (via zincio + zincio-http), making it great for high-performance needs. SWS in the other hand doesn’t use sendfile (it uses regular tokio + hyper stack), but it uses “direct” file I/O and in-memory caching for performance optimization. Still, SWS has good performance too.

Community

Both SWS and Ferron have small yet existing communities. static-web-server/static-web-server repository on GitHub has ~2.3K stars, while ferronweb/ferron one has ~2.1K stars.

But joseluisq/static-web-server Docker Hub image has far more downloads (5M+) than ferronserver/ferron image (10K+).

Also, SWS repository on GitHub seems to be created in 2019, while Ferron one was created in 2025. This might mean SWS has accumulated battle-testing for longer than Ferron. Although lack of battle-testing is an universal problem affecting new software projects…

Conclusion

Both SWS and Ferron 3 are suitable for static file serving, with both of them supporting core static file serving features, as well as metrics and logs.

Ferron 3 has more comprehensive observability and higher static file serving throughput. Choose Ferron 3 if you value debuggability (debugging is what many sysadmins struggle with) and high performance.

SWS in the other hand is more established and has smaller footprint (~4MB binary, versus 20MB+ binary of Ferron 3). That smaller footprint would help a lot when serving static files from within a Docker container (lower memory usage, lower disk space usage, and so on). Choose SWS v3 if you’re building Docker image for serving static files (for example in microservices architecture, although many would use CDNs anyway) and value small footprint, or if you value using established software a lot.