Короткий ответ

Страница открылась по https://site.example, но HTML/CSS тянет http://site.example/... или чужой http-CDN. Современный браузер блокирует активный mixed content (скрипты) и предупреждает о картинках. Лечение: исправить URL в шаблоне и БД, не «разрешить небезопасный контент» пользователям. IP origin 10.0.20.40, webroot /var/www/site.example.

Параллельно нужен корректный 301 с HTTP: редирект. CSP upgrade-insecure-requests — вспомогательный костыль, не замена правки данных. Полные заголовки: headers.

Редирект 301 с http→https не убирает mixed: браузер уже на https, а HTML всё ещё содержит http-URL скриптов. Чините разметку и БД. Плагины кэша CSS часто хранят абсолютные http — сброс кэша обязателен после search-replace.

Симптомы и как отличить

  • Консоль: Mixed Content: The page at 'https://site.example/' was loaded over HTTPS, but requested an insecure ....
  • Стили «как без темы», скрипты админки WP мертвы.
  • curl -s https://site.example/ | grep -i 'http://'.
ЧтоНе mixed
Сам HTTPS не открываетсяcert/DNS
404 на /style.css404 после переноса
Редирект-петляHSTS/редирект

Возможные причины

  1. WordPress siteurl/home остались http://.
  2. Контент в wp_posts с абсолютными http.
  3. Хардкод в теме http://site.example/wp-content/....
  4. Плагин CDN с http endpoint.
  5. CSP default-src без https, или наоборот upgrade-insecure-requests отсутствует, а URL не поправили.
  6. Трекинг-пиксели на http.
  7. После миграции search-replace не тронул сериализованные опции.

Диагностика

1. Заголовки и HTML

curl -sI --resolve site.example:443:10.0.20.40 https://site.example/ | grep -iE 'content-security|strict-transport|location'
curl -s --resolve site.example:443:10.0.20.40 https://site.example/ | grep -oE 'http://[^"'\'' ]+' | sort -u | head

2. WordPress опции

sudo -u www-data wp --path=/var/www/site.example option get siteurl
sudo -u www-data wp --path=/var/www/site.example option get home

Должно быть https://site.example без хвоста-слэша или со — как принято у вас, но схема https.

3. Поиск по файлам темы (не по всему uploads вслепую)

sudo grep -R --include='*.php' --include='*.js' --include='*.css' 'http://site.example' /var/www/site.example/wp-content/themes | head

4. CSP в nginx

sudo nginx -T 2>/dev/null | grep -i content-security-policy

Жёсткий CSP без https: для script-src плюс http-URL = блокировка даже после upgrade, если источник другой.

Решение

Снимок БД до search-replace.

sudo -u www-data wp --path=/var/www/site.example db export /root/site.example-before-https.sql

Сценарий A. siteurl

sudo -u www-data wp --path=/var/www/site.example option update siteurl 'https://site.example'
sudo -u www-data wp --path=/var/www/site.example option update home 'https://site.example'

Либо константы в wp-config.php:

define('WP_HOME', 'https://site.example');
define('WP_SITEURL', 'https://site.example');

Сценарий B. Контент и сериализация

sudo -u www-data wp --path=/var/www/site.example search-replace 'http://site.example' 'https://site.example' --all-tables

Не sed по sql-дампу: сломаете длину сериализованных строк → 500.

Сценарий C. Тема/хардкод

Замените на https:// или на относительные //site.example (всё ещё схема-относительные; лучше явный https или функции home_url()).

Сценарий D. Временный upgrade-insecure-requests

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

Браузер перепишет http→https на том же хосте. Чужой http-домен без HTTPS это не спасёт — уберите или найдите https-зеркало.

После зачистки URL можно вписать полноценный CSP, не только upgrade. HSTS — после стабильного HTTPS: HSTS.

Сценарий E. Сторонние скрипты только http

Замените провайдера или выкиньте тег. Не проксируйте чужой http через свой origin без понимания ToS и кэша.

Сценарий F. Кэш, CDN и «в браузере ещё http»

После search-replace HTML origin уже https, а Cloudflare/Nginx fastcgi_cache/proxy_cache отдаёт старую страницу. Purge кэша CDN и локального fastcgi_cache_path. Плагины WP Super Cache / LiteSpeed хранят файлы в wp-content/cache — удаляйте кэш, не uploads.

srcset и JSON в data- атрибутах Elementor часто содержат http. Повторите wp search-replace с --all-tables и сброс CSS Elementor (Regenerate CSS в UI). Проверьте письма WooCommerce: шаблон email-header с http логотипом — это не mixed в браузере витрины, но та же болезнь данных.

CSP upgrade-insecure-requests не поможет, если скрипт грузится с http://third.example без HTTPS-версии: браузер запросит https, получит fail, скрипт умрёт. Тогда либо https-URL провайдера, либо удалить тег.

Для админки WP отдельно откройте DevTools на /wp-admin/: load-scripts.php и load-styles.php должны быть https. FORCE_SSL_ADMIN без починенного siteurl даёт петлю — сначала опции, потом константа.

Не используйте расширение «HTTP Everywhere наоборот» у пользователя как стандарт лечения.

Как проверить, что проблема устранена

  • DevTools → Security: нет mixed.
  • Повторный grep HTML без http://site.example.
  • curl -sI показывает HTTPS и при наличии CSP.
  • Админка WP загружает load-scripts.php без блокировок.

Проверьте внутренние страницы и одну запись со старыми картинками в контенте.

Если не помогло

  • Elementor/кэш CSS с абсолютными http — сброс кэша плагина, не rm -rf wp-content/uploads.
  • FORCE_SSL_ADMIN без siteurl — частично.
  • WebSocket ws:// — нужен wss://.
  • Письма и RSS с http — отдельные шаблоны.
  • В HTML нет http://site.example, но DevTools орёт на http://static.oldcdn.example — это не ваш search-replace, а хардкод темы/виджета. Замените виджет или зеркало https, иначе CSP upgrade не спасёт чужой хост без TLS.
  • Админка зелёная, витрина нет: кэш анонимов на edge. Purge по URL главной и одной записи со старой датой.

Профилактика

  • Сразу выпускать HTTPS в чеклисте переноса.
  • Запретить http-URL в code review тем.
  • Staging с тем же CSP, что прод.
  • После search-replace откройте DevTools на витрине и на /wp-admin/ с отключённым кэшем: active mixed (скрипты) должен быть ноль.

FAQ

//site.example/foo достаточно?

Схема-относительный URL возьмёт https со страницы. На email/PDF не сработает. Предпочтительнее https.

Нужно ли редиректить каждый ассет 301?

Редирект спасёт часть картинок, активные скрипты браузер не подхватит с http. Чините HTML.

CDN http://cdn.site.example

Включите HTTPS на CDN и поправьте URL. Иначе mixed.

Почему grep по uploads молчит, а браузер орёт?

URL внутри CSS в uploads/elementor/css. Ищите http:// в css-кэше.

Apache Header CSP vs nginx add_header

Один слой. Дубль разных политик путает. add_header в location без always может пропасть на 404.