mTLS — привычная мера защиты внутренних API, админских кабинетов и технических эндпоинтов, когда пароль уже не считается достаточным барьером. На стороне Apache это реализуется модулем mod_ssl и директивой SSLVerifyClient, а также проверкой статуса сертификата через CRL и/или OCSP. В статье разберем минимальную рабочую конфигурацию, контроль доступа по атрибутам сертификата, способы онлайн-проверки статуса (CRL/OCSP), и главное — как организовать удобную ротацию клиентских сертификатов и доверенных CA, чтобы не сорвать работу продакшна. Если у вас ещё нет серверного сертификата для vhost, оформите его через SSL-сертификаты.
Зачем mTLS именно в Apache
Чаще всего Apache в вашей схеме — это reverse-proxy перед приложением или единственная фронтенд-точка для административных интерфейсов. Делегирование TLS-политик на веб-сервер дает повторно используемую, централизованную настройку, понятные логи и привычный operational-процесс: обновления ключей, ротации CRL, метрики, алерты и плавные reload’ы без простоя.
Идея проста: клиент доказывает подлинность сертификатом, сервер проверяет его цепочку доверия, срок действия и статус (не отозван ли). Доступ разрешается только при корректной проверке и выполнении ваших правил авторизации.
Базовая схема mTLS в Apache
Где хранить доверенные CA и как выдавать client certs
Для mTLS не обязательно поднимать огромную PKI. Часто достаточно одного промежуточного внутреннего CA, от которого вы будете выпускать короткоживущие клиентские сертификаты. В проде удобнее хранить доверенные CA в отдельном каталоге (с хешированием) и подключать его одной директивой, чтобы ротация делалась заменой файлов и graceful reload. Короткие сроки жизни client certs снижают риски, а также упрощают отзыв — меньше значение CRL/OCSP при систематической переэмиссии.
Перед стартом убедитесь, что включены нужные модули:
# Debian/Ubuntu
sudo a2enmod ssl
sudo a2enmod headers
sudo a2enmod proxy proxy_http
sudo systemctl reload apache2
# RHEL/CentOS/AlmaLinux
sudo apachectl -M | grep -E "ssl|proxy|headers"
sudo systemctl reload httpd
Минимальная конфигурация mod_ssl для mTLS
Простейший виртуальный хост с обязательной проверкой клиентского сертификата может выглядеть так:
<VirtualHost *:443>
ServerName api.internal.example
SSLEngine on
SSLCertificateFile /etc/pki/tls/certs/server.crt
SSLCertificateKeyFile /etc/pki/tls/private/server.key
SSLCertificateChainFile /etc/pki/tls/certs/chain.crt
# Доверенные CA для проверки клиентских сертификатов
SSLCACertificatePath /etc/pki/clients/ca.d
SSLVerifyClient require
SSLVerifyDepth 3
# Экспорт полей сертификата в переменные окружения и логи
SSLOptions +ExportCertData +StdEnvVars
# Пример апстрима
ProxyPass / http://127.0.0.1:8080/
ProxyPassReverse / http://127.0.0.1:8080/
</VirtualHost>
Директивы SSLCACertificatePath и SSLVerifyClient — критические: первая говорит Apache, каким CA доверять при проверке клиентского сертификата, вторая включает саму проверку. SSLVerifyDepth задает максимальную глубину цепочки. Флаг +ExportCertData добавляет в окружение переменные с деталями сертификата, что удобно для авторизации и логирования.

Авторизация поверх mTLS: от обязательной проверки до тонкой фильтрации
Режимы SSLVerifyClient
SSLVerifyClient принимает режимы none, optional, require, optional_no_ca. Для строгого mTLS обычно используют require на уровне всего vhost или только на приватных путях. Если есть «публичные» и «приватные» эндпоинты в рамках одного хоста, разумно поставить optional глобально, а require — в нужном Location. Тогда клиентский сертификат будет запрошен всегда, но обязателен только там, где это действительно нужно.
Правила доступа через Require expr
Одного факта корректного сертификата часто недостаточно. Можно фильтровать по издателю, организации или именам в DN — через выражения авторизации. Пример: разрешим доступ только сертификатам, выданным нашим внутренним CA, и с конкретным OU.
<Location /admin/api/>
SSLVerifyClient require
Require expr %{SSL:SSL_CLIENT_I_DN_O} == "MyOrg-CA" && %{SSL:SSL_CLIENT_S_DN_OU} == "PlatformTeam"
</Location>
Доступные ключи начинаются с SSL:, например SSL_CLIENT_S_DN (Subject DN), SSL_CLIENT_I_DN (Issuer DN), SSL_CLIENT_VERIFY (итог проверки), SSL_CLIENT_M_SERIAL (серийный номер). Этого хватает, чтобы строить понятные правила без доработок приложения.
Нюанс: CN в современных политиках считается устаревающим полем для идентификации. Если возможно, переходите на SAN и/или более строгие OU/политику атрибутов.
CRL: офлайн-проверка отзыва клиентских сертификатов
CRL по-прежнему остается надежной опцией, особенно во внутренних контурах без гарантированного доступа к OCSP. Две базовые схемы: подключить единый файл CRL или каталог с несколькими CRL (по разным CA). Каталог удобнее при ротации и масштабировании.
# Вариант с общим файлом CRL
SSLCARevocationFile /etc/pki/clients/crl/all.crl
SSLCARevocationCheck chain
# Вариант с каталогом CRL (хешированные файлы)
SSLCARevocationPath /etc/pki/clients/crl.d
SSLCARevocationCheck chain
Директива SSLCARevocationCheck контролирует глубину проверки статуса: chain проверяет всю цепочку, leaf — только конечный клиентский сертификат. Каталог SSLCARevocationPath должен быть «проиндексирован» утилитой хеширования (например, c_rehash из OpenSSL), после чего Apache найдет нужный CRL по хешу.
Ротация CRL без простоя выглядит так: вы загружаете новый CRL-файл рядом, обновляете симлинк или имя файла-версии, прогоняете c_rehash для каталога, затем выполняете graceful reload, чтобы Apache подхватил изменения без обрыва активных соединений.
# Пример процедурно
install -m 0644 new.crl /etc/pki/clients/crl.d/abcd1234.r0
c_rehash /etc/pki/clients/crl.d
apachectl graceful # или systemctl reload httpd
Важно следить за сроком действия CRL. Истекший CRL по сути превращает проверку в «непонятно что». Поставьте алертинг и регламент обновления: например, обновлять CRL раз в сутки/неделю и мониторить TTL.
OCSP: онлайн-статус клиентских сертификатов
OCSP позволяет проверять статус сертификата онлайн. Для клиентских сертификатов в Apache это настраивается отдельным набором директив. Типичный сценарий — включить OCSP, задать дефолтный респондер и аккуратно отстроить таймауты, чтобы не получить подвисания запросов.
# Включение OCSP-проверок клиентских сертификатов
SSLOCSPEnable on
# Явный респондер (если не хотите полагаться на AIA в сертификатах)
SSLOCSPDefaultResponder http://ocsp.responder.internal
SSLOCSPOverrideResponder on
# Верифицировать ответчика (рекомендуется)
SSLOCSPNoVerify off
# Таймауты и сдвиг времени (скью)
SSLOCSPResponderTimeout 3
SSLOCSPResponseTimeSkew 300
# При ошибке OCSP (недоступен/таймаут) — лучше фейлить доступ
# чтобы не пропустить отозванный сертификат
# В старых версиях может управляться логикой Require expr
# через %{SSL:SSL_CLIENT_VERIFY} и дополнительные флаги состояния.
Выбор политики «fail-closed» или «fail-open» при недоступности OCSP — это риск-менеджмент. Для критичных зон предпочтительнее «fail-closed», но не забудьте о резервном респондере и мониторинге. Если вы держите собственный OCSP/CRL-инфраструктурный узел, удобно размещать его на изолированном VDS с лаконичной конфигурацией firewall и резервированием.
OCSP для клиентских сертификатов — отдельная история от OCSP Stapling для серверного сертификата. Их часто путают. Stapling оптимизирует проверку статуса вашего серверного сертификата клиентами; SSLOCSP-настройки — про проверку статуса клиентских сертификатов, которыми аутентифицируются к вашему Apache.
Удобная ротация клиентских сертификатов и доверенных CA
Основной operational-вопрос: как менять корни доверия и пересоздавать клиентские сертификаты без «ночного приключения» и с минимумом ручных правок конфигов.
Паттерн 1: отдельный промежуточный CA для клиентов
Выпускайте клиентские сертификаты от выделенного intermediate CA. В Apache в SSLCACertificatePath держите только те CA, которые соответствуют активным «поколениям». Когда приходит время ротации, добавляете новый CA рядом (каталог, симлинк, rehash), делаете graceful reload, начинаете выпускать новые клиентские сертификаты. Через разумный grace-период убираете старый CA из каталога и снова graceful reload. Пользователи, не успевшие обновить сертификат, потеряют доступ — что и ожидаемо после дедлайна.
Паттерн 2: общий bundle-файл и атомарная замена
Если используете SSLCACertificateFile, собирайте bundle с одной или несколькими цепочками CA. Обновление — это запись нового файла рядом и атомарный mv на место старого, затем graceful reload. Такой подход удобен в конфигурационных менеджерах: файл — артефакт, reload — хук.
Паттерн 3: короткие TTL клиентских сертификатов
Короткая жизнь клиентских сертификатов (например, 30–90 дней) снижает ценность компрометации ключа и упрощает функцию «отзыва»: вы и так регулярно переэмитируете. Вместе с этим можно оставить CRL/OCSP в режиме «страховки», чтобы мгновенно блокировать украденные ключи.
Паттерн 4: CRL- и OCSP-регламенты
Для CRL задайте четкий SOP: как часто обновляем, откуда берем, как мониторим сроки. Для OCSP — таймауты, резервные респондеры, политика при сбое. Не забывайте о журналировании и тестах (см. ниже), чтобы заранее ловить проблемы.

Логирование и отладка
Подробные логи — ключ к спокойной эксплуатации. В Apache можно добавить поля окружения с атрибутами клиентского сертификата в формат логов доступа, чтобы быстро видеть, кто и с чем ходит.
LogFormat "%h %l %u %t \"%r\" %>s %b %{SSL_PROTOCOL}x %{SSL_CIPHER}x %{SSL_CLIENT_S_DN_CN}x %{SSL_CLIENT_I_DN_O}x %{SSL_CLIENT_VERIFY}x" mtls
CustomLog logs/access_mtls.log mtls
Теперь в логе будет видно CN клиента, издателя и результат верификации (SUCCESS или причина отказа). Для живой проверки используйте openssl s_client — он покажет детали рукопожатия, цепочки и статус проверки.
# Проверка подключения с клиентским сертификатом
openssl s_client -connect api.internal.example:443 -cert client.crt -key client.key -showcerts -verify_return_error
# Проверка конкретной версии TLS (при необходимости)
openssl s_client -connect api.internal.example:443 -tls1_2 -cert client.crt -key client.key -verify_return_error
Если авторизация завязана на атрибуты DN, не поленитесь вывести их в ответ или логи приложений на время отладки. Также полезно передать сертификат в приложение заголовком (только во внутреннем периметре и с осторожностью):
RequestHeader set X-SSL-Client-Verify %{SSL_CLIENT_VERIFY}s
RequestHeader set X-SSL-Client-DN %{SSL_CLIENT_S_DN}s
# Опционально полный PEM сертификата (многострочный)
RequestHeader set X-SSL-Client-Cert %{SSL_CLIENT_CERT}s
Производительность и надежность
mTLS добавляет вычислительную нагрузку (рукопожатие, верификация цепочки и статуса). Внутренние сервисы обычно справляются, но несколько приемов снижают стоимость:
- Используйте TLS 1.3 и современные шифры.
- Следите за keep-alive и переиспользованием соединений на клиенте.
- Не перегружайте OCSP/CRL: держите локальные зеркала, короткие таймауты и мониторинг; при CRL — полноценный регламент обновлений.
- На высоких нагрузках проверьте MPM event и разумные лимиты для рукопожатий/воркеров.
Поверх TLS не забудьте включить базовые заголовки безопасности — разбор и готовые примеры см. в статье «HTTP Security Headers для Nginx и Apache». Для кэшируемых эндпоинтов может пригодиться «Cache-Control и ETag для статики».
С точки зрения надежности ключевой процесс — graceful reload: Apache перечитывает конфиги и ключевые файлы без обрыва существующих соединений. Это идеально подходит для разворачивания новых CA, CRL и изменения правил Require expr.
Типовые ошибки и как их избежать
- Случайно поставить
optionalвместоrequireна приватном эндпоинте. Проверяйте через интеграционные тесты. - Забытый rehash каталога CA/CRL. Без корректных ссылок Apache не сможет найти цепочку/CRL.
- Истекший CRL. Поставьте алерты по срокам, публикуйте CRL регулярно.
- Неправильная политика при сбое OCSP. Явно выберите «fail-closed» или «fail-open», протестируйте недоступность респондера.
- Доверие к заголовкам, пришедшим снаружи. Все клиентские данные о сертификате формируйте только на фронтовом сервере.
- Слишком плотные правила
Require expr(жесткая привязка к CN). Лучше опираться на стабильные атрибуты и/или SAN.
Чек-лист внедрения mTLS в Apache
- Определите область применения: весь vhost или только приватные пути.
- Подготовьте CA для клиентских сертификатов (лучше отдельный intermediate).
- Выберите модель статуса: CRL, OCSP или обе.
- Реализуйте хранение CA/CRL как каталог с rehash и регламентом обновлений.
- Включите
SSLVerifyClient, задайтеSSLVerifyDepth,SSLCACertificatePathили файл. - Добавьте правила
Require exprдля авторизации по атрибутам. - Настройте логирование полей сертификата, соберите метрики и алерты по срокам CRL.
- Пропишите процедуру ротации: атомарные обновления файлов и graceful reload.
- Покройте интеграционными тестами «чистый» клиент, просроченный, отозванный, сертификат из неподдерживаемого CA и отсутствие клиентского сертификата.
Итог: mTLS на Apache — это несложно, если сразу подумать о ротации и операционном сопровождении. Разделите доверие по каталогам, используйте Require expr для прозрачных правил доступа, определитесь с CRL/OCSP-политикой и держите автоматические проверки. Тогда замена ключей и CA превратится из «большого запуска» в обычный регламентный процесс без простоя.


