Перейти к содержанию

Техническая глубина

Укрепление Tomcat против DDoS: пулы потоков Connector, тайм-ауты и фронтовый слой

Обновлено: август 2026 г. · Пул Connector, тайм-ауты и обязательный фронтовый слой · Время чтения ~18 мин

Фиксированный пул бирюзовых рабочих потоков, взятых из ограниченного резервуара, с короткой очередью на краю; когда пул и очередь заполнены, новые прибывающие получают отказ, а не топят пул.

Уязвимость Tomcat к DDoS живёт в его Connector. maxThreads, acceptCount, maxConnections и connectionTimeout — несущие настройки, а протокол NIO не даёт медленному соединению стоить целого потока. Но главное решение архитектурное: голый Tomcat на порту 8080, открытый в интернет, уязвим так, как не исправит ни одно значение Connector. Поэтому сначала идёт фронтовый слой.

Это руководство укрепляет сам Apache Tomcat против части DDoS-атаки, исчерпывающей соединения и потоки. От руководств по веб-серверам оно отличается одним структурным моментом, который надо назвать первым: Tomcat — сервер приложений, и стандартный производственный проект не выставляет его напрямую. Перед ним стоит укреплённый веб-сервер или балансировщик нагрузки, завершает соединение и передаёт чистые запросы по частной сети. Большая часть защит этой серии живёт в том фронтовом слое.

Поэтому настройка Connector ниже — вторая линия, применяемая за фронтовым слоем. Она важна, потому что фронтовый слой может отказать, быть обойдён во внутренней сети или передать атаку, которую не поймал. Но она не замена укреплённому слою впереди. Каждая настройка идёт со своим значением и счётчиком JMX или журнала, который показывает её работу.

Формы атак и настройки Tomcat, которые им отвечают
УстройствоФорма атакиНастройка TomcatПроверка
Медленный заголовок / тело (Slowloris)connectionTimeout; протокол NIO; keepAliveTimeoutManager: потоки в состоянии R; %D в журнале доступа
Поток соединенийmaxConnections; acceptCount (backlog ОС)jmx Connector connectionCount
Исчерпание пула потоковmaxThreads; общий Executor между коннекторамиjmx ThreadPool currentThreadsBusy
Злоупотребление большими запросамиmaxHttpHeaderSize, maxParameterCount, maxPostSizeДоля 400 в журнале доступа
Прямой выход в интернетНе выставляйте 8080; фронтовый слой завершает первымnetstat: 8080 привязан только к localhost/внутреннему

Последняя строка переосмысливает остальные: Tomcat — сервер приложений, и самый защищаемый стандартный проект ставит перед ним укреплённый веб-сервер или прокси. Настройка Connector ниже предполагает, что этот фронтовый слой существует.

0. Сначала базовые показатели: откройте метрики Connector

Живое состояние Tomcat — в JMX и журнале доступа. Включите журнал доступа с временем обработки и читайте пул потоков Connector в течение обычной недели:

<!-- server.xml, внутри <Host> — %D время запроса в мс, %S сессия -->
<Valve className="org.apache.catalina.valves.AccessLogValve"
       directory="logs" prefix="access." suffix=".log"
       pattern="%h %t &quot;%r&quot; %s %b %D" />
# Пул потоков и число соединений через JMX (jconsole или jmxterm без интерфейса)
# Catalina:type=ThreadPool,name="http-nio-8080"  -> currentThreadsBusy, connectionCount
# Самые медленные запросы из журнала доступа (последнее поле %D, мс)
awk '{print $NF, $0}' logs/access.*.log | sort -rn | head

# Число запросов на клиента, обычная неделя
awk '{print $1}' logs/access.*.log | sort | uniq -c | sort -rn | head

currentThreadsBusy против maxThreads и connectionCount против maxConnections — две базовые величины, относительно которых задаётся каждое значение ниже.

1. Сначала идёт фронтовый слой

Прежде чем трогать server.xml, убедитесь, что Tomcat не выставлен напрямую:

# HTTP Connector должен слушать localhost или внутренний адрес, никогда 0.0.0.0 на публичном хосте
ss -ltnp | grep 8080

Если 8080 привязан к публичному интерфейсу, это первое, что надо исправить. Привяжите его к внутреннему адресу, который использует фронтовый слой, и пусть nginx или Apache httpd завершает соединение из интернета. Эти фронтовые слои укреплены в руководствах nginx и Apache; они несут TLS, вытеснение медленных соединений и ограничение частоты куда лучше, чем Connector.

<!-- server.xml — привязать только к внутреннему интерфейсу -->
<Connector port="8080" address="10.0.0.5"
           protocol="org.apache.coyote.http11.Http11NioProtocol"
           ... />

2. Протокол Connector: NIO, а не блокирующий

Атрибут protocol решает, стоит ли медленное соединение потока. Используйте коннектор NIO; старый блокирующий коннектор тратил поток на соединение и делал Slowloris дешёвым:

<Connector port="8080" address="10.0.0.5"
           protocol="org.apache.coyote.http11.Http11NioProtocol"
           connectionTimeout="20000"
           maxThreads="400"
           minSpareThreads="25"
           maxConnections="8192"
           acceptCount="200"
           maxKeepAliveRequests="100"
           keepAliveTimeout="15000" />
# Подтвердить работающий протокол
grep -i 'protocol=' conf/server.xml
# В журнале при старте: "Initializing ProtocolHandler [http-nio-8080]"
grep -i 'ProtocolHandler' logs/catalina.out | tail

3. Три предела соединений, размеряемые вместе

maxConnections, acceptCount и maxThreads — три предела по очереди: удерживаемые соединения, соединения в backlog, обрабатываемые запросы. Размеряйте их как набор:

maxConnections  = 8192   # соединений принято и удерживается одновременно
acceptCount     = 200    # backlog ОС при заполнении maxConnections (соответствует listen backlog)
maxThreads      = 400    # запросов обрабатывается одновременно
minSpareThreads = 25     # потоков держится тёплыми

Большой maxConnections за малым maxThreads — не защита; это длинная очередь соединений, ждущих несколько потоков, которая отказывает медленно и запутанно. Размерьте maxThreads под то, что приложение и куча реально держат под нагрузкой, затем задайте maxConnections кратно выше и acceptCount как короткий backlog, а не большой. Большой backlog лишь откладывает отказ, который защищает пул.

# Занятые потоки против потолка, вживую
# JMX: Catalina:type=ThreadPool,name="http-nio-8080" currentThreadsBusy / maxThreads
# Когда busy == maxThreads и connectionCount растёт, узкое место — пул

4. Общий Executor между коннекторами

Если у сервера больше одного коннектора (HTTP и HTTPS), общий Executor ограничивает суммарные потоки между обоими вместо того, чтобы каждый коннектор держал свой пул:

<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
          maxThreads="400" minSpareThreads="25"
          maxIdleTime="60000" prestartminSpareThreads="true" />

<Connector executor="tomcatThreadPool" port="8080" ... />
<Connector executor="tomcatThreadPool" port="8443" ... />

Это не даёт потоку на одном коннекторе морить голодом другой и даёт одно число для размера под кучу.

5. Тайм-ауты и пределы размера запроса

connectionTimeout — подстраховка против Slowloris; пределы размера закрывают поверхность больших запросов:

<Connector ...
   connectionTimeout="20000"       <!-- мс на приём строки запроса + заголовков -->
   keepAliveTimeout="15000"        <!-- мс простоя до закрытия keep-alive -->
   maxKeepAliveRequests="100"
   maxHttpHeaderSize="8192"        <!-- байт -->
   maxParameterCount="1000"        <!-- параметров запроса; ограничивает атаку хеш-коллизиями -->
   maxPostSize="2097152"           <!-- тело запроса 2 МБ -->
   maxSwallowSize="2097152" />

maxParameterCount стоит задать явно: неограниченное число параметров — дешёвый способ сжечь процессор на разборе запроса. Отрицательное значение означает без ограничения, что неверно по умолчанию для сервера рядом с интернетом даже за прокси.

# Слишком большие/отклонённые запросы появляются как 400 в журнале доступа
awk '$4==400' logs/access.*.log | wc -l

6. IP клиента за фронтовым слоем

При наличии фронтового слоя каждая запись журнала доступа и каждое правило RemoteAddrValve видят прокси, пока не настроен RemoteIpValve:

<!-- server.xml, внутри <Host> -->
<Valve className="org.apache.catalina.valves.RemoteIpValve"
       remoteIpHeader="X-Forwarded-For"
       protocolHeader="X-Forwarded-Proto"
       internalProxies="10\.\d+\.\d+\.\d+|172\.1[6-9]\.\d+\.\d+" />

Без него журналы приписывают каждый запрос фронтовому слою, и любое правило по адресу становится бессмысленным — повторяющийся сбой прокси-IP по всей этой серии.

7. Необязательно: StuckThreadDetectionValve

Под атакой, толкающей приложение в медленные или зависшие запросы, клапан застрявших потоков записывает потоки, превысившие порог, превращая невидимую утечку потоков в оповещение:

<Valve className="org.apache.catalina.valves.StuckThreadDetectionValve"
       threshold="60" />

Сигналы для подключения к мониторингу

# 1. Занятые потоки / maxThreads (JMX currentThreadsBusy) — насыщение пула
# 2. connectionCount / maxConnections (JMX) — атака удержанием соединений
# 3. Доля 400 — поток больших/повреждённых запросов
awk '$4==400' logs/access.*.log | wc -l
# 4. Медленные запросы — растёт столбец %D
awk '{print $NF}' logs/access.*.log | sort -rn | head -1

currentThreadsBusy на maxThreads при растущем connectionCount — признак того, что частота запросов переросла пул. Это точка, в которой фронтовый слой или вышестоящий уровень должен поглотить то, что не может Tomcat.

Честный предел укрепления Tomcat

Всё здесь защищает уровень приложения от исчерпания соединений и пула потоков за фронтовым слоем, который должен ловить большую часть первым. Два случая лежат вне этого.

Первый — насыщение канала: объём, заполняющий трубу, не доходит ни до фронтового слоя, ни до Tomcat.

Граница, за которой уровень на площадке уже не помогает Ваш канал доступа 10 Гбит/с Канал уже забит — только вышестоящий уровень Устройство на площадке подавляет 2 Гбит/с 8 Гбит/с 25 Гбит/с 120 Гбит/с 1 Тбит/с+ Объём атаки (логарифмическая шкала)
Ниже канала у пределов Connector есть доля; выше него ни одно значение server.xml ничего не значит.

Второй — всё под Tomcat и его фронтовым слоем: TCP-стек ОС и, на фронтовом хосте, его собственное укрепление, разобранные в руководствах Linux или Windows. Укрепление Tomcat предполагает, что и фронтовый слой, и уровень ОС на месте. Выше канала ответ — сетевой уровень.

Порядок применения

  1. Убедитесь, что Connector не привязан к публичному интерфейсу; поставьте перед ним укреплённый фронтовый слой.
  2. Запишите базовые показатели: занятые потоки, число соединений, время запросов.
  3. Переведите протокол Connector на NIO, если он ещё не на нём.
  4. Размерьте maxThreads под кучу, затем вокруг — maxConnections и короткий acceptCount.
  5. Настройте RemoteIpValve, чтобы журналы и правила видели реального клиента.
  6. Задайте connectionTimeout и пределы размера запроса.
  7. Подключите четыре сигнала к мониторингу.

Больше всего исход меняет первый шаг: следующие за ним настройки Connector — вторая линия, а вторая линия полезна лишь настолько, насколько крепка первая, за которой она стоит.

Частые вопросы

Должен ли Tomcat вообще смотреть в интернет напрямую?
Редко, и об этом стоит сказать прямо. Tomcat — сервер приложений; стандартный производственный проект завершает соединения на укреплённом фронтовом слое. Это nginx, Apache httpd или балансировщик нагрузки; он берёт на себя TLS, вытеснение медленных соединений, ограничение частоты и статический контент и передаёт чистые запросы в Tomcat по частной сети. Большая часть DDoS-защит этой серии живёт именно в том фронтовом слое. Выставить Connector 8080 напрямую в интернет — значит заново реализовать всё это внутри Connector, что он делает хуже. Укрепление Connector здесь — вторая линия за фронтовым слоем, а не его замена.
Какой протокол Connector для устойчивости: NIO, NIO2 или APR?
NIO или NIO2, а не старый блокирующий коннектор. Блокирующий коннектор BIO тратил поток на соединение на весь запрос, что делало Slowloris дешёвой атакой против него; он был удалён в Tomcat 8.5. NIO и NIO2 используют неблокирующий ввод-вывод и поллер, поэтому медленное соединение держит сокет, а не выделенный рабочий поток — то же архитектурное преимущество, что у nginx. NIO — значение по умолчанию и правильный выбор почти для всех; различия между NIO и NIO2 для этой цели незначительны. Убедитесь, что вы на одном из них, а не на устаревшей конфигурации, закреплённой за BIO.
Как связаны maxThreads, maxConnections и acceptCount?
Три предела по очереди. maxConnections — сколько соединений Tomcat примет и удержит одновременно; acceptCount — backlog уровня ОС для соединений, ждущих приёма после заполнения maxConnections; maxThreads — сколько запросов обрабатывается активно. Под потоком соединения заполняются до maxConnections, затем встают в очередь acceptCount, затем отклоняются ОС. maxThreads — потолок обработки за этим. Их важно размерять вместе: огромный maxConnections за малым maxThreads — это просто множество соединений, ждущих несколько потоков, что само по себе медленный отказ.
Как именно остановить атаку медленными запросами на Tomcat?
С помощью connectionTimeout и коннектора NIO под ним. connectionTimeout ограничивает, сколько Tomcat ждёт строку запроса и заголовки после открытия соединения; клиент Slowloris, льющий заголовки по капле, отбрасывается по тайм-ауту. Держите его в пределах низких десятков секунд. На NIO застрявшее соединение изначально дёшево, поэтому тайм-аут крайне эффективен; это связка — эквивалент client_header_timeout nginx на цикле событий в Tomcat. Если фронтовый слой есть, он должен ловить это первым, а тайм-аут Connector — подстраховка.
Откуда берётся IP клиента за прокси?
Из RemoteIpValve, который нужно настроить, иначе каждое решение по IP и каждая запись журнала доступа фиксируют адрес прокси. RemoteIpValve читает X-Forwarded-For и X-Forwarded-Proto и задаёт удалённый адрес запроса реальным клиентом, так что журналы доступа, любые правила RemoteAddrValve и само приложение видят настоящий источник. Без него фронтовый слой прячет каждого клиента за одним адресом — та же форма отказа, что и неверно настроенный real_ip в nginx.
Останавливают ли эти настройки объёмную атаку?
Нет. Если атака заполняет канал перед серверами, ни фронтовый слой, ни Tomcat не получают запросы и ни одно значение Connector не применяется. Всё здесь защищает от исчерпания соединений и пула потоков на уровне приложения. Объём выше канала — задача сетевого уровня и не решается в server.xml.

Опубликовано: август 2026 г.

Руководство обновляется по мере выхода новых моделей и условий лицензирования. Как мы сравниваем производителей