S в HTTPS означает Secure («защищенный»): HTTPS — это тот же протокол HTTP, только его сообщения идут внутри защищенного канала TLS. Сам HTTP при этом не меняется — те же методы, заголовки и коды ответа. Меняется канал: по умолчанию HTTP работает на TCP-порту 80, HTTPS — на порту 443.
Содержание
TLS дает три разных свойства, и их полезно не смешивать: шифрование (посторонний не прочитает данные), целостность (не сможет незаметно их изменить) и проверку подлинности сервера по сертификату (вы говорите именно с доменом из адресной строки). Ниже — как это устроено, опыт на Python и разбор предупреждения «Не защищено».
Мини-словарь: что с чем путают
| Термин | Что это | Частая ошибка |
|---|---|---|
| HTTP | Прикладной протокол «запрос — ответ» между браузером и сервером | Считать его протоколом шифрования — он ничего не шифрует |
| TLS | Протокол защищенного канала поверх TCP (актуальны версии 1.2 и 1.3) | Называть его SSL |
| SSL | Предшественник TLS; SSL 3.0 запрещен с 2015 года (RFC 7568) | Думать, что «SSL-сертификат» работает по SSL — это привычное название сертификата для TLS |
| Сертификат | Документ с доменными именами и открытым ключом сервера, подписанный центром сертификации (CA) | Считать, что им шифруются все данные |
HTTP и HTTPS: сравнение
| Свойство | HTTP | HTTPS |
|---|---|---|
| Схема в адресе | http:// |
https:// |
| Порт по умолчанию | 80 | 443 |
| Что видно на пути (Wi-Fi, провайдер, прокси) | Все: адрес страницы, заголовки, cookie, пароли из форм | IP-адреса, объем трафика и обычно имя домена; путь, заголовки и тело зашифрованы |
| Можно ли незаметно подменить ответ | Да, например вставить рекламу или скрипт | Нет: подмена ломает проверку целостности |
| Проверка, что сервер настоящий | Нет | Да, по сертификату и цепочке до доверенного CA |
| Отметка в браузере | «Не защищено» | Значок настроек соединения без предупреждения |
| HTTP/2 и HTTP/3 в браузерах | Не используются | Доступны: HTTP/2 браузеры включают только поверх TLS, HTTP/3 работает на QUIC со встроенным TLS 1.3 |
Тезис «HTTP быстрее, потому что нет шифрования» устарел: TLS 1.3 добавляет к установке соединения один обмен сообщениями, шифрование на современных процессорах ускорено аппаратно, а HTTP/2 и HTTP/3 есть только с HTTPS.
Как устанавливается HTTPS-соединение
Упрощенная схема для TLS 1.3 (в TLS 1.2 шагов больше и сертификат передается открыто):
- Браузер открывает TCP-соединение с сервером на порт 443.
- ClientHello: браузер сообщает версии и шифры, свою часть обмена ключами и имя сайта (SNI). Сообщение не зашифровано, поэтому имя домена видно на пути, если не используется расширение ECH.
- ServerHello: сервер отвечает своей частью обмена. Из двух частей стороны независимо вычисляют общие сессионные ключи, сам ключ по сети не передается. Классически это Диффи — Хеллман на эллиптических кривых (X25519); современные браузеры и OpenSSL 3.5+ по умолчанию договариваются о гибриде X25519MLKEM768 с постквантовым ML-KEM.
- Уже в зашифрованном виде сервер присылает сертификат и подпись своим закрытым ключом — так он доказывает, что владеет ключом из сертификата.
- Браузер проверяет сертификат: цепочка подписей ведет к доверенному корневому CA, имя сайта есть в SAN, срок не истек.
- Дальше HTTP-запросы и ответы идут внутри канала, зашифрованные симметричными сессионными ключами (AES-GCM или ChaCha20-Poly1305).
Поправка к популярному объяснению: ключ из сертификата не шифрует ваши данные, он подтверждает, кто на другой стороне. Данные шифруются одноразовыми сессионными ключами, поэтому будущая утечка закрытого ключа сервера не раскрывает записанный ранее трафик TLS 1.3.
Проверяем на практике: что видит посредник
Опыт проверен 24.09.2026 в Python 3.14.7 и OpenSSL 3.5.7 (образ python:3.14-slim). Сначала создадим учебный центр сертификации и сертификат для localhost. Ключи учебные и без пароля — для боевого сервера так не делают.
# 1. Учебный центр сертификации (CA): самоподписанный, с правом подписывать
openssl req -x509 -newkey rsa:2048 -nodes -days 30 \
-keyout ca.key -out ca.crt -subj "/CN=Demo Training CA" \
-addext "basicConstraints=critical,CA:TRUE" \
-addext "keyUsage=critical,keyCertSign,cRLSign"
# 2. Ключ сервера и запрос на сертификат
openssl req -newkey rsa:2048 -nodes \
-keyout server.key -out server.csr -subj "/CN=localhost"
# 3. CA подписывает сертификат; имена, для которых он действует, - в SAN
cat > server.ext <<'X'
subjectAltName=DNS:localhost,IP:127.0.0.1
keyUsage=critical,digitalSignature,keyEncipherment
extendedKeyUsage=serverAuth
authorityKeyIdentifier=keyid
X
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-days 30 -extfile server.ext -out server.crt
openssl x509 -in server.crt -noout -subject -issuer -ext subjectAltName
Последняя команда печатает, кому выдан сертификат, кем подписан и для каких имен:
subject=CN=localhost
issuer=CN=Demo Training CA
X509v3 Subject Alternative Name:
DNS:localhost, IP Address:127.0.0.1
Расширения basicConstraints и keyUsage у CA не формальность: в Python 3.13+ проверка по умолчанию строгая, и без keyUsage CA отклоняется с ошибкой CA cert does not include key usage extension.
Скрипт demo.py в той же папке поднимает сервер по HTTP (порт 8080) и HTTPS (8443), ставит перед каждым «посредника», который пересылает и запоминает байты, и отправляет форму с паролем.
import socket, ssl, threading, urllib.error, urllib.request
from http.server import HTTPServer, BaseHTTPRequestHandler
class Hello(BaseHTTPRequestHandler):
def do_POST(self):
self.rfile.read(int(self.headers["Content-Length"]))
self.send_response(200)
self.end_headers()
self.wfile.write(b"ok")
def log_message(self, *args):
pass
def serve(port, tls):
srv = HTTPServer(("127.0.0.1", port), Hello)
if tls:
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.load_cert_chain("server.crt", "server.key")
srv.socket = ctx.wrap_socket(srv.socket, server_side=True)
threading.Thread(target=srv.serve_forever, daemon=True).start()
seen = bytearray() # все байты от клиента к серверу, которые прошли через посредника
def spy(listen_port, target_port):
"""Посредник на пути: пересылает байты и запоминает все, что прошло."""
lsock = socket.create_server(("127.0.0.1", listen_port))
def pipe(src, dst, record):
try:
while data := src.recv(4096):
if record:
seen.extend(data)
dst.sendall(data)
except OSError:
pass
try:
dst.shutdown(socket.SHUT_WR) # передать дальше "конец данных"
except OSError:
pass
def run():
while True:
client, _ = lsock.accept()
server = socket.create_connection(("127.0.0.1", target_port))
threading.Thread(target=pipe, args=(client, server, True), daemon=True).start()
threading.Thread(target=pipe, args=(server, client, False), daemon=True).start()
threading.Thread(target=run, daemon=True).start()
serve(8080, tls=False); serve(8443, tls=True)
spy(9080, 8080); spy(9443, 8443)
form = b"login=anna&password=qwerty"
def report():
print(" первые байты:", bytes(seen[:24]))
print(" пароль виден посреднику:", b"password=qwerty" in seen)
seen.clear()
print("1) HTTP через посредника:")
urllib.request.urlopen("http://127.0.0.1:9080/", data=form)
report()
print("2) HTTPS через посредника, CA известен клиенту:")
trusted = ssl.create_default_context(cafile="ca.crt")
resp = urllib.request.urlopen("https://127.0.0.1:9443/", data=form, context=trusted)
print(" ответ сервера:", resp.status, resp.read())
report()
print("3) HTTPS без доверия к нашему CA (как чужой браузер):")
try:
urllib.request.urlopen("https://127.0.0.1:8443/", data=form,
context=ssl.create_default_context())
except urllib.error.URLError as e:
print(" ошибка:", e.reason)
print("4) Сертификат есть, но имя не совпадает:")
try:
with socket.create_connection(("127.0.0.1", 8443)) as raw:
trusted.wrap_socket(raw, server_hostname="shop.example")
except ssl.SSLCertVerificationError as e:
print(" ошибка:", e.verify_message)
with socket.create_connection(("127.0.0.1", 8443)) as raw:
with trusted.wrap_socket(raw, server_hostname="localhost") as tls:
print("5) Параметры соединения:", tls.version(), tls.cipher()[0])
Вывод python demo.py (хвост байтов во втором пункте при каждом запуске свой — это случайное число из ClientHello):
1) HTTP через посредника:
первые байты: b'POST / HTTP/1.1\r\nAccept-'
пароль виден посреднику: True
2) HTTPS через посредника, CA известен клиенту:
ответ сервера: 200 b'ok'
первые байты: b'\x16\x03\x01\x05\xde\x01\x00\x05\xda\x03\x03\x87\xca\xc5\x1f\xe7A\xb6j\x1b\xa7H\x9e\xfd'
пароль виден посреднику: False
3) HTTPS без доверия к нашему CA (как чужой браузер):
ошибка: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1082)
4) Сертификат есть, но имя не совпадает:
ошибка: Hostname mismatch, certificate is not valid for 'shop.example'.
5) Параметры соединения: TLSv1.3 TLS_AES_256_GCM_SHA384
Что показывает каждый пункт:
- 1. По HTTP посредник получает запрос как есть, с паролем — так его видит, например, владелец открытой точки Wi-Fi.
- 2. По HTTPS первые байты — заголовок TLS-записи (
\x16\x03\x01) с ClientHello. Сам ClientHello открыт (версии, шифры, SNI), а запрос с формой идет позже в зашифрованных записях: сервер ответил 200, пароля в потоке нет. - 3. Клиент не знает учебного CA и не строит цепочку до доверенного корня — браузер здесь показал бы полноэкранное предупреждение.
- 4. Сертификат подлинный, но выдан для
localhost, а клиент ждалshop.example. Эта проверка не дает посреднику выдать себя за чужой сайт со своим сертификатом. - 5. Договорились о TLS 1.3 и AES-256-GCM (обмен ключами здесь — X25519MLKEM768, это видно в
openssl s_client -brief); на другой сборке OpenSSL шифр может отличаться.
Граница опыта: посредник пассивный. Активный подставил бы свой сертификат, и сработали бы пункты 3 и 4. Защита держится на проверке сертификата: если отключить ее в коде (verify=False, CERT_NONE), шифрование останется, а защиты от подмены сервера не будет.
Почему браузер пишет «Не защищено»
| Что видно | Причина | Что делать |
|---|---|---|
| «Не защищено» в адресной строке | Сайт открыт по http:// |
Владельцу — HTTPS и 301-редирект; посетителю — не вводить пароли и данные карт |
Полноэкранное предупреждение, NET::ERR_CERT_DATE_INVALID |
Сертификат истек или на компьютере неверные дата и время | Проверить часы; владельцу — настроить автопродление |
NET::ERR_CERT_COMMON_NAME_INVALID |
Имени сайта нет в SAN, например сертификат на www.example.com, а открыли example.com |
Выпустить сертификат на все используемые имена |
NET::ERR_CERT_AUTHORITY_INVALID |
Самоподписанный сертификат, неизвестный CA или сервер не отдает промежуточный сертификат | Отдавать полную цепочку; брать сертификат у публичного CA |
| Предупреждение о смешанном содержимом | Страница по HTTPS грузит картинки или скрипты по http:// |
Перевести все ресурсы на https:// или относительные адреса |
| Ошибка на всех сайтах подряд | Антивирус или корпоративный прокси подменяет сертификаты | Проверить, чей CA в сертификате; обратиться к администратору |
Коды ошибок приведены в формате Chrome; в Firefox и Safari формулировки другие, причины те же.
Чего HTTPS не гарантирует
- Честность сайта. Сертификат с проверкой домена (DV) подтверждает только контроль над доменом; фишинговый сайт тоже получает его бесплатно.
- Анонимность. Провайдер видит IP-адреса, объем трафика и, как правило, имя домена (через DNS и SNI).
- Безопасность на сервере и устройстве. HTTPS защищает данные в пути, но не от утечки из базы сайта или вируса на компьютере.
Как перевести сайт на HTTPS
- Получить сертификат на все нужные имена: у хостинга или бесплатно у Let’s Encrypt через ACME-клиент (например, certbot) с автопродлением.
- Включить на сервере только TLS 1.2 и 1.3.
- Настроить 301-редирект с
http://наhttps://, обновить внутренние ссылки, canonical и sitemap, исправить смешанное содержимое. - Добавить заголовок
Strict-Transport-Security(HSTS): браузер будет ходить на сайт только по HTTPS. Начинать с небольшогоmax-age,includeSubDomains— только когда все поддомены работают по HTTPS: браузеры запоминают заголовок, быстро откатить его нельзя. - Проверить:
curl -I http://ваш-домен/отдает 301 на https, браузер открывает сайт без предупреждений.
Выводы
- S в HTTPS — Secure: это HTTP внутри TLS; порт по умолчанию 443 вместо 80.
- TLS дает три свойства: шифрование, целостность и проверку сервера по сертификату; данные шифруются сессионными ключами, а ключ сертификата подтверждает, кто на другой стороне.
- «Не защищено» — это HTTP без TLS, просроченный сертификат, чужое имя, неизвестный CA или смешанное содержимое.
- HTTPS защищает данные в пути, но не доказывает честность сайта и не скрывает от провайдера домен.
Где применяется / связь с практикой
Разбор HTTPS — ежедневная работа сетевого инженера и администратора: настроить TLS на балансировщике, понять, почему клиент не доверяет сертификату, найти, где теряется промежуточный сертификат. Как устроен сам протокол поверх канала, показано в статье про HTTP-запросы от А до Я.
Освойте тему на практике
Системно разобраться с TCP/IP, маршрутизацией, портами и сервисами можно на курсе «Сетевой инженер. Базовый уровень». Чтобы сначала посмотреть формат обучения, подойдут открытые уроки Otus.
FAQ
Можно ли включить HTTPS для локального сайта на своем компьютере?
Да: создать свой CA и сертификат для localhost, как в опыте выше, и добавить CA в доверенные только на своей машине. Публичные CA на localhost сертификаты не выпускают.
Зачем HTTPS сайту без форм и оплаты?
Без HTTPS посредник может подменить страницу, например вставить рекламу или вредоносный скрипт, а браузер пометит сайт «Не защищено».
Сертификат выдается навсегда?
Нет. Максимальный срок публичных сертификатов сокращается по правилам CA/Browser Forum: с 15 марта 2026 года — 200 дней, к 2029 году — 47. У Let’s Encrypt по умолчанию 90 дней, к 2028 году срок сокращается до 45. Поэтому продление автоматизируют.



