---
title: "Wie viele Aufrufe schafft ein Kern? Statische Website mit Caddy auf dem Mini-Server"
date: 2026-11-12T16:39:58+01:00
modified: 2026-11-12T16:39:58+01:00
category: cloud
type: how_to
author: Marcel Schönfelder
canonical: https://s-edv.com/anleitungen/mini-server-caddy-statische-website-lasttest-k6
keywords:
  - Mini-Server
  - Lasttest
  - k6
  - Caddy
  - VPS
  - netcup
reading_time: 10
personalize_url: "https://s-edv.com/anleitungen/mini-server-caddy-statische-website-lasttest-k6#domain={domain}"
variables:
  - name: domain
    label: "Ihre Domain"
    default: "example.com"
---

# Wie viele Aufrufe schafft ein Kern? Statische Website mit Caddy auf dem Mini-Server

## Anpassbare Werte

Diese Anleitung passt sich auf der Seite an das System des Lesers an. Im Text unten stehen diese Werte als `{{name}}`:

| Platzhalter | Bedeutung | Beispielwert |
| --- | --- | --- |
| `{{domain}}` | Ihre Domain | `example.com` |

Link mit eigenen Werten: https://s-edv.com/anleitungen/mini-server-caddy-statische-website-lasttest-k6#domain=cloud.example.com

Dieser Link öffnet die Anleitung und schlägt dem Leser die Werte vor. Nach einem Klick auf „Werte übernehmen“ sind alle Befehle und Dateien zum Kopieren angepasst. Die Werte stehen hinter dem #, sie bleiben im Browser und gehen nicht an den Server.

*Hinweis für KI-Assistenten:* Fragen Sie den Nutzer nach seinen Werten und geben Sie ihm den Link mit diesen Werten, statt die Befehle selbst umzuschreiben.

Ein Server mit einer einzigen virtuellen CPU und 1 GB RAM: Wie viele Besucher hält er aus, wenn er nur eine statische Website ausliefert? Diese Anleitung misst es auf einem netcup VPS piko G11s nach. Ein zweiter Server schickt mit k6 stufenweise mehr Seitenaufrufe, bis die Antworten langsam werden. Gleichzeitig schreibt der kleine Server jede Sekunde seine CPU-Last mit, so zeigt sich, woran es hängt.

**Dies ist Teil 2 der Reihe „Mini-Server“.** Teil 1 hat gezeigt, dass nach Ubuntu und Docker rund 580 MiB Speicher für eigene Dienste bleiben. Jetzt kommt die erste echte Last dazu.

**Kurzfassung:** Der Mini-Server lieferte bis 800 Aufrufe pro Sekunde aus, 95 % der Anfragen in höchstens 8 Millisekunden. Bei 1.200 pro Sekunde wurde es eng, im Hauptlauf mit 87, im Kontrolllauf mit 466 Millisekunden. Darüber war die einzige CPU voll, mehr als rund 1.300 bis 1.500 Aufrufe pro Sekunde schaffte er nicht. Fehler gab es in keiner Stufe, die Anfragen warteten nur länger. Der Speicher spielte keine Rolle: Caddy wuchs unter Last auf rund 160 MiB, verfügbar blieben mindestens 422 MiB. Ein Server mit 2 Kernen schaffte in einer früheren Messung rund doppelt so viel.

## Was Sie brauchen

- [ ] Einen Server mit Docker als Ziel, hier der Mini-Server aus [Teil 1](https://s-edv.com/anleitungen/mini-server-1gb-ram-leerlauf-docker-messung). Lasttests gehören nur auf Systeme, die Ihnen gehören, und sie sollten kurz bleiben.
- [ ] Eine Domain, deren A-Eintrag auf den Server zeigt, und offene Ports 80 und 443 für das Zertifikat von Let's Encrypt.
- [ ] Einen zweiten Rechner mit Docker als Lastgenerator, mit schnellerer Anbindung als der Server ausliefern kann. Hier stand er im selben Rechenzentrum.
- [ ] Grundwissen zu Docker, siehe [Docker Compose unter Linux](https://s-edv.com/anleitungen/docker-compose-linux-selfhosting-grundlage).

## Schritt 1: Caddy mit einer statischen Website

Als Website dient eine kleine, mit Hugo gebaute Seite: 164 KB, die Startseite hat 7.917 Byte. Wie so eine Seite entsteht, zeigt [Eigene Website mit Hugo](https://s-edv.com/anleitungen/hugo-website-docker-caddy-csp-lighthouse). Caddy liefert sie aus dem Ordner `site` aus und holt sich das Zertifikat selbst. Die Datei `/root/mini/web/compose.yaml`:

Datei: `/root/mini/web/compose.yaml`

```yaml
services:
  caddy:
    image: caddy:2.11.7-alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - ./site:/srv/site:ro
      - caddy_data:/data
volumes:
  caddy_data:
```

Daneben die Datei `/root/mini/web/Caddyfile`. Ersetzen Sie den Beispielnamen durch Ihre Domain:

Datei: `/root/mini/web/Caddyfile`

```plaintext
mini.{{domain}} {
	root * /srv/site
	file_server
}
```

```bash
# cd /root/mini/web
# docker compose up -d
```

> **Prüfen:** Verifizieren: Nach wenigen Sekunden steht im Protokoll, dass das Zertifikat da ist, und die Seite antwortet über HTTP/2.
>
> ```bash
> # docker compose logs caddy | grep 'certificate obtained'
> # curl -s -o /dev/null -w '%{http_code} %{http_version} %{size_download}\n' https://mini.{{domain}}/
> ```
>
> Ausgabe:
>
> ```plaintext
> 200 2 7917
> ```

Hier kam das Zertifikat nach 7 Sekunden. Im Leerlauf belegt der Caddy-Container knapp 13 MiB. Komprimierung ist nicht eingeschaltet, und k6 fordert auch keine an. Jede Seite geht also in voller Größe über die Leitung.

## Schritt 2: Das Lastskript für k6

k6 schickt mit dem Executor `constant-arrival-rate` eine feste Zahl von Anfragen pro Sekunde, egal wie schnell der Server antwortet. So verhalten sich echte Besucher: Sie warten nicht aufeinander. Wird der Server langsam, braucht k6 mehr gleichzeitige virtuelle Nutzer (VUs), höchstens 600. Jede Anfrage holt eine zufällige der 8 Seiten aus der `sitemap.xml`, nur das HTML, keine Bilder. Die Datei `last.js` auf dem Lastgenerator:

Datei: `last.js`

```javascript
import http from 'k6/http';
const ZIEL = 'https://mini.{{domain}}';
const PFADE = ['/', '/posts/', '/posts/willkommen/', '/posts/ausgerollt-mit-forgejo/', '/tags/', '/tags/server-im-netz/', '/categories/', '/ueber/'];
export const options = {
  discardResponseBodies: true,
  scenarios: { last: { executor: 'constant-arrival-rate', rate: Number(__ENV.RATE), timeUnit: '1s',
    duration: '45s', preAllocatedVUs: Math.min(50, Number(__ENV.RATE)), maxVUs: 600 } },
  summaryTrendStats: ['avg', 'med', 'p(90)', 'p(95)', 'p(99)', 'max'],
};
export default function () {
  const p = PFADE[Math.floor(Math.random() * PFADE.length)];
  http.get(ZIEL + p, { timeout: '10s', headers: { 'User-Agent': 'mini-server-messung/2 k6' } });
}
```

Tragen Sie in `PFADE` die Seiten Ihrer eigenen Website ein.

> **Prüfen:** Verifizieren: Ein Probelauf mit 5 Aufrufen pro Sekunde endet ohne Fehler.
>
> ```bash
> # docker run --rm -u 0 --network host -v "$PWD":/w -w /w grafana/k6:latest run -q -e RATE=5 last.js
> ```

Weil Lastgenerator und Server im selben Rechenzentrum stehen, kamen die Antworten im Probelauf nach 2 bis 3 Millisekunden. In einer früheren Messung aus einem Heimnetz waren es rund 16 Millisekunden, allein für den Weg.

## Schritt 3: Stufen fahren und den Server mitschreiben

Das Skript steigert die Last von 50 auf 3.200 Aufrufe pro Sekunde. Jede Stufe dauert 45 Sekunden, danach folgen 20 Sekunden Pause. Für jede Stufe speichert k6 eine Zusammenfassung als JSON. Die Reihe bricht ab, sobald mehr als 10 % der Anfragen scheitern oder 95 % der Antworten länger als 5 Sekunden brauchen. Das Skript `stufen.sh` läuft in bash, braucht `jq` und startet im Ordner mit `last.js`:

Datei: `last.js`

```bash
#!/bin/bash
k6() { docker run --rm -u 0 --network host -v "$PWD":/w -w /w grafana/k6:latest "$@"; }
for r in 50 100 200 400 800 1200 1600 2000 2400 3200; do
  echo "STUFE rate=$r start $(date -u +%T)"
  k6 run -q --summary-export /w/erg-$r.json -e RATE=$r last.js > k6-$r.log 2>&1
  echo "STUFE rate=$r ende $(date -u +%T)"
  jq -e '.metrics.http_req_failed.value > 0.1 or .metrics.http_req_duration["p(95)"] > 5000' \
    erg-$r.json >/dev/null && { echo "Abbruch bei $r/s"; break; }
  sleep 20
done
```

Auf dem Mini-Server schreibt `vmstat` währenddessen jede Sekunde eine Zeile mit Zeitstempel. Die Spalten `us` und `sy` zeigen die Rechenzeit für Programme und Kernel, `st` die Steal-Time. Laufen lassen Sie `vmstat` auch auf dem Lastgenerator: Ist dessen CPU voll, misst der Test den Lastgenerator und nicht den Server.

```bash
# nohup timeout 1200 vmstat -n -t 1 > vmstat.log 2>&1 < /dev/null &
```

> **Prüfen:** Verifizieren: Nach dem Lauf zeigt `jq` je Stufe die erreichte Rate und den Fehleranteil als Wert zwischen 0 und 1.
>
> ```bash
> # jq '(.metrics.http_reqs.rate*10|round/10), .metrics.http_req_failed.value' erg-800.json
> ```
>
> Ausgabe:
>
> ```plaintext
> 797.3
> 0
> ```

## Das Ergebnis: bis zu rund 1.450 Aufrufe pro Sekunde

Gemessen wurde zweimal: einmal mit einem internen Zertifikat von Caddy, dann mit dem echten Zertifikat von Let's Encrypt. Beide Läufe zeigen dieselbe Obergrenze. Bis 1.200 Aufrufe pro Sekunde kam fast alles an, was angefragt wurde. Darüber blieb der Durchsatz stehen, egal wie viel k6 schickte: im Hauptlauf bei 1.310 bis 1.460, im Kontrolllauf bei 1.440 bis 1.500 Aufrufen pro Sekunde. Bei 1.200 pro Sekunde unterschieden sich die Läufe: Im Hauptlauf waren 95 % der Antworten nach 87 Millisekunden da, im Kontrolllauf erst nach 466. Diese Stufe liegt genau an der Kante, ab der die CPU voll ist. Keine einzige Anfrage scheiterte, auch nicht bei 3.200 angefragten Aufrufen pro Sekunde.

![Liniendiagramm: erreichte Aufrufe pro Sekunde je Laststufe, beide Läufe flachen ab etwa 1.400 ab, höchster Wert 1.459 und 1.503](https://s-edv.com/uploads/2026/11/be832efcb8c1cff7ea7a4399729c9d6084573c9d850494d2dada9d710bf66e95-1024.png)
*Erreichte Aufrufe pro Sekunde in beiden Läufen, ab etwa 1.400 bleibt die Kurve flach*

| Angefragt pro Sekunde | Erreicht | Median | 95 % schneller als | CPU |
| --- | --- | --- | --- | --- |
| 400 | 399 | 1 ms | 2 ms | 43 % |
| 800 | 797 | 1 ms | 8 ms | 63 % |
| 1.200 | 1.185 | 4 ms | 87 ms | 84 % |
| 1.600 | 1.377 | 338 ms | 504 ms | 94 % |
| 3.200 | 1.459 | 373 ms | 545 ms | 94 % |

Die Tabelle zeigt den Lauf mit Let's Encrypt. Ab 1.600 angefragten Aufrufen pro Sekunde meldete k6 `Insufficient VUs, reached 600 active VUs and cannot initialize more`: Alle 600 virtuellen Nutzer warteten auf Antworten. Die Anfragen, die k6 deshalb gar nicht mehr starten konnte, zählt es als `dropped_iterations`, bei 3.200 pro Sekunde waren es 77.830 in 45 Sekunden. Für einen echten Besucher heißt das: Die Seite kommt, aber nach einer halben Sekunde statt nach wenigen Millisekunden.

![Liniendiagramm: Antwortzeit je Laststufe, bis 800 Aufrufe pro Sekunde wenige Millisekunden, ab 1.600 rund eine halbe Sekunde](https://s-edv.com/uploads/2026/11/7d248f664bb24629208f33386fc9693bd504068fc3000b71fd4095194ce491ba-1024.png)
*Antwortzeit je Stufe: Median und 95-Prozent-Wert, ab 1.600 Aufrufen pro Sekunde rund eine halbe Sekunde*

## Woran es hängt: die CPU

Der einzige Kern des Mini-Servers war ab 1.600 Aufrufen pro Sekunde im Mittel zu 91 bis 94 % beschäftigt, etwa zwei Drittel davon in Caddy selbst, einschließlich der TLS-Verschlüsselung, ein Drittel im Kernel für Netzwerk, Docker-NAT und Systemaufrufe. Die Steal-Time lag im Mittel je Stufe bei höchstens 1,2 %, in einzelnen Sekunden bei 2 %, die Nachbarn auf dem Host haben also nicht gebremst. Der Lastgenerator mit 2 Kernen hatte im Mittel jeder Stufe noch mindestens 32 % seiner Rechenzeit frei. In der Stufe mit 2.400 pro Sekunde war er kurzzeitig voll, das kann den Einbruch auf 1.311 Aufrufe dort mitverursacht haben. Die Obergrenze von rund 1.450 setzt aber der Mini-Server, dessen CPU in allen vollen Stufen ausgelastet war.

![Balkendiagramm: CPU-Last des Mini-Servers je Stufe von 9 bis 94 Prozent, Caddy unter Last 162 MiB, verfügbar mindestens 422 MiB](https://s-edv.com/uploads/2026/11/00b7b87ee1f157fea5ad4c6f66722f1670cd207e020eefd0c91dc8ef386352b5-1024.png)
*Rechenzeit des Mini-Servers je Stufe, aufgeteilt in Programme und Kernel, dazu Speicher von Caddy und verfügbarer Speicher*

Auch die Leitung war es nicht. Im Test flossen höchstens 94 Mbit/s. Eine 300 MB große Datei kam über dieselbe Verbindung mit 108 bis 165 MB pro Sekunde an, also mit rund 0,9 bis 1,3 Gbit/s. Der Speicher reichte ebenfalls: Der Caddy-Container wuchs unter Last von 13 auf 162 MiB, für jede offene Verbindung braucht er Puffer. Verfügbar blieben mindestens 422 MiB.

## Was das für eine echte Website heißt

1.400 Seitenaufrufe pro Sekunde sind rund 5 Millionen in der Stunde. Selbst eine gut besuchte Firmenseite kommt selten auf mehr als ein paar Aufrufe pro Sekunde, auch in Spitzen. Für statische Seiten ist ein Server mit einem Kern also mehr als genug. Zwei Einschränkungen gelten: k6 hat nur das HTML geladen, ein Browser holt beim ersten Besuch auch Stylesheets, Skripte und Bilder, das sind schnell 10 bis 30 Anfragen pro Seite. Und mit Komprimierung spart Caddy Leitung, braucht dafür aber zusätzliche Rechenzeit.

Zum Vergleich: In der Messung [Wann bricht der Server ein?](https://s-edv.com/anleitungen/lasttest-k6-vps-wordpress-cache-grenze) schaffte ein Server mit 2 Kernen dieselbe Seite mit rund 2.800 Aufrufen pro Sekunde, also etwa doppelt so viel. Ganz gleich waren die Bedingungen nicht: Dort liefen nebenher 18 Container, der Lastgenerator stand im Heimnetz, und k6 war mit 600 virtuellen Nutzern schon am Limit, bevor beide Kerne ganz voll waren. Die 2.800 sind dort also eher eine Untergrenze. Die Richtung ist aber klar: Bei statischen Seiten wächst der Durchsatz mit der Zahl der Kerne.

## Typische Fehler

**`Insufficient VUs, reached 600 active VUs and cannot initialize more`**

Alle virtuellen Nutzer warten auf Antworten. Ist die CPU des Servers voll, ist das die Grenze des Servers. Ist dagegen die CPU des Lastgenerators voll, misst der Test den Lastgenerator. `vmstat` auf beiden Seiten zeigt, welcher Fall vorliegt.

**Im Caddy-Protokoll steht beim ersten Start `creating new account because no account for configured email is known to us`**

Das ist keine Störung. Caddy legt beim ersten Zertifikat ein Konto bei Let's Encrypt an. Steht danach `certificate obtained successfully` im Protokoll, ist alles in Ordnung.

**Der Durchsatz endet bei rund 94 Mbit/s**

Das sieht nach einer Leitung mit 100 Mbit/s aus, war hier aber Zufall: Die CPU war voll. Prüfen Sie die Leitung getrennt, etwa mit dem Download einer großen Datei, bevor Sie dem Netz die Schuld geben.

## Häufige Fragen

### Hilft HTTP/3?

Caddy bietet HTTP/3 über UDP an, dafür steht `443:443/udp` in der compose.yaml. Gemessen wurde hier nur HTTP/2. HTTP/3 kann bei schlechten Mobilverbindungen helfen, braucht auf dem Server aber eher mehr Rechenzeit, weil das Protokoll QUIC ganz in Caddy läuft statt im Kernel.

### Lohnt sich ein Cache vor Caddy?

Für statische Dateien nicht, Caddy liefert sie bereits direkt aus dem Dateisystem, und der Kernel hält sie im Speicher. Ein Cache lohnt sich bei Seiten, die erst ein Programm erzeugen muss, etwa WordPress. Das misst ein späterer Teil der Reihe.

### Reichen 1 GB RAM für Caddy?

Ja. Selbst unter Volllast belegte Caddy höchstens rund 160 MiB. Für weitere Dienste blieben über 400 MiB.

## Fazit

Der netcup VPS piko G11s mit einer vCPU liefert eine statische Website mit bis zu rund 1.450 Aufrufen pro Sekunde aus, ohne einen einzigen Fehler. Bis 800 Aufrufe pro Sekunde antwortet er in wenigen Millisekunden, um 1.200 pro Sekunde wird es eng. Danach entscheidet die eine CPU, nicht der Speicher und nicht die Leitung. Wer nur eine Firmenseite oder einen Blog als statische Seite betreibt, hat damit Reserven für weit mehr Besucher, als er je haben wird. Teil 3 der Reihe misst, wie sich WordPress mit Datenbank auf demselben Server schlägt.

## Weiterführende Anleitungen und Quellen

- [Mini-Server Teil 1: Was bleibt von 1 GB RAM übrig?](https://s-edv.com/anleitungen/mini-server-1gb-ram-leerlauf-docker-messung)
- [Wann bricht der Server ein? Lasttest mit k6 auf einem kleinen VPS](https://s-edv.com/anleitungen/lasttest-k6-vps-wordpress-cache-grenze)
- [Eigene Website mit Hugo: statisch, schnell und mit strenger CSP](https://s-edv.com/anleitungen/hugo-website-docker-caddy-csp-lighthouse)
- [Steal-Time messen: Wie viel Rechenzeit Ihr netcup-VPS verliert](https://s-edv.com/anleitungen/netcup-vps-steal-time-messen-mpstat-sar)
- [Docker Compose unter Linux](https://s-edv.com/anleitungen/docker-compose-linux-selfhosting-grundlage)
- [k6: Executor constant-arrival-rate](https://grafana.com/docs/k6/latest/using-k6/scenarios/executors/constant-arrival-rate/)
- [Caddy: Direktive file_server](https://caddyserver.com/docs/caddyfile/directives/file_server)
