TLS_SSL
TLS/SSL
TLS (Transport Layer Security) — протокол защиты данных при передаче по сети. В HTTPS он обеспечивает шифрование, целостность данных и проверку подлинности сервера. Название SSL всё ещё употребляется, но протоколы SSL устарели; на практике используют TLS 1.2 и TLS 1.3.
HTTP → обычно TCP/80, без TLS
HTTPS → обычно TCP/443, HTTP внутри TLSTLS handshake
TLS handshake — начальный этап соединения, на котором клиент и сервер согласовывают защиту и получают сеансовые ключи.
Упрощённая последовательность:
- Клиент сообщает поддерживаемые версии TLS, алгоритмы и имя сайта через SNI.
- Сервер выбирает параметры и отправляет сертификат.
- Клиент проверяет доменное имя, срок действия, подписи и цепочку доверия.
- Стороны обмениваются ключевым материалом и получают одинаковые сеансовые ключи.
- Последующий HTTP-трафик шифруется быстрым симметричным алгоритмом.
Современный обмен ключами, например ECDHE, обеспечивает прямую секретность: компрометация закрытого ключа сервера в будущем не должна раскрыть ранее записанные соединения.
SNI (Server Name Indication) передаёт имя сайта в начале TLS-соединения. Благодаря SNI Nginx может обслуживать несколько HTTPS-сайтов с разными сертификатами на одном IP-адресе.
Сертификаты и CA
TLS-сертификат связывает открытый ключ с доменным именем. Обычно он содержит:
- доменные имена в расширении SAN;
- открытый ключ;
- издателя;
- начало и окончание срока действия;
- серийный номер и цифровую подпись.
CA (Certificate Authority) — удостоверяющий центр, выпускающий и подписывающий сертификаты. Цепочка доверия обычно выглядит так:
Корневой CA
└── Промежуточный CA
└── Сертификат example.comКорневой CA уже находится в доверенном хранилище клиента. Сервер должен отправить сертификат сайта и промежуточные сертификаты.
Типичные файлы:
fullchain.pem # сертификат сайта и промежуточная цепочка
privkey.pem # закрытый ключ
chain.pem # промежуточные сертификаты CAДля Nginx обычно используют:
ssl_certificate /path/to/fullchain.pem;
ssl_certificate_key /path/to/privkey.pem;Закрытый ключ нельзя публиковать или хранить в каталоге статики. Ограничьте доступ:
sudo chown root:root /path/to/privkey.pem
sudo chmod 600 /path/to/privkey.pemПросмотр сертификата:
openssl x509 -in certificate.pem -text -noout
openssl x509 -in certificate.pem -noout -subject -issuer -datesПроверка удалённого сайта с передачей SNI:
openssl s_client -connect example.com:443 \
-servername example.com </dev/nullПроверка дат:
openssl s_client -connect example.com:443 \
-servername example.com </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -datesSelf-signed сертификаты
Self-signed certificate подписан собственным ключом, а не публично доверенным CA. Он подходит для локальной разработки, лабораторных стендов и внутренних систем, где собственный CA добавлен в доверенные хранилища клиентов.
Браузер не доверяет произвольному self-signed сертификату и показывает предупреждение. Соединение может быть зашифровано, но подлинность сервера не подтверждена доверенным CA.
Создание сертификата для localhost с SAN:
openssl req -x509 -newkey rsa:2048 -sha256 -nodes \
-days 365 \
-keyout local.key \
-out local.crt \
-subj "/CN=localhost" \
-addext "subjectAltName=DNS:localhost,IP:127.0.0.1"Установка файлов:
sudo install -d -m 700 /etc/nginx/tls
sudo install -m 644 local.crt /etc/nginx/tls/local.crt
sudo install -m 600 local.key /etc/nginx/tls/local.keyКонфигурация Nginx:
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name localhost;
ssl_certificate /etc/nginx/tls/local.crt;
ssl_certificate_key /etc/nginx/tls/local.key;
root /var/www/html;
}Let's Encrypt и Certbot
Let's Encrypt — публичный CA, бесплатно выдающий доменно-проверенные сертификаты через протокол ACME. Основные проверки контроля над доменом:
- HTTP-01 — CA запрашивает специальный файл по HTTP;
- DNS-01 — владелец создаёт TXT-запись в DNS;
- TLS-ALPN-01 — проверка выполняется через TLS.
Для HTTP-01 домен должен указывать на сервер, а TCP-порт 80 должен быть доступен извне. DNS-01 применяется в том числе для wildcard-сертификатов *.example.com.
Certbot — ACME-клиент для получения и обновления сертификатов.
Установка
Debian/Ubuntu:
sudo apt update
sudo apt install certbot python3-certbot-nginxFedora:
sudo dnf install certbot python3-certbot-nginxArch Linux:
sudo pacman -S certbot certbot-nginxНазвания пакетов могут зависеть от версии дистрибутива.
Подготовка
До выпуска сертификата настройте рабочий HTTP-хост:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example.com;
location / {
try_files $uri $uri/ =404;
}
}Проверьте конфигурацию и DNS:
sudo nginx -t
sudo systemctl reload nginx
dig +short example.com A
dig +short example.com AAAAОшибочная AAAA-запись может сорвать проверку, если сервер недоступен по IPv6.
Получение сертификата
Получить сертификат и автоматически настроить Nginx:
sudo certbot --nginx \
-d example.com \
-d www.example.comПолучить сертификат без изменения конфигурации:
sudo certbot certonly --nginx \
-d example.com \
-d www.example.comОсновные файлы обычно расположены здесь:
/etc/letsencrypt/live/example.com/fullchain.pem
/etc/letsencrypt/live/example.com/privkey.pemКаталог live содержит ссылки на актуальные версии. Не копируйте эти файлы вручную без необходимости: это может нарушить автоматическое обновление.
Webroot
sudo certbot certonly --webroot \
-w /var/www/letsencrypt \
-d example.com \
-d www.example.comСоответствующий location:
location ^~ /.well-known/acme-challenge/ {
root /var/www/letsencrypt;
default_type text/plain;
try_files $uri =404;
}Wildcard
Wildcard требует DNS-01:
sudo certbot certonly --manual \
--preferred-challenges dns \
-d example.com \
-d '*.example.com'Ручной режим неудобен для автоматического продления. Для production обычно применяют DNS-плагин с ограниченными API-учётными данными.
*.example.com охватывает api.example.com, но не сам example.com и не a.b.example.com.
Автоматическое обновление
Просмотр сертификатов:
sudo certbot certificatesТест продления:
sudo certbot renew --dry-runПроверка systemd timer:
systemctl list-timers | grep -i certbotПри собственной схеме можно использовать deploy hook:
sudo certbot renew \
--deploy-hook "systemctl reload nginx"Не запускайте принудительное обновление слишком часто: ACME-сервисы применяют лимиты запросов. Для проверки используйте --dry-run.
HTTPS в Nginx
Минимальная конфигурация:
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name example.com www.example.com;
ssl_certificate \
/etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key \
/etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
root /var/www/example.com;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}После изменения:
sudo nginx -t
sudo systemctl reload nginx
curl -I https://example.comНе включайте SSLv2, SSLv3, TLS 1.0 и TLS 1.1 без специального требования. Не копируйте устаревшие наборы ssl_ciphers: настройки должны соответствовать версиям Nginx и OpenSSL.
HTTPS reverse proxy
Если Nginx завершает TLS и передаёт запрос приложению по HTTP:
location /api/ {
proxy_pass http://127.0.0.1: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;
}X-Forwarded-Proto сообщает приложению, что исходный запрос использовал HTTPS. Приложение должно доверять proxy-заголовкам только от известного reverse proxy.
Для HTTPS-upstream включите проверку сертификата:
location / {
proxy_pass https://backend.example.internal;
proxy_ssl_server_name on;
proxy_ssl_name backend.example.internal;
proxy_ssl_verify on;
proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
proxy_ssl_verify_depth 3;
}Без proxy_ssl_verify on трафик может быть зашифрован, но подлинность upstream не проверяется.
Редирект HTTP → HTTPS
Отдельный виртуальный хост на порту 80:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}$request_uri сохраняет путь и строку запроса. Для API можно рассмотреть код 308, который явно сохраняет HTTP-метод и тело:
return 308 https://$host$request_uri;Поведение всей цепочки клиентов и прокси следует проверить заранее.
Для webroot-проверки можно сделать исключение:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
location ^~ /.well-known/acme-challenge/ {
root /var/www/letsencrypt;
try_files $uri =404;
}
location / {
return 301 https://$host$request_uri;
}
}Если www.example.com перенаправляется на example.com по HTTPS, сертификат всё равно должен быть действителен для www.example.com: TLS handshake выполняется раньше HTTP-редиректа.
HSTS
HSTS (HTTP Strict Transport Security) сообщает браузеру, что сайт необходимо открывать только по HTTPS.
add_header Strict-Transport-Security \
"max-age=31536000" always;max-ageзадаётся в секундах;alwaysдобавляет заголовок также к ответам с ошибками.
Распространение на все поддомены:
add_header Strict-Transport-Security \
"max-age=31536000; includeSubDomains" always;Используйте includeSubDomains только тогда, когда все поддомены поддерживают HTTPS.
Вариант для preload:
add_header Strict-Transport-Security \
"max-age=63072000; includeSubDomains; preload" always;Слово preload не добавляет домен в список автоматически: требуется отдельная заявка. Отмена preload занимает время, поэтому включайте его только после полной проверки инфраструктуры.
Безопаснее увеличивать срок постепенно:
# Первичная проверка: 5 минут
add_header Strict-Transport-Security "max-age=300" always;Затем перейти к одному дню и только после стабильной работы — к году. HSTS должен отправляться по HTTPS; заголовок, полученный по HTTP, браузер игнорирует.
Проверка:
curl -I https://example.comСледует увидеть:
Strict-Transport-Security: max-age=31536000Учтите наследование add_header: собственный add_header внутри location может отменить наследование заголовков родительского уровня.
Полный пример
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
location ^~ /.well-known/acme-challenge/ {
root /var/www/letsencrypt;
try_files $uri =404;
}
location / {
return 301 https://example.com$request_uri;
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name example.com www.example.com;
ssl_certificate \
/etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key \
/etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
add_header Strict-Transport-Security \
"max-age=31536000" always;
root /var/www/example.com;
index index.html;
access_log /var/log/nginx/example.access.log;
error_log /var/log/nginx/example.error.log warn;
location / {
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://127.0.0.1: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;
}
}Применение и проверка:
sudo nginx -t
sudo systemctl reload nginx
curl -I http://example.com
curl -I https://example.com
sudo certbot renew --dry-runДиагностика
Итоговая конфигурация Nginx:
sudo nginx -TЖурналы:
sudo journalctl -u nginx -n 100 --no-pager
sudo tail -F /var/log/nginx/error.log
sudo less /var/log/letsencrypt/letsencrypt.logПроверка портов:
sudo ss -lntp | grep -E ':(80|443)\b'Проверка TLS 1.2 и TLS 1.3:
openssl s_client -connect example.com:443 \
-servername example.com -tls1_2 </dev/null
openssl s_client -connect example.com:443 \
-servername example.com -tls1_3 </dev/nullТипичные проблемы
Сертификат не соответствует домену: домен отсутствует в SAN, выбран другой server-блок или клиент не передал SNI.
openssl s_client -connect example.com:443 \
-servername example.com </dev/null 2>/dev/null \
| openssl x509 -noout -subject -ext subjectAltNameНеполная цепочка: в ssl_certificate указан сертификат сайта вместо fullchain.pem.
HTTP-01 не проходит: проверьте A/AAAA-записи, порт 80, firewall, server_name, доступ к /.well-known/acme-challenge/ и журналы.
Nginx не читает ключ: проверьте путь и права через sudo nginx -t; не выдавайте всем пользователям доступ к закрытому ключу.
Бесконечный редирект: проверьте TLS termination на внешнем балансировщике и обработку X-Forwarded-Proto.
Mixed content: HTTPS-страница загружает ресурсы по http://; переведите все ресурсы и API на HTTPS.
Рекомендации
- Используйте TLS 1.2 и TLS 1.3.
- Храните закрытые ключи вне публичных каталогов и репозиториев.
- Используйте
fullchain.pemдля корректной цепочки. - Выполняйте
nginx -tперед каждым reload. - Проверяйте продление через
certbot renew --dry-run. - Контролируйте срок сертификатов и состояние systemd timer.
- Проверяйте сертификаты HTTPS-upstream.
- Включайте HSTS,
includeSubDomainsиpreloadпостепенно. - Регулярно обновляйте Nginx, OpenSSL и Certbot пакетным менеджером.
Краткая памятка
# Получить сертификат и настроить Nginx
sudo certbot --nginx -d example.com -d www.example.com
# Проверить сертификаты и продление
sudo certbot certificates
sudo certbot renew --dry-run
# Проверить и применить конфигурацию
sudo nginx -t
sudo systemctl reload nginx
# Проверить HTTPS
curl -I https://example.com
# Проверить сертификат сервера
openssl s_client -connect example.com:443 \
-servername example.com </dev/null