Когда я писал статью про HAProxy, у меня возникла идея сравнить его с другим популярным proxy-сервером, например с Envoy. Но тогда мне показалось, что простое сравнение в виде таблицы или пары абзацев будет неинформативным — и я решил сделать полноценный разбор в отдельной статье. Если вам интересно — добро пожаловать! Здесь рассмотрены не все возможности каждого решения, но ключевые — те, которые действительно важны на практике.
Сегодня я разберу три популярных прокси, сравню их и расскажу: что, где и когда лучше применять. Под «популярными» я имею в виду те, с которыми работал сам и изучил их устройство «под капотом». Прокси существует гораздо больше, но о других говорить не буду — либо не копал глубоко, либо знаю слишком мало, чтобы включать их в разбор. Отдельно отмечу важность документации: если она запутана или неполна, приходится гадать, что и где настраивать, а это быстро отбивает желание работать с инструментом.
HAProxy 3.3, NGINX 1.29 и Envoy 1.35 — три open source-прокси с разной архитектурой и моделью управления. Enterprise-версии рассматривать не буду — капитализм делает свое дело: серьёзных отличий почти нет, а вот в OSS-вариантах есть что сравнить — в ряде моментов конкуренция пошла на пользу.
Итак, начинаем. Рассматриваем самые свежие релизы на дату публикации.
План разбора:
-
Цель и версии (только open source, последние доступные stable-ветки)
-
Архитектура (Модель потоков/процессов, event-loop, статическая vs динамическая конфигурация (xDS, Data Plane API).
-
Поддерживаемые протоколы L4/L7 (HTTP/1.1, HTTP/2, HTTP/3/QUIC, gRPC, WebSocket, TCP/UDP)
-
Балансировка и маршрутизация (Алгоритмы round-robin, least-conn, hash, L7-маршруты, аффинити)
-
Надежность и отказоустойчивость (Health-check, retries, circuit-breaking, rate-limiting)
-
Наблюдаемость (Метрики Prometheus/StatsD, логи, OpenTelemetry/Jaeger, трассировка)
-
Безопасность (TLS 1.3, mTLS, RBAC, OAuth/JWT)
-
Переменные (Область видимости, встроенные, динамические)
-
Таймауты (виды, где\как применять, принцип работы)
-
Kubernetes-интеграция (gateway, ingress, service mesh)
Архитектура
HAProxy. Высокопроизводительный L4/L7-прокси с моделью master–worker и неблокирующим event-loop. Мастер руководит процессами, воркеры обрабатывают трафик — как шеф-повар, который не трогает еду, но следит, чтобы повара не спали. Поддерживает TCP/HTTP, TLS-терминацию, health-checks и продвинутые алгоритмы балансировки. Конфигурация статична, но есть Runtime API для живых правок; полный reconfigure делается через бесшовный reload. Типовой кейс: frontend принимает HTTPS, терминирует TLS, выбирает backend по leastconn и отправляет запрос — pipeline прозрачнее, чем бюджет в стартапе.

NGINX. Архитектура «master + workers». Воркеры принимают TLS/HTTP, парсят протокол, применяют фильтры, кеш и проксируют на upstream. Хорошо подходит для статических и программируемых конфигураций, если «динамика» нужна только на уровне переменных и Lua. Перезагрузка конфигурации обычно бесшовная: старые воркеры завершают запросы, новые уже готовы — никакой драмы, кроме как у админа в 3 ночи. Типовой кейс: server с proxy_pass на пул API, с кешем и ограничениями скорости по ключу. JWT/OAuth? Или Plus, или Lua — выбирайте, что меньше ломает мозг.

Envoy. Динамический L4/L7-прокси с фильтровой архитектурой и управлением через xDS. Конфигурация подтягивается из контрольной плоскости в реальном времени, маршрутизация гибкая до абсурда: можно отправить трафик хоть на кофемашину, если она объявилась в EDS. Поддерживает HTTP/1.1, HTTP/2, HTTP/3, gRPC, ретраи, метрики и обновляется через hot-restart без обрыва сокетов — хоть разворачивайся каждую минуту. Типовой кейс: listener с TLS, далее цепочка фильтров (ALPN → HTTP/2 → router) маршрутизирует gRPC-потоки по кластерам, которые сами приходят и уходят, как разработчики после демо.

Итого по архитектуре:
-
HAProxy — минимализм, высокая производительность, минимум магии.
-
NGINX — стабильность и зрелая экосистема, но с ограниченной гибкостью.
-
Envoy — динамика и API-центричность, но сложнее в эксплуатации.
Поддерживаемые протоколы

HAProxy работает как TCP/UDP-прокси и полнофункциональный HTTP-прокси. Поддерживает HTTP/1.1, HTTP/2 и HTTP/3 (QUIC), включая ALPN (Application-Layer Protocol Negotiation) и SNI (Server Name Indication). Может терминировать TLS или проксировать TLS passthrough. gRPC работает в режиме HTTP/2 без сторонних плагинов.
frontend fe mode http bind :80 bind :443 ssl crt /etc/haproxy/certs/foo.com/cert.crt alpn h2 bind quic4@:443 ssl crt /mycert.pem alpn h3 http-request redirect scheme https unless { ssl_fc } http-after-response add-header alt-svc 'h3=":443"; ma=60' backend be_api mode http option httpchk GET /health server s1 10.0.0.31:443 ssl verify none sni str(api.example.com) alpn h2 check server s2 10.0.0.32:443 ssl verify none sni str(api.example.com) alpn h2 check
Входящий TLS-трафик принимается на порту 443, ALPN выбирает HTTP/2 или HTTP/3, затем HAProxy терминирует соединение, балансирует по серверам с поддержкой h2 и шифрует трафик заново. Плюсы: высокая производительность на L4 и L7, зрелая поддержка HTTP/2. Минусы: настройка HTTP/3/QUIC требует проверки и ручной конфигурации; для сложных L7-правил доступны только ACL и Lua. Downstream/Upstream: HAProxy может принимать HTTP/1.1, HTTP/2 или HTTP/3 от клиентов (downstream) и независимо использовать любой протокол при подключении к бэкенду (upstream). Возможны сценарии h1→h2, h2→h1, h2→h2, h3→h1 и т.д., что позволяет менять протокол между клиентом и сервером без ограничений.
NGINX работает как TCP-stream прокси и как HTTP-прокси. Поддерживает HTTP/1.1 и HTTP/2 из коробки. HTTP/3/QUIC доступен с версии 1.25+ через директивы quic и http3. gRPC поддерживается через отдельный модуль grpc_pass, который работает поверх HTTP/2.
server { listen 443 ssl http2; ssl_certificate /etc/nginx/certs/site.pem; ssl_certificate_key /etc/nginx/certs/site.key; location / { proxy_pass https://backend_pool; proxy_http_version 1.1; } } server { listen 8443 quic reuseport; listen 8443 ssl; ssl_certificate /etc/nginx/certs/site.pem; ssl_certificate_key /etc/nginx/certs/site.key; location /api { add_header Alt-Svc 'h3=":8443"; ma=86400'; proxy_pass https://backend_pool; proxy_http_version 1.1; } } upstream backend_pool { least_conn; server 10.0.0.31:443; server 10.0.0.32:443; }
NGINX принимает TLS-соединения и включает HTTP/2 или HTTP/3 для клиентов, но к backend всё равно ходит по HTTP/1.1 (кроме gRPC). Плюсы: стабильный HTTP/2, простой конфиг. Минусы: HTTP/3 менее зрелый и требует ручного включения; stream-режим TCP не даёт детальной L7-логики; расширенные функции доступны только в Plus-версии. Downstream/Upstream: клиенты могут подключаться по HTTP/1.1, HTTP/2 или HTTP/3, но к backend соединение будет по HTTP/1.1 (кроме gRPC-location).
Envoy изначально построен как L4/L7-прокси с поддержкой TCP/UDP HTTP/1.1, HTTP/2, HTTP/3 и gRPC. Протоколы выбираются автоматически через ALPN, а фильтры позволяют обрабатывать как TCP, так и полный HTTP-поток. Для upstream на TLS/443 необходимо включить http2_protocol_options и настроить transport_socket для TLS.
Как примерно выглядит конфиг с поддержкой h1/h2/h3
admin: address: socket_address: address: 127.0.0.1 port_value: 9901 static_resources: listeners: - name: main_listener address: socket_address: address: 0.0.0.0 port_value: 443 # TLS конфигурация для HTTPS listener_filters: - name: envoy.filters.listener.tls_inspector typed_config: "@type": type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector filter_chains: - filter_chain_match: application_protocols: ["h2", "http/1.1"] transport_socket: name: envoy.transport_sockets.tls typed_config: "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsConfig common_tls_context: tls_certificates: - certificate_chain: {filename: "/etc/envoy/cert.pem"} private_key: {filename: "/etc/envoy/privkey.pem"} require_client_certificate: false filters: - name: envoy.filters.network.http_connection_manager typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: ingress_http http2_protocol_options: {} http_protocol_options: {} codec_type: AUTO route_config: name: local_route virtual_hosts: - name: local_service domains: ["*"] routes: - match: {prefix: "/"} route: {cluster: backend_cluster} http_filters: - name: envoy.filters.http.router typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router # Отдельный filter chain для HTTP/3 - filter_chain_match: transport_protocol: "quic" transport_socket: name: envoy.transport_sockets.quic typed_config: "@type": type.googleapis.com/envoy.extensions.transport_sockets.quic.v3.QuicDownstreamTransportConfig downstream_tls_context: common_tls_context: tls_certificates: - certificate_chain: {filename: "/etc/envoy/cert.pem"} private_key: {filename: "/etc/envoy/privkey.pem"} filters: - name: envoy.filters.network.http_connection_manager typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: ingress_http_quic http2_protocol_options: {} http_protocol_options: {} codec_type: HTTP3 route_config: name: local_route_quic virtual_hosts: - name: local_service domains: ["*"] routes: - match: {prefix: "/"} route: {cluster: backend_cluster} http_filters: - name: envoy.filters.http.router typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router # UDP listener для QUIC udp_listener_config: quic_options: {} clusters: - name: backend_cluster connect_timeout: 5s type: STATIC lb_policy: ROUND_ROBIN typed_extension_protocol_options: envoy.extensions.upstreams.http.v3.HttpProtocolOptions: "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions explicit_http_config: http2_protocol_options: {} load_assignment: cluster_name: backend_cluster endpoints: - lb_endpoints: - endpoint: address: socket_address: address: 127.0.0.1 port_value: 8080
Envoy listener принимает соединения на 443, сам определяет протокол (h1/h2/h3), маршрутизирует поток на пул с тем же протоколом или переводит его в нужный режим. gRPC обрабатывается нативно через HTTP/2. Плюсы: полная поддержка современных протоколов, гибкость через фильтры, без перезапуска можно менять маршруты. Минусы: выше потребление ресурсов, сложнее конфигурация для простых TCP-сценариев. Downstream/Upstream: Envoy автоматически конвертирует любые протоколы — h1↔h2↔h3 в любых комбинациях. Можно принимать h3 downstream и ходить к бэкенду по h1, или наоборот.
HAProxy — чемпион по скорости, будто с допингом. Глотает TCP, HTTP/1.1, HTTP/2, HTTP/3 и даже gRPC без костылей. Протокол downstream/upstream можно миксовать как коктейли на корпоративе — хоть h3→h1, хоть h1→h2. Минус: для QUIC придётся повозиться с ручной настройкой, а за сложные L7-правила — только ACL и Lua, никакой встроенной магии. Но зато latency минимальная, как у хорошего эспрессо.
NGINX — надёжен как дедовский жигуль: HTTP/2 держит стабильно, HTTP/3 прикручен «на синюю изоленту», а сложной L7-логики не жди. TCP через stream идёт, но апгрейд backend до h2 не завезут — хочешь комфорта, смотри NGINX Plus.
Envoy — как инженер с немецким дипломом: жонглирует h1/h2/h3 фильтрами, не потея и не перезапускаясь. Downstream хоть HTTP/3, backend хоть HTTP/1.1 — всё под контролем. Но ресурсы ест как браузер с сотней вкладок и требует знаний на уровне DevOps-соревнований.
Вывод:
-
HAProxy — брать, если нужна минимальная задержка и зрелый HTTP/1/2 без плясок.
-
NGINX — выбрать для стабильного HTTP/1/2 и простого конфига, но помнить про слабый upstream-функционал.
-
Envoy — король gRPC и любых протокольных миксов, но для банального L4-прокси это как на Porsche картошку возить.
Балансировка и маршрутизация

HAProxy поддерживает алгоритмы балансировки: roundrobin, leastconn, source, uri, hdr, consistent-hash. Маршрутизация уровня L7 реализуется через ACL: можно матчить по URI, заголовкам, TLS-отпечаткам (JA3/JA4) с использованием Lua.
frontend fe_api bind :443 ssl crt /etc/haproxy/certs/api.pem acl v1 path_beg /v1/ use_backend be_v1 if v1 default_backend be_v2 backend be_v1 balance leastconn server s1 10.0.0.41:443 ssl verify none check server s2 10.0.0.42:443 ssl verify none check backend be_v2 balance uri hash-type consistent server s3 10.0.0.43:443 ssl verify none check server s4 10.0.0.44:443 ssl verify none check
Фронтенд принимает HTTPS-запросы, проверяет путь /v1/ и отправляет их в пул be_v1 с балансировкой по наименьшему числу активных подключений. Остальные запросы попадают в пул be_v2 с консистентным хешированием URI.
NGINX поддерживаемые алгоритмы: round_robin (по умолчанию), least_conn, ip_hash, hash с опцией consistent. Маршруты уровня L7 задаются через location. Конфигурация обновляется только через reload.
upstream api_v1 { least_conn; server api1.local:443; server api2.local:443; } upstream api_v2 { hash $request_uri consistent; server api3.local:443; server api4.local:443; } server { listen 443 ssl http2; ssl_certificate /etc/nginx/certs/api.pem; ssl_certificate_key /etc/nginx/certs/api.key; proxy_ssl_server_name on; location /v1/ { proxy_pass https://api_v1; } location / { proxy_pass https://api_v2; } }
Запросы к /v1/ перенаправляются на пул api_v1 с алгоритмом least_conn. Остальные — на api_v2 с консистентным хешированием URI.
Envoy поддерживает алгоритмы: round_robin, least_request, maglev, ring_hash, а также веса, канареечные релизы, shadow-трафик. Управление и маршрутизация меняются динамически через xDS.
static_resources: listeners: - name: https_listener address: { socket_address: { address: 0.0.0.0, port_value: 443 } } filter_chains: - transport_socket: name: envoy.transport_sockets.tls typed_config: "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "/etc/envoy/tls/api.crt" } private_key: { filename: "/etc/envoy/tls/api.key" } filters: - name: envoy.filters.network.http_connection_manager typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: ingress route_config: virtual_hosts: - name: api domains: ["*"] routes: - match: { prefix: "/v1/" } route: { cluster: api_v1 } - match: { prefix: "/" } route: { cluster: api_v2 } http_filters: - name: envoy.filters.http.router clusters: - name: api_v1 lb_policy: LEAST_REQUEST transport_socket: name: envoy.transport_sockets.tls typed_config: "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext sni: "api.internal" load_assignment: cluster_name: api_v1 endpoints: - lb_endpoints: - endpoint: { address: { socket_address: { address: api1.local, port_value: 443 }}} - name: api_v2 lb_policy: MAGLEV transport_socket: name: envoy.transport_sockets.tls typed_config: "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext sni: "api.internal" load_assignment: cluster_name: api_v2 endpoints: - lb_endpoints: - endpoint: { address: { socket_address: { address: api3.local, port_value: 443 }}}
Envoy слушает HTTPS-трафик на порту 443, маршрутизирует запросы по префиксу URI: /v1/ → кластер api_v1 (least_request), / → кластер api_v2 (Maglev). Все соединения к апстримам идут по TLS с проверкой SNI.
HAProxy — работает как швейцарский механизм: поддерживает roundrobin, leastconn, source, uri, hdr, consistent-hash — всё стабильно и предсказуемо. L7-маршруты через ACL: можно матчить по URI, заголовкам, даже по TLS-отпечаткам (JA3/JA4). Конфигурация статична, но понятна и прозрачна.
NGINX — минимализм без сюрпризов: доступны только round_robin, least_conn, ip_hash. HTTP-маршруты задаются через location, но без динамических алгоритмов или ring_hash. Sticky-сессии — только через ip_hash, API для внешнего управления нет, расширенные возможности балансировки есть только в NGINX Plus. Хорош, если конфиг «разложил и забыл», а трафик стабилен.
Envoy — тяжёлая артиллерия: поддерживает round_robin, least_request, maglev, ring_hash, веса, канареечные релизы, shadow-трафик. Всё управляется динамически через xDS — можно менять маршруты хоть каждую секунду. Но YAML-схемы перегружены и требуют опыта, иначе будет больно. Для сложной продакшен-динамики — это лучший выбор, но придётся вложиться в автоматизацию и обучение.
Выводы:
-
HAProxy — низкая задержка и предсказуемый контроль L4/L7. Настроил ACL — и работает.
-
NGINX — статические маршруты без динамических требований. Конфиг «разложил и забыл».
-
Envoy — динамическое управление, канареечные релизы и сложные политики, но конфигурация громоздкая и требует автоматизации.
Надежность и отказоустойчивость

HAProxy обеспечивает встроенные механизмы отказоустойчивости на L4 и L7: активные health-check, повторные попытки при ошибках, circuit breaker через лимиты соединений, а также защиту от перегрузки через stick-tables.
global maxconn 20000 # верхний предел соединений на процесс HAProxy defaults maxconn 10000 # лимит для фронтендов/бэкендов по умолчанию backend be_api mode http option httpchk GET /health retry-on all-retryable-errors retries 3 http-request disable-l7-retry if METH_POST METH_PUT METH_DELETE default-server inter 2s fall 3 rise 2 server s1 10.0.0.51:443 ssl verify none maxconn 500 server s2 10.0.0.52:443 ssl verify none maxconn 500 stick-table type ip size 1m expire 10m store conn_rate(10s) tcp-request connection track-sc0 src tcp-request connection reject if { sc0_conn_rate gt 100 }
Серверы проверяются каждые две секунды по пути /health. Они исключаются из пула после трёх неудачных ответов и возвращаются обратно после двух успешных. Если лимит подключений к серверу (500 соединений) превышен, лишние запросы становятся в очередь и ожидают timeout queue (по умолчанию — от timeout connect), после чего возвращается 503. Параметр retries 3 выполняет до трёх попыток соединения (исходная + до двух повторов), а опция retry-on all-retryable-errors и директива http-request disable-l7-retry гарантируют, что HAProxy будет повторять только идемпотентные методы и не будет ретраить POST, PUT, DELETE. Параметры global maxconn и server maxconn задают верхние пределы соединений на процесс и на каждый backend. Практический предел зависит также от лимита файловых дескрипторов (ulimit -n), поскольку одно проксируемое соединение обычно требует два FD(≈1–2 FD на сессию). Дополнительно stick-tables отслеживают частоту подключений и блокируют источник при превышении порога, например 100 соединений за 10 секунд.
NGINX использует пассивные health-check (max_fails/fail_timeout) и proxy_next_upstream для fallback. Важно понимать нюанс подсчёта повторов: proxy_next_upstream_tries N считается на всю upstream-группу, то есть при proxy_next_upstream_tries 3 реально получается исходный запрос + до 2 повторов по остальным серверам (итого 1 + 2), а не 1 + 3 для каждого backend.
worker_processes auto; worker_rlimit_nofile 4096; # максимальное количество файловых дескрипторов, которые может использовать один worker-процесс events { worker_connections 2048; # максимальное число одновременных соединений, которые может обработать один worker-процесс multi_accept on; } http { upstream api_pool { least_conn; server 10.0.0.51 max_fails=3 fail_timeout=5s; server 10.0.0.52 max_fails=3 fail_timeout=5s; } limit_req_zone $binary_remote_addr zone=req_limit:10m rate=100r/s; server { listen 443 ssl; limit_req zone=req_limit burst=50; proxy_pass https://api_pool; proxy_next_upstream error timeout http_502 http_503 http_504; proxy_next_upstream_tries 3; } }
При трёх ошибках подряд сервер исключается из пула на пять секунд. Для ограничения нагрузки используются limit_req и limit_conn, которые регулируют частоту и количество запросов на клиента или глобально. Максимальное число соединений на процесс определяется как минимум из worker_connections и worker_rlimit_nofile, а реальный предел для всех воркеров равен произведению числа процессов на лимит воркера, с учётом того, что одно проксируемое соединение использует около двух файловых дескрипторов. В Open-Source NGINX нет per-backend лимитов, аналогичных server maxconn; эти возможности (max_conns и очередь на апстриме) есть только в NGINX Plus.
Envoy предоставляет полный набор инструментов: активные health-check (HTTP/TCP/gRPC), circuit breaking с лимитами по соединениям, запросам и ошибкам, retries с гибкой политикой, outlier detection для автоматического исключения проблемных узлов и фильтры для ограничения входящих соединений
static_resources: listeners: - name: https_listener address: { socket_address: { address: 0.0.0.0, port_value: 443 } } filter_chains: - filters: - name: envoy.filters.network.connection_limit typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.connection_limit.v3.ConnectionLimit stat_prefix: conn_limit max_connections: 10000 delay: 0s - name: envoy.filters.network.http_connection_manager typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: ingress route_config: { ... } http_filters: [ { name: envoy.filters.http.router } ] clusters: - name: api_pool lb_policy: LEAST_REQUEST circuit_breakers: thresholds: - max_connections: 500 max_requests: 1000 health_checks: - timeout: 1s interval: 2s unhealthy_threshold: 3 healthy_threshold: 2 http_health_check: { path: /health } outlier_detection: consecutive_5xx: 5 interval: 5s base_ejection_time: 30s
Фильтр connection_limit ограничивает входящие соединения на listener, а circuit breakers задают пределы подключений и одновременных запросов к каждому кластеру. Механизм retries настраивается в retry_policy: параметр num_retries определяет только повторные попытки, всего выполняется исходный запрос + num_retries, а условия повтора можно задавать детально (например, только при отказе соединения или только при ответах 503) с использованием экспоненциального backoff. Количество файловых дескрипторов управляется на уровне ОС через ulimit -n, а перегрузка отдельных серверов контролируется с помощью outlier detection, автоматически исключающего узлы с избыточными ошибками 5xx.
HAProxy — как строгий охранник у клуба: лишние соединения не пустит, пьяных клиентов выгонит (stick-tables), сердцебиение проверяет каждую секунду. Circuit breaker настраивается вручную, но работает чётко. Минус: сложные сценарии придётся писать на Lua или подключать внешнюю автоматику — без этого всё руками, как в старой школе DevOps.
NGINX — охранник без рации: может заметить, что клиент упал, но только если он реально свалился на пол. Активных health-check нет (оставили для платной версии), circuit breaker в зачатке. Зато лимиты соединений и запросов задаются парой директив, без магии и Lua. Хорош для стабильных пулов, где никто не бегает и свет не мигает.
Envoy — охранник с камерой, тепловизором и аналитикой на ИИ: активные проверки, circuit breaker на всё подряд, outlier detection, retries с умной задержкой. Встроенный или внешний rate limiting — на выбор. Минус: он просит больше зарплату (CPU/memory) и инструктаж посложнее, иначе случайно закроет дверь даже перед хозяином.
Вывод:
-
HAProxy — лучший выбор для L4/L7-балансировщиков, где нужна быстрая защита от перегрузки и детальная ручная настройка.
-
NGINX — подойдёт, если достаточно пассивных проверок и простых лимитов без динамики.
-
Envoy — подходит для живых систем с автоскейлом и централизованным контролем трафика, но требует аккуратного обращения.
Наблюдаемость

HAProxy выводит подробные логи через syslog (RFC5424/3164), поддерживает structured logging (JSON) через log-format и экспорт метрик в Prometheus через встроенный prometheus-exporter или stats socket. Встроенный веб-статус (stats uri) есть, но распределённой трассировки нет — требуются внешние агенты (Jaeger, Zipkin).
global log stdout format raw local0 stats socket /var/run/haproxy.sock mode 600 level admin stats timeout 30s defaults log global option httplog log-format { "time":"%t", "frontend":"%f", "backend":"%b", "srv":"%s", "status":%ST, "bytes":%B } frontend prometheus bind *:8405 mode http http-request use-service prometheus-exporter if { path /metrics } no log
Логи пишутся в stdout, метрики публикуются по http://<host>:8405/metrics. Плюсы: минимальные накладные расходы, детальные логи. Минусы: нет встроенной трассировки запросов, приходится интегрировать вручную.
NGINX поддерживает access/error логи, может логировать в JSON для экспорта в ELK/Graylog. Метрики — только через сторонние модули (nginx-module-vts, nginx-prometheus-exporter). Встроенной распределённой трассировки нет (в отличие от NGINX Plus, где есть OpenTelemetry).
http { log_format json_combined escape=json '{ "time":"$time_iso8601",' '"remote":"$remote_addr",' '"host":"$host",' '"request":"$request",' '"status":$status,' '"bytes":$body_bytes_sent }'; access_log /var/log/nginx/access.json json_combined; server { listen 80; location / { proxy_pass http://10.0.0.52:8080; } } }
Трафик логируется в JSON, готов для парсинга внешними системами. Плюсы: гибкий формат логов, легко подключать ELK. Минусы: нет нативных метрик и трассировки, нужен внешний экспортер.
Envoy из коробки поддерживает structured logging, метрики в Prometheus, а также OpenTelemetry для распределённой трассировки (Jaeger, Zipkin, Datadog). Каждый фильтр может генерировать собственные статистики.
admin: access_log_path: /dev/stdout address: socket_address: { address: 0.0.0.0, port_value: 9901 } tracing: http: name: envoy.tracers.opentelemetry typed_config: "@type": type.googleapis.com/envoy.config.trace.v3.OpenTelemetryConfig grpc_service: envoy_grpc: cluster_name: otel_collector
Envoy пишет логи и метрики напрямую(http://<host>:9901/stats/prometheus), трассировка передается в OpenTelemetry Collector. Плюсы: нативная поддержка Prometheus и трассировки, детализированные метрики L4/L7.
HAProxy — пишет логи быстро и честно, как будто ведёт дневник без цензуры. Легко отдаёт метрики Prometheus через экспортер, а встроенный веб-статус даёт картинку без лишних зависимостей. Но трассировку придётся прикручивать самому — Lua, агенты, шаманский бубен.
NGINX — логи делает удобоваримыми в JSON и сразу готовыми для ELK, но метрики и трассировка — только через сторонние модули или экспортеры. Конфигурация простая, но никакой нативной магии для L7-метрик или распределённого следа. То есть видно, кто зашёл, но не куда он потом пошёл.
Envoy — наблюдает за всем, как параноидальный сосед с биноклем на пенсии. Логи структурированы, метрики идут прямо в Prometheus, а OpenTelemetry встроен из коробки: можно смотреть трассировку запроса от клиента до базы без внешних костылей. Минус: съедает больше ресурсов и требует аккуратной настройки, иначе легко утонуть в собственных графиках.
Вывод:
-
HAProxy — лёгкий и быстрый, но наблюдаемость только базовая.
-
NGINX — нормальные логи, но за метрики и трассировку придётся платить сторонними модулями и временем.
-
Envoy — лидер для глубокой наблюдаемости и трассировки без костылей, если готовы мириться с его аппетитами.
Безопасность

HAProxy — поддерживает TLS 1.3 и mTLS прямо на фронтендах, с версии 2.5 умеет проверять JWT и OAuth2 без внешних сервисов (http_auth_bearer, jwt_verify, jwt_payload), а для RBAC применяют ACL и переменные. Zero Trust-сценарии реализуемы через SPIFFE/SPIRE, но требуют дополнительного glue-кода для ротации ключей.
frontend https_in bind :443 ssl crt /etc/haproxy/certs.pem ca-file /etc/haproxy/ca.pem verify required http-request set-var(txn.jwt) http_auth_bearer http-request set-var(txn.verified) var(txn.jwt),jwt_verify(alg=RS256,key=/etc/haproxy/jwks.json) http-request deny if { var(txn.verified) -m int 0 } http-request set-var(txn.role) var(txn.jwt),jwt_payload,query(role) acl role_admin var(txn.role) -m str admin http-request deny unless role_admin default_backend web
Производительность высокая, функционал политик менее гибкий, чем в Envoy, но достаточно для edge-защиты. За подробностями можно глянуть здесь. В OSS нативного WAF нет. Обычно используют ModSecurity через SPOE-агент или выносят проверку во внешний сервис с правилами OWASP CRS. Это добавляет операционную сложность и накладные расходы.
NGINX — прост в эксплуатации, но без встроенной проверки JWT в OSS-версии. TLS 1.3 и mTLS поддерживаются через стандартный ssl-модуль. JWT, OIDC и сложные RBAC-сценарии реализуют через внешний авторизатор (чаще всего oauth2-proxy) с auth_request или через Lua/OpenResty. Zero Trust возможен, но интеграция со SPIFFE/SPIRE полностью ручная
server { listen 443 ssl; ssl_certificate /etc/nginx/ssl/server.crt; ssl_certificate_key /etc/nginx/ssl/server.key; ssl_client_certificate /etc/nginx/ssl/ca.pem; ssl_verify_client on; location / { auth_request /oauth2/auth; error_page 401 = /oauth2/sign_in; proxy_set_header X-Auth-Request-User $upstream_http_x_auth_request_user; proxy_set_header X-Auth-Request-Email $upstream_http_x_auth_request_email; proxy_pass http://backend; } location = /oauth2/auth { internal; proxy_pass http://oauth2-proxy; } location /oauth2/ { proxy_pass http://oauth2-proxy; } }
Прост в эксплуатации, надёжен, но без внешнего авторизатора сложные политики реализовать трудно. В OSS встроенного WAF нет. Чаще всего применяют libmodsecurity + модуль ModSecurity-nginx с профилем OWASP CRS. Всё настраивается вручную, нужно следить за обновлением правил и тюнинговать под нагрузку.
Envoy — оптимален для Zero Trust. Поддерживает TLS 1.3, mTLS, динамическую загрузку сертификатов через SDS, встроенные фильтры JWT, RBAC и OAuth2. Интеграция со SPIFFE/SPIRE нативная, сертификаты обновляются автоматически. OAuth2/OIDC решают через внешний IdP совместно с jwt_authn или ext_authz
Очень большой конфиг (danger)
static_resources: listeners: - name: main_listener address: socket_address: address: 0.0.0.0 port_value: 8080 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 stat_prefix: ingress_http route_config: name: local_route virtual_hosts: - name: local_service domains: ["*"] routes: - match: prefix: "/" route: cluster: service_cluster http_filters: - name: envoy.filters.http.jwt_authn typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication providers: example_provider: issuer: https://example.com audiences: - api.example.com remote_jwks: http_uri: uri: https://example.com/.well-known/jwks.json cluster: jwks_cluster timeout: 1s cache_duration: seconds: 300 - name: envoy.filters.http.rbac typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC rules: policies: "require-valid-jwt": permissions: - any: true principals: - metadata: filter: envoy.filters.http.jwt_authn path: - key: example_provider value: string_match: exact: verified - name: envoy.filters.http.router typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router clusters: - name: service_cluster connect_timeout: 0.25s type: STATIC lb_policy: ROUND_ROBIN load_assignment: cluster_name: service_cluster endpoints: - lb_endpoints: - endpoint: address: socket_address: address: 127.0.0.1 port_value: 8081 - name: jwks_cluster connect_timeout: 5s type: LOGICAL_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: jwks_cluster endpoints: - lb_endpoints: - endpoint: address: socket_address: address: example.com port_value: 443 transport_socket: name: envoy.transport_sockets.tls typed_config: "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext sni: example.com
JWT проверяется через jwt_authn filter с поддержкой локальных и удалённых JWKS, а RBAC-фильтр позволяет описывать allow/deny-политику прямо в конфигурации. WAF: встроенного решения нет. Используют WASM-фильтры на базе Coraza (правила совместимы с OWASP CRS) или интегрируют Curiefense как Envoy-фильтр. Подход открытый, но требует тестов под прод-нагрузкой и ручной настройки правил.
HAProxy — шифрует быстро, JWT/OAuth2 проверяет нативно с версии 2.5 без лишних зависимостей. TLS 1.3 и mTLS на фронтендах, ACL и переменные для RBAC. Zero Trust через SPIFFE/SPIRE возможен, но придётся дописывать собственный «клей» для ротации ключей и распределения SVID. Хорош, когда важны скорость и простые политики без лишней магии.
NGINX — настраивается так же легко, как добавить новый upstream. TLS 1.3 и mTLS встроены, но JWT и OIDC в OSS нет — всё решается через внешний авторизатор (чаще oauth2-proxy). Сложный RBAC потребует вынесенной логики. Zero Trust сценарии возможны, но интеграция полностью ручная. Конфиг прост, но возможностей меньше, чем у HAProxy и Envoy.
Envoy — Zero Trust в чистом виде. TLS 1.3, mTLS, SDS для динамики ключей, встроенные фильтры JWT, RBAC, OAuth2. SPIFFE/SPIRE подключается нативно, сертификаты обновляются сами. Можно строить политику хоть под микроскопом, без лишних костылей.
Вывод:
-
HAProxy — быстрый минимализм: простые ACL и встроенная JWT/OAuth2-проверка.
-
NGINX — надёжен и понятен, но для сложных политик нужны внешние сервисы.
-
Envoy — всё для Zero Trust из коробки: TLS, mTLS, JWT, RBAC, OAuth2, SDS и SPIFFE.
Переменные
HAProxy — переменные бывают как коты в коробке Шрёдингера: живут то в транзакции (txn.*), то в сессии (sess.*), то в запросе (req.*) или ответе (res.*). Можно хранить что угодно: IP, токены, промежуточные флаги. Эти переменные легко помещать в ACL, логи или Lua.
frontend fe_https bind :443 ssl crt /etc/haproxy/certs/site.pem alpn h2,http/1.1 mode http http-request set-var(txn.client_ip) src http-request set-header X-Client-IP %[var(txn.client_ip)] default_backend be_api backend be_api mode http http-request set-var(sess.backend_name) be_api server s1 10.0.0.31:443 ssl verify none server s2 10.0.0.32:443 ssl verify none
Здесь IP клиента записывается в переменную транзакции и сразу же уезжает в заголовок. Переменная sess.backend_name живёт дольше — пока TCP-сессия не умрёт. Плюсы: быстрые ACL, всё можно хранить прямо в прокси. Минусы: сложные сценарии часто требуют Lua — готовься писать код прямо в конфиге, как в 2000-х.
NGINX — переменные как студенты на паре: появляются только на время запроса, потом испаряются. Есть встроенные ($host, $remote_addr) и те, что задаёшь руками через set. Между запросами память не держат — хочешь что-то протащить дальше, прокидывай заголовки или ставь cookie.
server { listen 443 ssl http2; ssl_certificate /etc/nginx/certs/site.pem; ssl_certificate_key /etc/nginx/certs/site.key; set $client_ip $remote_addr; location / { proxy_set_header X-Client-IP $client_ip; proxy_pass https://backend_pool; } } upstream backend_pool { least_conn; server 10.0.0.31:443; server 10.0.0.32:443; }
Переменная $client_ip живёт ровно один запрос, как батарейка в китайском фонарике. Плюсы: просто, стабильно, никаких утечек состояния. Минусы: никакой «долгой памяти», всё руками через заголовки.
Envoy — вместо переменных тут подстановки и метаданные. %DOWNSTREAM_REMOTE_ADDRESS%, %REQ(:authority)%, %UPSTREAM_HOST% и десятки других. Можно хранить временные данные прямо внутри фильтров — хочешь память на уровень TCP, хочешь на уровень HTTP-запроса.
static_resources: listeners: - name: https_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 stat_prefix: ingress route_config: virtual_hosts: - name: api domains: ["*"] routes: - match: { prefix: "/" } route: cluster: api_pool request_headers_to_add: - header: key: X-Client-IP value: "%DOWNSTREAM_REMOTE_ADDRESS%" http_filters: - name: envoy.filters.http.router
Envoy сам добавляет X-Client-IP из встроенной подстановки. А если захочешь хранить ещё что-то — используй Dynamic Metadata, и у тебя будет «карманная база данных внутри прокси». Плюсы: гибкость и фильтры как LEGO. Минусы: сложность конфига, настраивать без кофе опасно.
Вывод:
-
HAProxy — переменные на любой вкус и срок жизни, но Lua придётся учить.
-
NGINX — переменные как одноразовые стаканчики: удобно, но без повторного использования.
-
Envoy — это как владение магией: невероятная мощь, но один неверный символ в метаданных или переменной и ты уже не повелеваешь трафиком, а тушишь фаервол.
Таймауты

HAProxy таймаутов столько, что можно потеряться. Таймауты обычно указывают в секциях defaults/frontend/backend. Главные параметры: timeout connect (тайм-аут на установку TCP), timeout client (неактивность со стороны клиента), timeout server (неактивность со стороны бэкенда), timeout http-request (время ожидания заголовков запроса), timeout http-keep-alive и timeout tunnel для туннелей/вебсокетов. Таймауты часто задаются явно в defaults; без корректных значений соединения могут «залипать» или падать. Каждый отвечает за свой этап: соединение, запрос, очередь, туннель. Можно гибко настроить поведение и на L4, и на L7.
defaults timeout connect 5s timeout client 1m timeout server 1m timeout http-request 10s timeout queue 15s timeout http-keep-alive 15s timeout tunnel 1h
Соединение к backend ждём 5 секунд, клиентский запрос и ответ сервера — по 1 минуте, запросы на уровне HTTP проверяем 10 секунд, очередь не держим дольше 15 секунд. Плюсы: полный контроль, можно «настроить до болтика». Минусы: легко запутаться, если лепить всё подряд, конфиг превращается в «таймаутное болото».
NGINX даёт набор директив для клиента и прокси. Ключевые: client_header_timeout, client_body_timeout — время чтения заголовков/тела от клиента; keepalive_timeout — время удержания keep-alive; proxy_connect_timeout, proxy_read_timeout, proxy_send_timeout — поведение при proxy_pass: подключение, чтение ответа и отправка запроса учитываются между последовательными операциями, а не за весь ответ. То есть proxy_read_timeout измеряет паузу между чтениями, а не общий срок передачи.
http { client_header_timeout 60s; client_body_timeout 60s; keepalive_timeout 75s; proxy_connect_timeout 10s; proxy_read_timeout 300s; # увеличить для долгих ответов/streaming proxy_send_timeout 300s; }
Для потоковых gRPC/HTTP2 соединений нужно поднять proxy_*_timeout и следить за http2_recv_timeout/настройками upstream. Минусы: меньше гранулярности, чем у HAProxy, например, нет отдельного таймаута очереди или туннеля — всё «оптом».
Envoy оперирует таймаутами на уровне connection manager, route и cluster. Важные пункты: route timeout по умолчанию 15s (это время ожидания полного upstream-ответа; стартует после получения всего downstream-запроса), stream_idle_timeout у HTTP connection manager по умолчанию 5 минут (закрывает бездействующие стримы), cluster.connect_timeout/transport_socket_connect_timeout по умолчанию ~5s. Таймауты можно задавать глобально и на уровне маршрута; per-route timeout: 0s отключает таймаут маршрута. Для стриминговых API нужно отключать/увеличивать route/stream таймауты.
http_connection_manager: stream_idle_timeout: 300s # обычно 5m по умолчанию route_config: virtual_hosts: - name: default domains: ["*"] routes: - match: { prefix: "/" } route: cluster: service_a timeout: 0s # отключить route timeout для стриминга idle_timeout: 0s # отключить per-route idle
В Envoy поведение таймаутов сложнее из-за retry/retry-timeout и взаимодействия connection-manager ↔ route ↔ cluster. Тестируйте сценарии с retry и streaming. Плюсы: гибкость, можно делать очень тонкие сценарии. Минусы: конфиг превращается в «головоломку на скорость», без документации самому себе — через неделю не разберёшься.
HAProxy — с таймаутами как со швейцарскими часами. Всё задаётся явно: connect, client, server, http-request, keep-alive, tunnel. Хочешь стриминг — поставь timeout tunnel и забудь. Минимум магии, максимум контроля. Если соединение зависло — это твоя вина, а не сюрприз в дефолтах.
NGINX — таймауты как бытовая техника. Client, proxy, keepalive — всё просто и понятно, но надо помнить, что proxy_read_timeout меряет паузы между байтами, а не всю загрузку. Для долгих gRPC или стриминга таймауты придётся крутить руками. Работает надёжно, если не забыть про все ручки.
Envoy — таймауты как корпоративные дедлайны. По умолчанию route timeout 15 секунд, stream_idle_timeout 5 минут — и Envoy безжалостно разрывает стриминг, если не объяснить, что проект ещё не готов. Гибкости море: можно отключить таймауты, настроить idle отдельно на route и cluster, но придётся думать, что именно отключать.
Вывод:
-
HAProxy — максимальная детализация: можно настроить всё, даже «таймаут на таймаут». Но легко перестараться.
-
NGINX — базовый набор: быстро, понятно, но без редких «спецрежимов».
-
Envoy — конструктор с любыми таймаутами на любой уровень, но нужно помнить, что гибкость требует мозгов и кофе.
Kubernetes-интеграция

HAProxy. Есть два OSS-варианта:
-
haproxytech/kubernetes-ingress — классический Ingress с аннотациями, поддержка Gateway API флагом. Метрики для Prometheus из коробки. Плавная миграция на новые API.
-
jcmoraisjr/haproxy-ingress — тоже Ingress v1, частичная поддержка Gateway API (HTTPRoute/Gateway в v0.14). Подойдёт, если ближе его модель конфигурации.
Минимум магии, максимум контроля. Если что-то не работает — виноваты не «умные дефолты», а конкретные настройки.
NGINX Ingress Controller — фокус на Ingress v1. TLS/mTLS через аннотации (auth-tls-*), внешняя авторизация (auth-url), глобальные параметры через ConfigMap. Всё просто, но за сложные сценарии придётся платить количеством аннотаций. Работает стабильно, но «особые режимы» придётся включать руками.
Envoy Gateway (OSS) — нативный Gateway API, расширения SecurityPolicy для mTLS/JWT и авторизации по клеймам, метрики Prometheus.
Contour — управляет Envoy, поддерживает Gateway API и свои CRD для HTTP/gRPC/TCP. Гибкости море: route, cluster, policy — всё настраивается. Но за каждую опцию придётся думать.
Istio — mesh как отдельный отдел. Envoy выполняет роль data plane, политика и телеметрия централизуются через контроль-плейн. Если mesh уже есть или планируется — Ingress лучше строить на Istio или Gateway API с Envoy Gateway, чтобы не плодить зоопарк контроллеров.
Кратко:
-
Хотите «всё под контролем» и минимум сюрпризов — HAProxy.
-
Хотите «просто работает» — NGINX.
-
Хотите «тонкая политика и современные API» — Envoy Gateway или Contour.
-
Хотите «единая политика на весь трафик» — Istio.
Вывод
-
HAProxy — максимальная детализация, можно настроить всё. Но легко утонуть в параметрах.
-
NGINX — базовый и понятный путь. Быстро стартовать, без редких «сюрпризов».
-
Envoy/Contour — гибкость и глубокая интеграция с Gateway API. Требует продуманной конфигурации.
-
Istio — если mesh уже в планах, Ingress лучше сразу строить на нём.
Выводы и рекомендации

Подойдёт, когда важна минимальная латентность, высокая пропускная способность и предсказуемое поведение без control-plane. Нативная сильная сторона — L4/L7 балансировка с детальной ручной настройкой (таймауты, maxconn, stick-tables, httpchk). QUIC/H3 в активной доработке — уже можно тестировать, но планируйте нагрузочные тесты.
Когда брать:
-
Edge TLS-терминация с высокими SLA по latency.
-
Большие объёмы TCP/HTTP трафика, где нужна малая задержка.
-
Нужен контроль на уровне соединений и простая схема автозамены бэкендов (seamless reload).
Операционные ограничения и подводные камни:
-
Сложные L7-политики требуют Lua или внешней логики.
-
HTTP/3 требует тестов и знаний о QUIC.
-
Нет встроенной «из коробки» распределённой трассировки — нужно подключать OTEL/агрегаторы.

Лучше для простых и стабильных веб-фронтов, кэширования и ситуаций, где конфиг «разложил и забыл». HTTP/2 зрел, HTTP/3 стабилизируется в mainline. Для WAF/JWT/metrics/tracing — внешние модули или Plus
Когда брать:
-
Статический контент, reverse-proxy с кешем, rate-limiting на уровне edge.
-
Быстрый rollout и простая конфигурация без control-plane.
-
Нужен модульный моддинг (lua, njs) для лёгкого расширения.
Операционные ограничения и подводные камни:
-
Активные health-checks, advanced LB и enterprise-функции в основном в Plus.
-
Метрики и трассировка требуют внешних экспортеров/модулей.

Если система динамическая, микросервисная, с gRPC, канареечными релизами, shadow-трафиком и требованием к глубокой телеметрии — Envoy даёт наилучшую платформу. Требует control-plane (xDS) и автоматизации.
Когда брать:
-
Service mesh или централизованное управление L7-политиками.
-
gRPC-first архитектуры и обширная трассировка/OTel.
-
Нужно гибкое управление канареями, retry/circuit-breaker, rate-limit через внешние сервисы.
Операционные ограничения и подводные камни:
-
Высокая сложность конфигурации и поддержания YAML-политик.
-
Больше CPU/RAM на экземплах по сравнению с NGINX/HAProxy.
-
Требует CI/CD для xDS/контроль-плейна и хорошей наблюдаемости.
Итого по синтетическому разбору:
-
Низкая латентность и грубая производительность L4/L7 → HAProxy
-
Простой веб-фронт, кеш, статический контент, минимум динамики → NGINX
-
Динамичная среда, gRPC, mesh, канареи/трафик-менеджмент → Envoy
Практические советы по внедрению
-
Test-first: для HTTP/3 и QUIC задействуйте этап нагрузочного теста и SLO-валидацию
-
Observability: если выбираете Envoy — проектируйте OTEL-пайплайн сразу. Для HAProxy/NGINX готовьте Prometheus → OTEL/Jaeger интеграцию
-
K8s: для простого ingress — ingress-nginx или haproxy-ingress; для политики и mesh — Envoy Gateway/Contour/Istio. Планируйте единый control-plane, чтобы не плодить контроллеров.
-
Ресурсы: закладывайте больше CPU/RAM под Envoy и автоматизацию для его конфигураций.
NGINX — как велосипед с корзинкой: завёл и забыл, но далеко не уедешь. HAProxy — как надёжный жигуль: предсказуемо, стабильно, но без наворотов. Envoy — как BMW: летит быстро и уверенно, но вечно приходится ковыряться под капотом. Всё едет, вопрос только — куда и с какой скоростью.
Кто победил?
По сумме плюсов — Envoy, остаётся лучшим выбором для динамических, telemetry-heavy и gRPC-проектов. HAProxy держит первенство по сырой производительности и простоте L4/L7 в высоких нагрузках. NGINX остаётся лучшим выбором для традиционных web/edge сценариев с простым управлением и кешированием.
P.S. Если я в процессе написания статьи где-то что-то упустил, ошибся в конфиге или описании — присылайте исправления в личку, чтобы я мог внести их в статью.
ссылка на оригинал статьи https://habr.com/ru/articles/937898/
Добавить комментарий