Zum Hauptinhalt springen
S-EDV news
← Alle Anleitungen
📘 Anleitung Cloud / Hosting 12.11.2026 · 10 min Lesezeit Geprüft 12.11.2026

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

Ein Server mit einer vCPU liefert eine statische Website mit Caddy bis zu rund 1.450-mal pro Sekunde aus, ohne Fehler. Bis 800 Aufrufe pro Sekunde antwortet er in wenigen Millisekunden, ab 1.200 wird es eng. Dann ist die CPU voll, Speicher und Leitung reichen. Mit Messaufbau und Skripten.

Werbung Mit * markierte Links sind Werbelinks: Bei einem Kauf erhalten wir eine Provision, der Preis bleibt gleich. Als Amazon-Partner verdiene ich an qualifizierten Verkäufen. Mehr dazu

Symbolbild: kleiner Server mit grünem Auslastungsbalken

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.

Was Sie brauchen

  • Einen Server mit Docker als Ziel, hier der Mini-Server aus Teil 1. 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.

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. Caddy liefert sie aus dem Ordner site aus und holt sich das Zertifikat selbst. Die Datei /root/mini/web/compose.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:

mini.example.com {
	root * /srv/site
	file_server
}
# cd /root/mini/web
# docker compose up -d

Verifizieren: Nach wenigen Sekunden steht im Protokoll, dass das Zertifikat da ist, und die Seite antwortet über HTTP/2.

# docker compose logs caddy | grep 'certificate obtained'
# curl -s -o /dev/null -w '%{http_code} %{http_version} %{size_download}\n' https://mini.example.com/
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:

import http from 'k6/http';
const ZIEL = 'https://mini.example.com';
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.

Verifizieren: Ein Probelauf mit 5 Aufrufen pro Sekunde endet ohne Fehler.

# 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:

#!/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.

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

Verifizieren: Nach dem Lauf zeigt jq je Stufe die erreichte Rate und den Fehleranteil als Wert zwischen 0 und 1.

# jq '(.metrics.http_reqs.rate*10|round/10), .metrics.http_req_failed.value' erg-800.json
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
Erreichte Aufrufe pro Sekunde in beiden Läufen, ab etwa 1.400 bleibt die Kurve flach
Angefragt pro SekundeErreichtMedian95 % schneller alsCPU
4003991 ms2 ms43 %
8007971 ms8 ms63 %
1.2001.1854 ms87 ms84 %
1.6001.377338 ms504 ms94 %
3.2001.459373 ms545 ms94 %

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
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
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? 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-ServerLasttestk6CaddyVPSnetcup