Всем привет, меня зовут Стас, я техлид в Mish Product Lab.
Тема возникла не просто так: внутри команды у нас было немало споров и дискуссий о том, какой инструмент для проксирования и терминации SSL лучше использовать в различных ситуациях. Изначально все наши гипотезы были основаны больше на личных предпочтениях, чем на реальных данных. Мы долго спорили, надеясь, что истина будет где-то рядом с нашими любимыми решениями. Но в итоге пришли к выводу, что единственный способ получить действительно объективный ответ — это протестировать и сравнить различные варианты на практике.
Именно так родилась идея провести сравнительный анализ производительности HAProxy, Envoy, Nginx, Caddy и Traefik с поддержкой SSL/TLS. Мы хотели понять, какой из инструментов «из коробки» предоставляет наилучшую производительность и минимальные накладные расходы, особенно при обработке SSL-трафика, который, как известно, требует дополнительных ресурсов из-за шифрования и дешифрования.
Важно подчеркнуть, что наша задача не заключалась в поиске идеального или максимально оптимизированного решения. Для глубоких и тонких настроек всегда лучше обратиться к DevOps-инженерам, которые обладают необходимыми компетенциями и понимают нюансы каждого инструмента. Нашей целью было скорее провести «полевые испытания» и составить заметки, которые могли бы помочь нам самим и будущим поколениям разработчиков быстрее принимать решения на основе хотя бы приблизительных ориентиров.
Итак, мы готовы поделиться нашими наблюдениями и результатами тестов.
Работаем с тестовыми стендами
Для обеспечения объективности и прозрачности тестирования мы выбрали три отдельных стенда, каждый из которых отвечал за свою часть сценария нагрузки. Разделение ролей стендов было принципиально важным для минимизации возможных взаимных влияний и искажений результатов. Это позволило максимально точно зафиксировать поведение каждого инструмента и компонента инфраструктуры.
Все тестовые стенды были размещены в одной сети, что гарантировало стабильную и предсказуемую сеть без значительных задержек. Никаких настроек ядра, голые системы.
Тестовый стенд 1 (K6) предназначался для генерации и управления трафиком при помощи инструмента K6. Его ресурсы (32 vCPU, 128 ГБ RAM) обеспечивали возможность генерировать существенную нагрузку и давала нам уверенность в том, что сам инструмент генерации не станет узким местом.
Тестовый стенд 2 (Web) был основным объектом тестирования. Здесь были установлены различные инструменты для проксирования (HAProxy, Envoy, Nginx, Caddy, Traefik) и мониторинг штатными средствами хостера. Это позволило нам не только протестировать производительность каждого решения, но и подробно отслеживать потребление ресурсов. Ресурсы 32 vCPU, 128 ГБ RAM.
Тестовый стенд 3 (Backend) выполнял роль типичного приложения (backend), написанного на Golang, которое должно было принимать и обрабатывать запросы, проходящие через прокси. Ресурсы 8 vCPU, 32 ГБ RAM.
Такое разделение ролей и ресурсов дало возможность получить максимально чистые и объективные данные о производительности каждого инструмента.
Версии веб серверов
haproxy 3.1.6
envoy 1.34.0-dev
nginx 1.27.4
traeffik 3.3.5
caddy 2.9.1
Работаем с бэкэндом
Сервис делали на Golang, для обработки запросов использовали fasthttp — никаких излишеств.
package main
import (
"fmt"
"log"
"github.com/valyala/fasthttp"
)
func main() {
fmt.Println("Listening on :8080")
requestHandler := func(ctx *fasthttp.RequestCtx) {
switch string(ctx.Path()) {
case "/":
handleRoot(ctx)
default:
ctx.Error("Unsupported path", fasthttp.StatusNotFound)
}
}
if err := fasthttp.ListenAndServe(":8080", requestHandler); err != nil {
log.Fatalf("Error in ListenAndServe: %s", err)
}
}
func handleRoot(ctx *fasthttp.RequestCtx) {
ctx.SetStatusCode(fasthttp.StatusOK)
ctx.SetBodyString("hello world")
}
Запускаем стресс-тест на этот сервис: 50 000 rps, 1 минута
K6 (генерация трафика и проверка ответа, http code и body):
import http from 'k6/http';
import { check } from 'k6';
export let options = {
scenarios: {
constant_request_rate: {
executor: 'constant-arrival-rate',
rate: 50000,
timeUnit: '1s',
duration: '60s',
preAllocatedVUs: 1000,
maxVUs: 100000,
},
},
};
export default function () {
let res = http.get('http://10.0.0.8:8080');
check(res, {
'status is 200': (r) => r.status === 200,
'body contains expected content': (r) => typeof r.body === 'string' && r.body.includes('hello world'),
});
}
scenarios: (100.00%) 1 scenario, 100000 max VUs, 1m30s max duration (incl. graceful stop):
* constant_request_rate: 50000.00 iterations/s for 1m0s (maxVUs: 1000-100000, gracefulStop: 30s)
█ THRESHOLDS
checks
✓ 'rate>=0.95' rate=100.00%
http_req_duration
✓ 'p(95)<500' p(95)=387.29µs
http_req_failed
✓ 'rate<0.01' rate=0.00%
█ TOTAL RESULTS
checks_total.......................: 5993052 99880.644605/s
checks_succeeded...................: 100.00% 5993052 out of 5993052
checks_failed......................: 0.00% 0 out of 5993052
✓ status is 200
✓ body contains expected content
HTTP
http_req_duration.......................................................: avg=283.17µs min=110.92µs med=234.52µs max=235.41ms p(90)=324.36µs p(95)=387.29µs
{ expected_response:true }............................................: avg=283.17µs min=110.92µs med=234.52µs max=235.41ms p(90)=324.36µs p(95)=387.29µs
http_req_failed.........................................................: 0.00% 0 out of 2996526
http_reqs...............................................................: 2996526 49940.322303/s
EXECUTION
dropped_iterations......................................................: 3476 57.931271/s
iteration_duration......................................................: avg=349.05µs min=142.88µs med=277.87µs max=1.06s p(90)=374.7µs p(95)=453.85µs
iterations..............................................................: 2996526 49940.322303/s
vus.....................................................................: 15 min=10 max=183
vus_max.................................................................: 1044 min=1044 max=1044
NETWORK
data_received...........................................................: 438 MB 7.3 MB/s
data_sent...............................................................: 246 MB 4.1 MB/s
Результат — тест выполнен успешно.
Максимальная задержка — max=47.52ms
Пропускная способность — 49964.433616/s
Настройка и тестирование проксирования
K6 (генерация трафика и проверка ответа, http code и body):
import http from 'k6/http';
import { check } from 'k6';
export let options = {
scenarios: {
constant_request_rate: {
executor: 'constant-arrival-rate',
rate: 50000, // количество запросов в секунду
timeUnit: '1s', // единица времени для rate
duration: '60s', // длительность теста
preAllocatedVUs: 1000, // количество предварительно выделенных виртуальных пользователей
maxVUs: 100000, // максимальное количество VU, которое может быть задействовано
},
},
thresholds: {
checks: ['rate>=0.95'],
http_req_failed: ['rate<0.01'],
http_req_duration: ['p(95)<500'],
},
};
export default function () {
let res = http.get('https://test-backend.mish.design');
check(res, {
'status is 200': (r) => r.status === 200,
'body contains expected content': (r) => typeof r.body === 'string' && r.body.includes('hello world'),
});
}
Получаем сертификат:
sudo apt install certbot
sudo certbot certonly --standalone -d test-backend.mish.design
Сертификаты появятся тут:
/etc/letsencrypt/live/test-backend.mish.design/fullchain.pem
/etc/letsencrypt/live/test-backend.mish.design/privkey.pem
Теперь совместим их — это нужно для HAProxy:
cd /etc/letsencrypt/live/test-backend.mish.design/
cat fullchain.pem privkey.pem > haproxy.pem
HAProxy
Начать решили именно с HAProxy, так как он позволяет максимально быстро и эффективно протестировать работоспособность архитектуры без сложной предварительной настройки инфраструктуры.
docker-compose.yml
HAProxy запускается через Docker Compose, что позволяет легко воспроизводить окружение на разных серверах и машинах разработчиков.
Порты 80 и 443 прокидываются наружу, то есть сервис явно ориентирован на обработку HTTP и HTTPS-запросов.
SSL-сертификат Let’s Encrypt подключается напрямую через volume, таким образом сервис сразу готов к работе в защищённом режиме (HTTPS).
services:
haproxy:
image: haproxy:latest
container_name: haproxy
ports:
- "80:80"
- "443:443"
volumes:
- ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
- /etc/letsencrypt/live/test-backend.mish.design/haproxy.pem:/usr/local/etc/haproxy/certs/haproxy.pem:ro
haproxy.cfg
HAProxy настроен в режиме http, и весь трафик, поступающий на порт 443, перенаправляется в backend-сервис, указанный в конфигурации (10.0.0.8:8080).
defaults
mode http
frontend main
bind *:443 ssl crt /usr/local/etc/haproxy/certs/haproxy.pem
default_backend test_backend
backend test_backend
server test_backend_server 10.0.0.8:8080 check
Запускаем созданный выше сценарий
scenarios: (100.00%) 1 scenario, 100000 max VUs, 1m30s max duration (incl. graceful stop):
* constant_request_rate: 50000.00 iterations/s for 1m0s (maxVUs: 1000-100000, gracefulStop: 30s)
█ THRESHOLDS
checks
✓ 'rate>=0.95' rate=100.00%
http_req_duration
✓ 'p(95)<500' p(95)=1.16ms
http_req_failed
✓ 'rate<0.01' rate=0.00%
█ TOTAL RESULTS
checks_total.......................: 5967620 99455.517508/s
checks_succeeded...................: 100.00% 5967620 out of 5967620
checks_failed......................: 0.00% 0 out of 5967620
✓ status is 200
✓ body contains expected content
HTTP
http_req_duration.......................................................: avg=759.38µs min=330.64µs med=571.94µs max=1.07s p(90)=861.18µs p(95)=1.16ms
{ expected_response:true }............................................: avg=759.38µs min=330.64µs med=571.94µs max=1.07s p(90)=861.18µs p(95)=1.16ms
http_req_failed.........................................................: 0.00% 0 out of 2983810
http_reqs...............................................................: 2983810 49727.758754/s
EXECUTION
dropped_iterations......................................................: 16192 269.8536/s
iteration_duration......................................................: avg=984.55µs min=360.44µs med=643.34µs max=1.36s p(90)=968.37µs p(95)=1.35ms
iterations..............................................................: 2983810 49727.758754/s
vus.....................................................................: 34 min=28 max=357
vus_max.................................................................: 1293 min=1293 max=1293
NETWORK
data_received...........................................................: 374 MB 6.2 MB/s
data_sent...............................................................: 108 MB 1.8 MB/s
Нагрузка на систему


Основные выводы:
CPU
Загрузка до ~900% (9 ядер), стабильна весь тест → CPU справился.
K6 Test Summary
RPS (успешные): 49,728 / сек
Ошибки: 0% (http_req_failed = 0.00%)
Дропнутые итерации: 16,192 (≈0.5%)
Время ответа p(95): 1.16 ms
Макс. задержка: 1.07s
Объём данных:
Получено: 374 MB (6.2 MB/s)
Отправлено: 108 MB (1.8 MB/s)
VUs использовано: 28–357
Макс. допустимо: 1293 VUs
🟢 Отличная производительность, минимальные задержки, всё стабильно.
Envoy
Высокопроизводительный прокси-сервер и балансировщик нагрузки, разработанный компанией Lyft, поддерживающий динамическое конфигурирование, observability и множество современных протоколов.
docker-compose.yml
Envoy разворачивается через Docker Compose для простоты повторяемости окружения.
Порты 80 и 443 открываются для обработки входящего трафика.
Используется сертификат Let’s Encrypt, напрямую подключённый в конфигурацию.
services:
envoy:
image: envoyproxy/envoy-dev:latest
container_name: envoy
environment:
- "ENVOY_UID=0"
volumes:
- /etc/letsencrypt:/etc/certs:ro
- ./envoy.yaml:/etc/envoy/envoy.yaml
ports:
- "80:80"
- "443:443"
envoy.yaml
Запросы, поступающие на домен test-backend.mish.design, перенаправляются на backend-сервис по адресу 10.0.0.4:8080 с балансировкой по алгоритму round-robin.
Envoy автоматически терминирует TLS-соединения и маршрутизирует трафик по HTTP.
static_resources:
secrets:
- name: server_cert
tls_certificate:
certificate_chain:
filename: /etc/certs/live/test-backend.mish.design/fullchain.pem
private_key:
filename: /etc/certs/live/test-backend.mish.design/privkey.pem
listeners:
- name: back_listener
address:
socket_address:
address: 0.0.0.0
port_value: 443
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
codec_type: AUTO
stat_prefix: ingress_http
upgrade_configs:
- upgrade_type: websocket
route_config:
name: local_route
virtual_hosts:
- name: main
domains:
- "test-backend.mish.design"
routes:
- match:
prefix: "/"
route:
cluster: back
http_filters:
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
common_tls_context:
tls_certificate_sds_secret_configs:
- name: server_cert
clusters:
- name: back
connect_timeout: 5s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: back
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 10.0.0.4
port_value: 8080
Запускаем созданный выше сценарий
scenarios: (100.00%) 1 scenario, 100000 max VUs, 1m30s max duration (incl. graceful stop):
* constant_request_rate: 50000.00 iterations/s for 1m0s (maxVUs: 1000-100000, gracefulStop: 30s)
█ THRESHOLDS
checks
✓ 'rate>=0.95' rate=100.00%
http_req_duration
✓ 'p(95)<500' p(95)=3.34ms
http_req_failed
✓ 'rate<0.01' rate=0.00%
█ TOTAL RESULTS
checks_total.......................: 5970208 99497.954679/s
checks_succeeded...................: 100.00% 5970208 out of 5970208
checks_failed......................: 0.00% 0 out of 5970208
✓ status is 200
✓ body contains expected content
HTTP
http_req_duration.......................................................: avg=1.38ms min=421.73µs med=1ms max=131.98ms p(90)=2.11ms p(95)=3.34ms
{ expected_response:true }............................................: avg=1.38ms min=421.73µs med=1ms max=131.98ms p(90)=2.11ms p(95)=3.34ms
http_req_failed.........................................................: 0.00% 0 out of 2985104
http_reqs...............................................................: 2985104 49748.977339/s
EXECUTION
dropped_iterations......................................................: 14895 248.236248/s
iteration_duration......................................................: avg=1.63ms min=454.49µs med=1.08ms max=1.15s p(90)=2.33ms p(95)=3.87ms
iterations..............................................................: 2985104 49748.977339/s
vus.....................................................................: 59 min=49 max=291
vus_max.................................................................: 1262 min=1246 max=1262
NETWORK
data_received...........................................................: 597 MB 9.9 MB/s
data_sent...............................................................: 344 MB 5.7 MB/s
Нагрузка на систему


Основные выводы:
CPU
Загрузка до ~1600% → использовано до 16 ядер, держалось стабильно.
K6 Test Summary
RPS (успешные): 49,749 / сек
Ошибки: 0%
Дропнутые итерации: 14,895 (≈0.5%)
Время ответа p(95): 3.34 ms
Макс. задержка: 132 ms
Объём данных:
Получено: 597 MB (9.9 MB/s)
Отправлено: 344 MB (5.7 MB/s)
VUs использовано: 49–291
Макс. допустимо: 1262 VUs
🟢 Отличная производительность, минимальные задержки, всё стабильно.
Traefik
Современный обратный прокси и балансировщик нагрузки с встроенной поддержкой автоматической выдачи и обновления SSL-сертификатов через Let’s Encrypt. Он разработан для динамической работы с контейнерными средами, такими как Docker и Kubernetes.
docker-compose.yml
Запуск через Docker Compose — Traefik работает как отдельный сервис в контейнере.
Порты 80 и 443 — открыт для приёма HTTP и HTTPS-трафика.
Сертификаты Let’s Encrypt — полностью автоматическая выдача и хранение сертификатов.
services:
traefik:
image: traefik:latest
container_name: traefik
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./traefik_data:/etc/traefik
- ./traefik_data/dynamic_conf.yml:/etc/traefik/dynamic_conf.yml
command:
- "--log.level=ERROR"
- "--accesslog=false"
- "--api.dashboard=false"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--providers.file.filename=/etc/traefik/dynamic_conf.yml"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.myresolver.acme.httpchallenge=true"
- "--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web"
- "--certificatesresolvers.myresolver.acme.email=example@yandex.com"
- "--certificatesresolvers.myresolver.acme.storage=/etc/traefik/acme.json"
dynamic_conf.yml
Запросы на test-backend.mish.design по HTTPS идут на backend-сервис по адресу http://10.0.0.8:8080.
Используется entryPoint websecure (порт 443).
Балансировка выполняется на уровне Traefik.
http:
routers:
myrouter:
rule: "Host(`test-backend.mish.design`)"
entryPoints:
- websecure
tls:
certResolver: myresolver
service: myservice
services:
myservice:
loadBalancer:
servers:
- url: "http://10.0.0.4:8080"
Запускаем созданный выше сценарий
scenarios: (100.00%) 1 scenario, 100000 max VUs, 1m30s max duration (incl. graceful stop):
* constant_request_rate: 50000.00 iterations/s for 1m0s (maxVUs: 1000-100000, gracefulStop: 30s)
█ THRESHOLDS
checks
✗ 'rate>=0.95' rate=67.15%
http_req_duration
✗ 'p(95)<500' p(95)=8.46s
http_req_failed
✗ 'rate<0.01' rate=32.84%
█ TOTAL RESULTS
checks_total.......................: 723606 9317.72162/s
checks_succeeded...................: 67.15% 485906 out of 723606
checks_failed......................: 32.84% 237700 out of 723606
✗ status is 200
↳ 67% — ✓ 242953 / ✗ 118850
✗ body contains expected content
↳ 67% — ✓ 242953 / ✗ 118850
HTTP
http_req_duration.......................................................: avg=2.11s min=0s med=70.12ms max=32.98s p(90)=6.25s p(95)=8.46s
{ expected_response:true }............................................: avg=2.47s min=622.32µs med=289.6ms max=32.98s p(90)=6.68s p(95)=9.22s
http_req_failed.........................................................: 32.84% 118850 out of 361803
http_reqs...............................................................: 361803 4658.86081/s
EXECUTION
dropped_iterations......................................................: 2088749 26896.379681/s
iteration_duration......................................................: avg=4.42s min=1.1ms med=358.05ms max=1m0s p(90)=10.28s p(95)=27.16s
iterations..............................................................: 361803 4658.86081/s
vus.....................................................................: 26 min=26 max=29182
vus_max.................................................................: 29243 min=2532 max=29243
NETWORK
data_received...........................................................: 126 MB 1.6 MB/s
data_sent...............................................................: 24 MB 308 kB/s
Нагрузка на систему


Основные выводы:
CPU
Загрузка до ~3000% → задействовано ~30 ядер.
Network Traffic
Периоды падения, из-за ошибок и дропов.
K6 Test Summary (Traefik)
Успешные запросы: 4,659 RPS (в 10 раз ниже цели)
Ошибки: ❌ 32.84%
Дропы: ❌ 2,088,749 итераций (‼️ огромный уровень отказов)
p(95) ответа: ❌ 8.46 сек
Максимальная задержка: 32.98 сек (!)
Передано:
Получено: 126 MB (1.6 MB/s)
Отправлено: 24 MB (308 KB/s)
VUs: до 29,243 → рост числа VUs указывает на тяжёлую деградацию под нагрузкой
❗ Вывод:
Traefik не справился с 50k RPS.
Наблюдаются:
Массовые дропы итераций
Высокие задержки
Требуется оптимизация, либо он не подходит для такого уровня нагрузки в текущей конфигурации.
Nginx
Классический веб-сервер и обратный прокси, известный своей стабильностью, высокой производительностью и минимальным потреблением ресурсов. Один из самых популярных инструментов для терминации TLS, балансировки нагрузки и проксирования трафика
docker-compose.yml
Запуск через Docker Compose — контейнер с NGINX разворачивается быстро и удобно.
Открыты порты 80 (HTTP) и 443 (HTTPS) для обработки входящего трафика.
Подключены сертификаты от Let’s Encrypt для обеспечения HTTPS.
services:
nginx:
image: nginx:latest
volumes:
- ./default.conf:/etc/nginx/nginx.conf
- /etc/letsencrypt/live/test-backend.mish.design/fullchain.pem:/etc/letsencrypt/live/test-backend.mish.design/fullchain.pem:ro
- /etc/letsencrypt/live/test-backend.mish.design/privkey.pem:/etc/letsencrypt/live/test-backend.mish.design/privkey.pem:ro
ports:
- "80:80"
- "443:443"
default.conf
Прописаны все основные заголовки для правильной передачи оригинального IP и схемы клиента на backend.
worker_processes auto; — количество воркеров подстраивается под доступные ядра.
worker_rlimit_nofile 100000; — увеличен лимит на количество открытых файлов (важно при высоких нагрузках).
worker_connections 5000; и multi_accept on; — рассчитано на большое количество одновременных соединений.
Используется epoll — эффективный режим для Linux-систем с высоким количеством соединений.
worker_processes auto;
worker_rlimit_nofile 300000;
events {
worker_connections 5000;
multi_accept on;
use epoll;
}
http {
server {
listen 443 ssl;
server_name test-backend.mish.design;
ssl_certificate /etc/letsencrypt/live/test-backend.mish.design/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/test-backend.mish.design/privkey.pem;
location / {
proxy_pass http://10.0.0.4:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}
Запускаем созданный выше сценарий
scenarios: (100.00%) 1 scenario, 100000 max VUs, 1m30s max duration (incl. graceful stop):
* constant_request_rate: 50000.00 iterations/s for 1m0s (maxVUs: 1000-100000, gracefulStop: 30s)
█ THRESHOLDS
checks
✗ 'rate>=0.95' rate=89.79%
http_req_duration
✗ 'p(95)<500' p(95)=3.82s
http_req_failed
✗ 'rate<0.01' rate=10.20%
█ TOTAL RESULTS
checks_total.......................: 1649228 18316.01633/s
checks_succeeded...................: 89.79% 1480978 out of 1649228
checks_failed......................: 10.20% 168250 out of 1649228
✗ status is 200
↳ 89% — ✓ 740489 / ✗ 84125
✗ body contains expected content
↳ 89% — ✓ 740489 / ✗ 84125
HTTP
http_req_duration.......................................................: avg=1.72s min=0s med=1.73s max=30.09s p(90)=3.32s p(95)=3.82s
{ expected_response:true }............................................: avg=1.91s min=4.27ms med=1.81s max=30.09s p(90)=3.42s p(95)=3.86s
http_req_failed.........................................................: 10.20% 84125 out of 824614
http_reqs...............................................................: 824614 9158.008165/s
EXECUTION
dropped_iterations......................................................: 1154367 12820.183033/s
iteration_duration......................................................: avg=1.81s min=14.05ms med=1.81s max=30.09s p(90)=3.4s p(95)=3.9s
iterations..............................................................: 824614 9158.008165/s
vus.....................................................................: 1 min=1 max=29297
vus_max.................................................................: 29354 min=2620 max=29354
NETWORK
data_received...........................................................: 231 MB 2.6 MB/s
data_sent...............................................................: 95 MB 1.1 MB/s
Нагрузка на систему


Основные выводы:
CPU
Загрузка до ~3000% → задействовано ~30 ядер.
Network Traffic
Трафик стабильнее, чем у Traefik, но ниже, чем у Envoy.
K6 Test Summary (NGINX)
Успешные RPS: 9,158 / сек (х2 выше Traefik, но всё ещё ниже цели)
Ошибки: ❌ 10.2% (http_req_failed)
Дропы: ❌ 1,154,367 итераций
p(95) задержка: ❌ 3.82 сек, медиана 1.73 сек
Макс. задержка: 30 сек (таймаут)
Передано:
Получено: 231 MB (2.6 MB/s)
Отправлено: 95 MB (1.1 MB/s)
VUs: до 29,354 → высокая нагрузка на уровне Traefik.
⚠️ Вывод:
NGINX работает лучше Traefik, но сильно уступает HAProxy и Envoy.
Присутствуют таймауты, высокие задержки и дропы.
Caddy
Это современный веб-сервер и обратный прокси с нативной поддержкой HTTPS и автоматического получения сертификатов от Let’s Encrypt. Главные преимущества Caddy — простота конфигурации, автоматическое управление TLS и минимальные усилия по обслуживанию.
docker-compose.yml
Запуск через Docker Compose — контейнерный запуск с удобным управлением жизненным циклом.
Открыты порты 80 и 443 (включая 443/udp для HTTP/3).
Используется Caddyfile — минималистичная и читаемая конфигурация.
services:
caddy:
image: caddy:latest
container_name: caddy
hostname: caddy
ports:
- "80:80"
- "443:443"
- "443:443/udp"
restart: unless-stopped
volumes:
- caddy_data:/data
- caddy_config:/config
- ./Caddyfile:/etc/caddy/Caddyfile
logging:
driver: "json-file"
options:
max-size: "200k"
max-file: "10"
cap_add:
- NET_ADMIN
volumes:
caddy_data:
caddy_config:
Caddyfile
Caddy автоматически берёт и обновляет сертификаты Let’s Encrypt без дополнительных скриптов и настроек.
Логирование настроено на уровень ERROR для минимальной нагрузки на диск.
Проксирование запросов на backend-сервис по адресу http://10.0.0.4:8080.
test-backend.mish.design {
log {
level ERROR
}
reverse_proxy http://10.0.0.4:8080
}
Запускаем созданный выше сценарий
scenarios: (100.00%) 1 scenario, 100000 max VUs, 1m30s max duration (incl. graceful stop):
* constant_request_rate: 50000.00 iterations/s for 1m0s (maxVUs: 1000-100000, gracefulStop: 30s)
█ THRESHOLDS
checks
✗ 'rate>=0.95' rate=64.12%
http_req_duration
✗ 'p(95)<500' p(95)=17.03s
http_req_failed
✗ 'rate<0.01' rate=35.87%
█ TOTAL RESULTS
checks_total.......................: 603996 6708.595341/s
checks_succeeded...................: 64.12% 387296 out of 603996
checks_failed......................: 35.87% 216700 out of 603996
✗ status is 200
↳ 64% — ✓ 193648 / ✗ 108350
✗ body contains expected content
↳ 64% — ✓ 193648 / ✗ 108350
HTTP
http_req_duration.......................................................: avg=4.29s min=0s med=377.57ms max=34.92s p(90)=12.88s p(95)=17.03s
{ expected_response:true }............................................: avg=5.03s min=917.98µs med=2.73s max=31.94s p(90)=12.95s p(95)=16.93s
http_req_failed.........................................................: 35.87% 108350 out of 301998
http_reqs...............................................................: 301998 3354.29767/s
EXECUTION
dropped_iterations......................................................: 1861627 20677.127362/s
iteration_duration......................................................: avg=5.14s min=999.47µs med=477.38ms max=56.43s p(90)=16.17s p(95)=19.59s
iterations..............................................................: 301998 3354.29767/s
vus.....................................................................: 1 min=1 max=28943
vus_max.................................................................: 29170 min=2432 max=29170
NETWORK
data_received...........................................................: 104 MB 1.2 MB/s
data_sent...............................................................: 23 MB 250 kB/s
Нагрузка на систему


Основные выводы:
CPU
Загрузка до ~3000% → задействовано ~30 ядер.
Network Traffic
Пик входящего трафика: ~6–7 MBps, исходящего: ~3–4 MBps
Колебания присутствуют, особенно в начале — возможны ошибки или нестабильная отдача.
K6 Test Summary (Caddy)
Успешные RPS: 3,354 / сек → в 15 раз ниже цели
Ошибки: ❌ 35.87% (http_req_failed)
Дропы: ❌ 1,861,627 итераций
p(95) задержка: ❌ 17.03 сек (!), медиана ≈0.4 сек
Макс. задержка: 34.92 сек
Трафик:
Получено: 104 MB (1.2 MB/s)
Отправлено: 23 MB (250 KB/s)
VUs: до 29,170 — почти на пределе лимита
❌ Вывод:
Caddy не выдерживает нагрузку в 50k RPS
Большая доля ошибок, дропов и задержек
Работает хуже, чем NGINX и Traefik, ближе к антирекорду
Итоги
✅ HAProxy — лидер по стабильности
Уверенный лидер по результатам тестов: стабильно держит 50 000 rps при минимальной нагрузке на CPU. Прост в запуске и конфиге.
🟢 Envoy — почти наравне с HAProxy
Нагрузка на CPU выше, но приемлемая. Удобен в сценариях, где нужны гибкие настройки маршрутизации.
🟡 NGINX — середняк
Нужен fine-tuning или другой режим работы
Пропускная способность в 5 раз ниже ожидаемой
Аномальная нагрузка на CPU
🔴 Traefik — нестабилен
Почти 33% ошибок, сильные дропы, p95 = 8.5 сек.
Пропускная способность в 5 раз ниже ожидаемой
Аномальная нагрузка на CPU
🔻 Caddy — худший результат
Самые высокие задержки (p95 = 17 сек), ошибки 36%, дропов >1.8 млн.
Аномальная нагрузка на CPU
Явно не справляется с такой нагрузкой в дефолтной конфигурации.
Что важно понять из результатов?
HAProxy и Envoy — лучшие выборы, если нужна готовая к бою система без плясок с бубном.
Traefik, NGINX, Caddy — требуют дополнительной настройки и оптимизации для высоконагруженных сценариев.
Для кого подойдёт и зачем всё это использовать?
Когда можно брать HAProxy или Envoy:
Когда нужен стабильный и производительный балансировщик «здесь и сейчас».
Минимальные усилия на настройку — запустил и работает.
Когда стоит смотреть на Traefik / NGINX / Caddy:
Для небольших и средних проектов, где нагрузки ниже и можно позволить себе чуть сложнее конфиг или доработку.
Если в команде есть человек, который умеет настраивать и оптимизировать эти инструменты под конкретные задачи.
Update
По просьбам из комментариев дополнительные тесты NGINX
Тест №1
keepalive 4;
Без multi_accept
worker_processes auto;
worker_rlimit_nofile 300000;
events {
worker_connections 5000;
use epoll;
}
http {
upstream backend {
server 10.0.0.4:8080;
keepalive 4;
}
server {
listen 443 ssl;
server_name test-backend.mish.design;
ssl_certificate /etc/letsencrypt/live/test-backend.mish.design/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/test-backend.mish.design/privkey.pem;
location / {
proxy_pass http://backend;
proxy_set_header Connection '';
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}
█ THRESHOLDS
checks
✗ 'rate>=0.95' rate=30.91%
http_req_duration
✗ 'p(95)<500' p(95)=1.11s
http_req_failed
✗ 'rate<0.01' rate=69.08%
█ TOTAL RESULTS
checks_total.......................: 1012194 11241.972175/s
checks_succeeded...................: 30.91% 312910 out of 1012194
checks_failed......................: 69.08% 699284 out of 1012194
✗ status is 200
↳ 30% — ✓ 156455 / ✗ 349642
✗ body contains expected content
↳ 30% — ✓ 156455 / ✗ 349642
HTTP
http_req_duration.......................................................: avg=482.88ms min=0s med=541.38ms max=2.64s p(90)=1s p(95)=1.11s
{ expected_response:true }............................................: avg=308.14ms min=448.5µs med=26.44ms max=2.64s p(90)=1.19s p(95)=1.45s
http_req_failed.........................................................: 69.08% 349642 out of 506097
http_reqs...............................................................: 506097 5620.986087/s
EXECUTION
dropped_iterations......................................................: 1780766 19778.147096/s
iteration_duration......................................................: avg=3.13s min=495.19µs med=658.68ms max=1m0s p(90)=1.37s p(95)=30s
iterations..............................................................: 506097 5620.986087/s
vus.....................................................................: 13 min=13 max=29174
vus_max.................................................................: 29266 min=1504 max=29266
NETWORK
data_received...........................................................: 179 MB 2.0 MB/s
data_sent...............................................................: 56 MB 624 kB/s
Тест №2
keepalive 4;
C multi_accept
worker_processes auto;
worker_rlimit_nofile 300000;
events {
worker_connections 5000;
use epoll;
multi_accept on;
}
http {
upstream backend {
server 10.0.0.4:8080;
keepalive 4;
}
server {
listen 443 ssl;
server_name test-backend.mish.design;
ssl_certificate /etc/letsencrypt/live/test-backend.mish.design/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/test-backend.mish.design/privkey.pem;
location / {
proxy_pass http://backend;
proxy_set_header Connection '';
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}
█ THRESHOLDS
checks
✗ 'rate>=0.95' rate=25.90%
http_req_duration
✗ 'p(95)<500' p(95)=9.14s
http_req_failed
✗ 'rate<0.01' rate=74.09%
█ TOTAL RESULTS
checks_total.......................: 944528 13781.245473/s
checks_succeeded...................: 25.90% 244664 out of 944528
checks_failed......................: 74.09% 699864 out of 944528
✗ status is 200
↳ 25% — ✓ 122332 / ✗ 349932
✗ body contains expected content
↳ 25% — ✓ 122332 / ✗ 349932
HTTP
http_req_duration.......................................................: avg=3.1s min=0s med=2.85s max=20.66s p(90)=7.18s p(95)=9.14s
{ expected_response:true }............................................: avg=1.37s min=462.33µs med=26.11ms max=20.66s p(90)=6.27s p(95)=8.41s
http_req_failed.........................................................: 74.09% 349932 out of 472264
http_reqs...............................................................: 472264 6890.622736/s
EXECUTION
dropped_iterations......................................................: 1642833 23969.94567/s
iteration_duration......................................................: avg=3.28s min=504.69µs med=3.01s max=21.41s p(90)=7.37s p(95)=9.31s
iterations..............................................................: 472264 6890.622736/s
vus.....................................................................: 376 min=376 max=29063
vus_max.................................................................: 29248 min=1490 max=29248
NETWORK
data_received...........................................................: 200 MB 2.9 MB/s
data_sent...............................................................: 55 MB 797 kB/s
Тест №3
Исходный конфиг + тюнинг SSL
worker_processes auto;
worker_rlimit_nofile 300000;
events {
worker_connections 5000;
multi_accept on;
use epoll;
}
http {
server {
listen 443 ssl;
server_name test-backend.mish.design;
ssl_certificate /etc/letsencrypt/live/test-backend.mish.design/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/test-backend.mish.design/privkey.pem;
ssl_ciphers 'TLS_AES_128_GCM_SHA256:ECDHE-RSA-AES128-GCM-SHA256';
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://10.0.0.4:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}
█ THRESHOLDS
checks
✗ 'rate>=0.95' rate=90.23%
http_req_duration
✗ 'p(95)<500' p(95)=3.97s
http_req_failed
✗ 'rate<0.01' rate=9.76%
█ TOTAL RESULTS
checks_total.......................: 1719696 24413.0758/s
checks_succeeded...................: 90.23% 1551846 out of 1719696
checks_failed......................: 9.76% 167850 out of 1719696
✗ status is 200
↳ 90% — ✓ 775923 / ✗ 83925
✗ body contains expected content
↳ 90% — ✓ 775923 / ✗ 83925
HTTP
http_req_duration.......................................................: avg=1.49s min=0s med=1.29s max=1m0s p(90)=3.12s p(95)=3.97s
{ expected_response:true }............................................: avg=1.65s min=4.09ms med=1.42s max=22.06s p(90)=3.26s p(95)=4.06s
http_req_failed.........................................................: 9.76% 83925 out of 859848
http_reqs...............................................................: 859848 12206.5379/s
EXECUTION
dropped_iterations......................................................: 1921167 27273.189911/s
iteration_duration......................................................: avg=1.56s min=13.02ms med=1.35s max=1m0s p(90)=3.18s p(95)=4.03s
iterations..............................................................: 859848 12206.5379/s
vus.....................................................................: 1 min=1 max=28502
vus_max.................................................................: 28789 min=2785 max=28789
NETWORK
data_received...........................................................: 239 MB 3.4 MB/s
data_sent...............................................................: 100 MB 1.4 MB/s
Спасибо что дочитали до конца ;-)