Безопасность сайта

Смешанный контент на HTTPS: почему пропал замок и как это починить

Сайт на https, но браузер ругается. Как найти ресурсы, которые грузятся по http, починить их в базе и теме и защититься на будущее.

12 минут чтения 2 Дмитрий Акиндинов
Содержание
  1. Что браузер считает смешанным контентом
  2. Где эти http-ссылки берутся
  3. Как найти всё за пятнадцать минут
  4. Чиним базу без порчи данных
  5. Что делать с чужими ресурсами
  6. Про upgrade-insecure-requests
  7. Редиректы и HSTS
  8. Чек-лист проверки после починки
  9. Как не наступить снова
  10. Сколько это стоит и когда звать
  11. Частые вопросы

Замок пропадает, когда страница отдаётся по защищённому протоколу, а часть картинок, скриптов или стилей тянется по обычному. Ищут такие ссылки в базе, в шаблонах темы и в настройках плагинов. Пятнадцати минут обычно хватает, чтобы найти все.

Сертификат стоит, редирект на https работает, а замок в адресной строке всё равно пропал или превратился в предупреждение. Значит, где-то на странице остался смешанный контент: сайт отдаётся по https, а часть картинок, скриптов или стилей тянется по http. Статья для владельцев сайтов на WordPress, Битрикс, OpenCart и MODX, которые уже перешли на https и не понимают, почему браузер продолжает жаловаться. Разберу, где обычно прячутся такие ссылки, как их найти за пятнадцать минут, чем чинить базу без порчи данных и какие заголовки ставить, чтобы проблема не вернулась после следующего обновления.

Значок замка, разделённый на защищённую и незащищённую половины

Ко мне такие сайты приезжают пачками, обычно после самостоятельного перехода на https или после переноса на новый хостинг. Владелец сделал всё правильно на 90 процентов, а последние 10 процентов сидят в базе данных и в захардкоженных путях внутри темы. Найти их руками через админку нереально, а через консоль браузера и одну команду в SSH это дело получаса.

Что браузер считает смешанным контентом

Смешанный контент это ситуация, когда HTML-документ пришёл по защищённому каналу, а подгружаемые им ресурсы идут по обычному http. Браузеру это не нравится по понятной причине: любой узел на пути (провайдер, роутер в кафе, прокси на корпоративном шлюзе) видит эти запросы открытыми и может подменить содержимое. Один незащищённый JS-файл сводит на нет весь смысл сертификата, потому что подменённый скрипт делает со страницей всё, что захочет.

Раньше браузеры делили такой контент на активный и пассивный и вели себя по-разному. Сейчас разница стирается: Chrome с 86-й версии сначала пробует автоматически поднять http-подресурсы до https, и только если ресурс по https не отдаётся, блокирует его или показывает битую картинку. Firefox и Safari идут тем же путём. Практический вывод простой: у вас либо ресурс доступен по https и всё тихо, либо он просто исчезает со страницы.

Тип ресурсаПоведение браузера в 2026Чем это видно владельцу
Картинки, видео, аудиопопытка апгрейда до https, при неудаче блокировкапустые места вместо фото, битые превью
CSS-файлыапгрейд, при неудаче блокировкавёрстка рассыпается, страница без стилей
JavaScript, XHR/fetchапгрейд, при неудаче жёсткая блокировкане работают формы, слайдеры, корзина
iframe (карты, видео, виджеты)апгрейд, при неудаче блокировкапустая рамка вместо карты или плеера
Ссылки <a href="http://...">не блокируютсяничего, но лишний редирект и потеря referrer

Отдельно про сам замок. Chrome в 117-й версии убрал иконку замка и заменил её на значок настроек, так что привычный маркер «всё хорошо» у части посетителей уже не отображается вообще. Яндекс.Браузер замок пока показывает. Поэтому жалоба «пропал замок» сейчас требует уточнения: то ли реально сломалось шифрование, то ли просто обновился браузер. Проверять надо не по иконке, а по консоли.

Про случай, когда браузер прямо пишет посетителю про небезопасное соединение и красный экран, я разбирал отдельно в статье про то, почему браузер пишет «опасный сайт». Смешанный контент это более мягкая ситуация: страница открывается, но часть её не работает.

Где эти http-ссылки берутся

За шесть лет обслуживания чужих сайтов я вижу одни и те же пять источников. Порядок примерно по частоте.

Тело записей и страниц в базе

Самый массовый случай. Картинки, вставленные в редакторе до перехода на https, хранятся в wp_posts.post_content с полным абсолютным адресом вида http://site.ru/wp-content/uploads/2023/05/foto.jpg. Таких вхождений на сайте с трёхлетней историей блога легко набирается несколько сотен.

Сюда же попадают вставленные вручную iframe с картами, старые embed-коды видео и любой HTML, который кто-то когда-то скопировал из другого места.

Настройки темы и плагинов

Логотип, favicon, фоновые картинки секций, слайдеры на главной. Всё это лежит в wp_options и wp_postmeta, часто внутри сериализованных массивов. Отдельная беда это конструкторы страниц: Elementor хранит свои данные в JSON внутри postmeta и умеет прятать http-пути в местах, куда обычный поиск не заглядывает.

В Битриксе аналогичная история сидит в свойствах инфоблоков и в настройках компонентов, которые кто-то поправил через режим правки.

Захардкоженные пути в шаблонах

Разработчик вписал в header.php подключение шрифта или счётчика с явным http://. Пока сайт был на http, всё работало. После перехода строка осталась. Такие вещи не лечатся заменой в базе, потому что они в файлах темы.

Внешние скрипты и виджеты

Старый jQuery с чужого CDN, счётчики, карты, чаты, кнопки соцсетей. Многие из этих сервисов уже давно отдают https, но подключены по-старому. Часть сервисов умерла вместе со своими доменами, и их подключения теперь просто мусор в разметке.

Свой код в functions.php и сниппетах

Отправка данных в CRM, обращение к чужому API, генерация ссылок для писем. Тут http-адрес часто не виден на странице, но всплывает в консоли при отправке формы или в логах.

Как найти всё за пятнадцать минут

Я делаю это в три захода: браузер, краулер, база. Каждый шаг ловит то, что предыдущий пропускает.

Консоль браузера

Открываете страницу, жмёте F12, вкладка Console. Ищете строки вида:

Mixed Content: The page at 'https://site.ru/uslugi/' was loaded over HTTPS,
but requested an insecure element 'http://site.ru/wp-content/uploads/logo.png'.
This request was automatically upgraded to HTTPS.

Если ресурс не поднялся, вместо automatically upgraded будет This content should also be served over HTTPS или прямое blocked. Вкладка Security в DevTools показывает сводку по странице: какие ресурсы пришли небезопасно.

Минус способа очевиден: проверяете одну страницу за раз. Для сайта на 200 страниц это не метод, но для быстрой диагностики главной сойдёт.

Краулер по всему сайту

Screaming Frog в бесплатной версии обходит 500 URL, этого хватает большинству сайтов малого бизнеса. После обхода смотрите вкладку Security и отчёт Reports > Security > Mixed Content. Получаете таблицу: страница, небезопасный ресурс, тип. Дальше просто фильтруете по домену и понимаете, свои это картинки или чужие скрипты.

Онлайн-альтернативы (whynopadlock.com и подобные) проверяют один URL и годятся для контроля после починки, не для инвентаризации.

Поиск по базе

Самое честное. Если есть SSH, на WordPress:

wp db query "SELECT COUNT(*) FROM wp_posts WHERE post_content LIKE '%http://site.ru%';"
wp db query "SELECT option_name FROM wp_options WHERE option_value LIKE '%http://site.ru%' LIMIT 50;"
wp db query "SELECT meta_key, LEFT(meta_value,120) FROM wp_postmeta WHERE meta_value LIKE '%http://site.ru%' LIMIT 50;"

Сразу видно масштаб: 4 вхождения или 1 800. От этого зависит, чинить точечно или гнать замену по всей базе.

По файлам темы и плагинов проходимся grep:

grep -rn "http://" wp-content/themes/ --include=*.php --include=*.js --include=*.css
grep -rn "http://" wp-content/mu-plugins/ --include=*.php

Игнорируйте попадания в комментариях, в xmlns схемах и в служебных строках вроде http://www.w3.org/1999/xhtml. Это не ресурсы, браузер их не грузит.

Чиним базу без порчи данных

Главная ловушка этого этапа: сериализованные данные. WordPress хранит настройки виджетов, темы и многих плагинов в PHP-сериализованных строках, где длина каждого значения записана числом. Строка s:24:"http://site.ru/logo.png" после наивной замены на https:// становится длиннее, число 24 перестаёт совпадать, и PHP при чтении получает битые данные. Виджеты пропадают, настройки слетают, конструктор страниц показывает пустой холст.

Поэтому запрос вида UPDATE wp_options SET option_value = REPLACE(...) через phpMyAdmin это плохая идея. Нужен инструмент, который распаковывает сериализацию, меняет значение и пакует обратно.

На WordPress это делает WP-CLI:

# сначала бэкап, без вариантов
wp db export backup-before-mixed-fix.sql

# смотрим, что будет заменено, ничего не меняя
wp search-replace 'http://site.ru' 'https://site.ru' --all-tables --precise --dry-run --report-changed-only

# если картина устраивает, повторяем без --dry-run
wp search-replace 'http://site.ru' 'https://site.ru' --all-tables --precise --report-changed-only

# сбрасываем кэш, иначе увидите старую версию страниц
wp cache flush

Флаг --precise заставляет WP-CLI обрабатывать сериализацию через PHP, а не быстрым бинарным способом. Медленнее, зато без сюрпризов. --all-tables нужен, если плагины создали свои таблицы (у форм и конструкторов это норма).

Если SSH нет, есть плагин Better Search Replace: тот же алгоритм через админку, с обязательным сухим прогоном. После работы плагин лучше удалить, чтобы он не висел лишним кодом.

На Битриксе универсальной команды нет. Обычно чиню заменой в дампе базы для статических текстов плюс правкой свойств инфоблоков через отдельный скрипт с CIBlockElement::SetPropertyValues. Дамп правится так:

mysqldump -u user -p dbname > dump.sql
sed -i 's|http://site.ru|https://site.ru|g' dump.sql
mysql -u user -p dbname < dump.sql

Оговорка: этот способ подходит, только если в базе нет сериализованных PHP-массивов с этим доменом. В Битриксе они встречаются в настройках компонентов, поэтому перед заливкой проверьте дамп на s: перед вашими ссылками. Если такие есть, правьте их отдельно и аккуратно.

Отдельно про относительные пути. Многие советуют менять абсолютные адреса на протокол-независимые //site.ru/file.jpg. Формально работает, но это наследие 2014 года, и сейчас так делать не надо: в письмах и RSS такие ссылки ломаются, потому что там нет базового протокола. Пишите полный https://.

Что делать с чужими ресурсами

Свой домен вылечили, остались внешние подключения. Тут три исхода, и решение зависит от того, какой именно.

Ресурс отдаётся по https, надо просто поправить протокол. Это большинство живых сервисов: Яндекс.Карты, Яндекс.Метрика, VK, шрифтовые CDN. Меняете http:// на https:// в шаблоне или в настройке плагина, проверяете, что грузится.

Ресурс не поддерживает https. Такое ещё встречается у мелких региональных сервисов и старых виджетов погоды или курсов валют. Вариантов два: убрать совсем или проксировать через свой сервер. Проксирование это лишний код и лишняя точка отказа, для виджета с курсом доллара оно того не стоит. Я обычно предлагаю убрать.

Сервис вообще перестал существовать. Проверяется за секунду: домен не резолвится или отдаёт заглушку регистратора. Такие подключения надо вычищать из шаблонов, они висят мёртвым грузом и добавляют задержку в загрузку страницы.

Заодно посмотрите, что вообще подключено на страницу. У меня был клиент, у которого в footer.php жили четыре счётчика, два из которых закрылись в 2019 году, а третий дублировал Метрику. Уборка таких хвостов даёт заметный выигрыш в скорости и заодно решает половину вопросов со смешанным контентом.

Про upgrade-insecure-requests

Рано или поздно вам посоветуют добавить заголовок CSP и закрыть вопрос одной строкой. В .htaccess это выглядит так:

<IfModule mod_headers.c>
    Header always set Content-Security-Policy "upgrade-insecure-requests"
</IfModule>

Для nginx:

add_header Content-Security-Policy "upgrade-insecure-requests" always;

Директива говорит браузеру: любой http-подресурс на этой странице считай запрошенным по https. Работает, поддерживается везде, ставится за минуту.

Моё отношение к ней прохладное, и вот почему. Она не чинит источник проблемы: в базе по-прежнему лежат http-ссылки, и они полезут наружу везде, где заголовка нет. В письмах от сайта, в выгрузке в маркетплейс, в RSS, в JSON для мобильного приложения. Директива работает только для HTML-страницы, отданной браузеру.

Дальше, если ресурс по https недоступен физически, апгрейд не спасёт: запрос уйдёт на https, получит ошибку, ресурс не загрузится. Разница с блокировкой только в том, что вы этого не увидите в консоли как проблему смешанного контента, а увидите как обычную сетевую ошибку. Проблему это маскирует.

И третье: заголовок легко забыть при переезде на другой хостинг или при смене веб-сервера. Через год кто-то перенесёт сайт, .htaccess не доедет, и старые ссылки вылезут снова.

Где директива уместна: как временная затычка на время, пока чините базу, и как страховка после починки. Ставить её вместо починки я не советую. Родственную директиву block-all-mixed-content использовать не надо, она объявлена устаревшей и её поведение поглотил апгрейд по умолчанию.

Редиректы и HSTS

Когда страницы вылечены, имеет смысл закрыть вход по http окончательно. Минимальный набор в .htaccess:

RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]

<IfModule mod_headers.c>
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
</IfModule>

Первая часть это обычный 301 на https. Вторая, HSTS, говорит браузеру запомнить на год, что к этому домену надо ходить только по https, вообще без первого http-запроса.

Две оговорки по HSTS, обе из практики.

Первая: includeSubDomains распространяется на все поддомены. Если у вас на old.site.ru живёт древняя версия сайта без сертификата, она станет недоступна. Сначала проверьте, что сертификат покрывает все используемые поддомены.

Вторая: не начинайте с года. Поставьте max-age=300, проверьте сайт неделю, потом поднимайте. Откатить HSTS у уже посетивших сайт браузеров нельзя: они будут помнить директиву до истечения срока, что бы вы ни делали на сервере. Директиву preload и внесение домена в список Chrome без острой необходимости не трогайте, оттуда выписываться долго и муторно.

Про выпуск и автопродление самого сертификата я писал в статье про бесплатный SSL в 2026 году. Смешанный контент и просроченный сертификат это разные болезни, но лечат их обычно в один заход.

Чек-лист проверки после починки

Проходите по пунктам сверху вниз, каждый занимает пару минут.

  • Главная, страница услуги, карточка товара и одна старая статья блога открыты с F12, в консоли пусто.
  • Проверены страницы с формами: форма отправляется, ответ приходит, в консоли нет ошибок сети.
  • Проверен режим инкогнито или другой браузер (в вашем основном сидит кэш и старые сервис-воркеры).
  • Screaming Frog обошёл сайт заново, отчёт Mixed Content пустой.
  • В базе SELECT COUNT(*) по http://вашдомен возвращает ноль или только осмысленные остатки.
  • Все три формы на сайте протестированы после замены в базе (search-replace задевает настройки форм).
  • Проверены письма, которые шлёт сайт: ссылки внутри ведут на https.
  • В Яндекс.Вебмастере через сутки нет новых ошибок в разделе диагностики.
  • Сброшен кэш плагина, кэш CDN и кэш браузера.
  • Сделан свежий бэкап уже вылеченного сайта.

Последний пункт часто пропускают, а он важен: если бэкапы у вас недельные, откат по ошибке через три дня вернёт сайт в состояние до починки.

Как не наступить снова

Смешанный контент возвращается в трёх ситуациях: перенос сайта, смена домена, установка нового плагина или виджета. Все три предсказуемы, поэтому лечатся регламентом.

Работы, которые трогают адреса, всегда прогоняйте на копии. Тестовая площадка стоит копейки и снимает почти все риски такого рода: подробности в статье про staging для маленького сайта. После переноса на новый хостинг проверка на смешанный контент должна быть отдельным пунктом приёмки, а не «потом посмотрим».

Раз в квартал имеет смысл прогонять краулер по сайту целиком. Это пятнадцать минут, и оно ловит не только смешанный контент, но и битые ссылки, которые накапливаются сами собой.

Ещё привычка, которую советую редакторам: не вставлять картинки и iframe копипастом из чужих страниц. Картинку загрузить в медиатеку, iframe брать из официального кода сервиса. Половина проблем приходит именно из копипаста.

Сколько это стоит и когда звать

Разовая чистка смешанного контента на типовом сайте занимает у меня 1-3 часа: диагностика, бэкап, замена в базе, правка шаблона, проверка форм и писем. Сюда же обычно входит настройка редиректов и HSTS. По деньгам это попроектная работа, ориентир по ставкам и пакетам есть на странице тарифов.

Больше времени уходит, когда сайт сидит на конструкторе страниц с данными в JSON, когда домен менялся дважды и в базе лежат остатки от обоих, или когда часть контента приходит из внешней выгрузки, которую тоже надо править. Такие случаи считаю отдельно, после того как посмотрю на сайт.

Делать самому есть смысл, если у вас есть SSH, вы понимаете, что такое дамп базы, и готовы восстановиться из бэкапа при неудаче. Если каждое из этих условий вызывает вопрос, дешевле отдать работу: сломанные сериализованные настройки конструктора восстанавливаются дольше, чем чинится исходная проблема.

Если сайт уже теряет заявки из-за неработающей формы или пустых картинок, напишите через форму на главной. Обычно смотрю за вечер и говорю, сколько там реально работы, без выдуманных часов.

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

Что такое смешанный контент

Ситуация, когда защищённая страница подгружает ресурсы по незащищённому протоколу. Браузер помечает такое соединение как небезопасное, а скрипты и стили может просто заблокировать.

Где искать оставшиеся ссылки

В содержимом записей и настройках в базе, в файлах темы, в настройках плагинов и в подключаемых сторонних ресурсах. Консоль браузера показывает конкретные адреса, с них и начинают.

Как чинить ссылки в базе

Заменой, которая понимает сериализованные значения. Прямой поиск и замена по дампу ломает настройки, потому что в них записана длина строки.

Что делать с чужими ресурсами

Если сторонний сервис не отдаёт ресурс по защищённому протоколу, его меняют или убирают. Принудительное повышение протокола помогает не всегда и маскирует проблему.

Передайте техническую часть в надёжные руки.

Слежу за хостингом, бэкапами и обновлениями. Когда что-то пойдёт не так – звоните мне, не своему программисту, не в спортзал и не маме.

Короткие вопросы по теме

Все вопросы по теме "Безопасность сайта"

Заявка

Обсудим задачу

Напишите имя и телефон, выберите удобный канал. Перезвоню или напишу в течение часа в рабочее время.

Куда удобнее ответить?