Коротка відповідь

Порядок дій, якщо часу мало:

  • Відкрийте проблемну сторінку і натисніть F12 → Console. Браузер сам напише адреси ресурсів, які заблокував або підтягнув по http. Це 90% діагностики.
  • Лагодьте базу, а не окремі сторінки. У WordPress - wp search-replace по всіх таблицях, з бекапом: таких адрес зазвичай сотні.
  • Пройдіться по шаблону. grep -rn "http://" wp-content/themes/ знаходить зашиті в код адреси, яких у базі немає.
  • Розберіться із зовнішніми скриптами. Старий лічильник чи віджет без https з вашого боку не полагодити - його замінюють або прибирають.
  • Увімкніть примусовий редирект на https у панелі, щоб http-версія сайту більше не відкривалась.

Якщо сертифікат ви ще не вмикали, спершу прочитайте, навіщо сайту SSL і як його увімкнути. Ця стаття - про те, що йде далі: коли сертифікат уже є, а сайт усе одно «не безпечний».

Що таке змішаний контент і як він проявляється

Сторінка сайту - це не один файл: разом із HTML браузер довантажує десятки окремих ресурсів зі своїми адресами - стилі, скрипти, шрифти, картинки, відео, карти, віджети, лічильники. Коли сама сторінка приїхала по захищеному https, а всередині неї є посилання на ресурс по звичайному http, виходить змішаний контент: адресний рядок обіцяє захист, але частина вмісту йде відкритим каналом, де її можна прочитати чи підмінити дорогою. Тому браузер або лається, або мовчки блокує такий ресурс.

Вилазить це після переїзду старого сайту, де адреси з http:// роками накопичувались у статтях, налаштуваннях теми і картках товарів. Сертифікат їх не бачить - він відповідає за канал, а не за вміст сторінки.

Порада «подивіться на замок у адресному рядку» застаріла: у Chrome іконку замка прибрали ще у версії 117 у вересні 2023 року, замість неї там значок із повзунками, а статус з'єднання видно після кліку по ньому. Та на практиці відвідувач попереджень не читає - він бачить биті картинки, порожнє місце замість відео і кнопку, яка не реагує на клік. Скарги приходять саме такі.

Що браузер робить із http-ресурсом на https-сторінці

Реакція залежить від типу ресурсу: скрипт може переписати всю сторінку, тому ставлення до нього суворе.

Що вантажиться по httpЩо робить сучасний браузерЯк це бачить відвідувач
Скрипт <script src="http://...">блокує повністюслайдер не крутиться, меню не відкривається, кошик не рахує
Стилі <link rel="stylesheet">блокує повністюверстка розсипається, сайт «голий»
Фрейм: карта, відео, віджетблокує повністюпорожній прямокутник на місці блока
Запити форм (fetch, XHR)блокує повністюформа крутиться і не відправляється
Картинки <img>пробує сам підставити https, не вийшло - не показуєбиті картинки або порожні місця
Звичайне посилання <a href="http://...">не блокує, це не змішаний контентпрацює, але через зайвий редирект

Звідси висновок: сайт цілий, а замка немає - винні картинки. Поїхала верстка чи не працюють кнопки - шукайте скрипт або стилі.

Крок 1. Знайти всі http-адреси

Через консоль браузера

Відкрийте сторінку, натисніть F12 і перейдіть на вкладку Console. Повідомлення виглядає так: Mixed Content: The page at 'https://example.com/'
was loaded over HTTPS, but requested an insecure element
'http://example.com/img/logo.png'
. Наприкінці рядка - точна адреса винуватця.

Корисна дрібниця: на вкладці Network у полі фільтра можна ввести mixed-content:all - і залишаться тільки проблемні запити. Зручно, коли сторінка тягне сотню файлів.

Якщо консоль незвична, відкрийте код сторінки (Ctrl+U) і пошукайте http://: так знайдуться навіть ті адреси, які браузер уже мовчки перетворив на https і в консоль не вивів.

Одразу по всьому сайту

Одна сторінка не показує всієї картини: змішаний контент часто сидить у старих статтях і картках товарів. Пройтись по всіх адресах із карти сайту можна одним рядком:

curl -s https://example.com/sitemap.xml | grep -o 'https://[^<]*' \
| while read u; do curl -s "$u" | grep -o 'http://[^"]*'; done \
| sort -u | head -50

На виході - унікальний список http-адрес, які реально трапляються в коді. Далі лишається зрозуміти, звідки кожна береться.

Крок 2. Полагодити базу даних

Спершу бекап. Масова заміна по базі - операція без кнопки «скасувати». Копії на моїх серверах робляться автоматично, але перед такою правкою я все одно роблю ще одну руками, в розділі бекапів DirectAdmin.

Для WordPress правильний інструмент - WP-CLI, він стоїть на сервері за замовчуванням. Спочатку прогін «на суху»: wp search-replace 'http://example.com' 'https://example.com' --all-tables --dry-run. Команда покаже кількість збігів у кожній таблиці; якщо цифри адекватні, запускайте те саме без --dry-run.

Чому не можна просто зробити UPDATE ... REPLACE() у phpMyAdmin: WordPress зберігає налаштування тем і конструкторів сторінок серіалізованими, тобто поруч із текстом записана його довжина. Адреса з https на один символ довша, записана довжина лишається старою - і блок зникає зі сторінки або конструктор перестає відкривати макет. WP-CLI перераховує довжини сам, без SSH те саме зробить плагін типу Better Search Replace.

Не забудьте про два поля в Налаштування → Загальні: адреса WordPress і адреса сайту мають бути з https. Якщо вони прописані в wp-config.php через WP_HOME і WP_SITEURL, правити треба там - в адмінці поля неактивні. У магазинів на OpenCart адреси лежать не в базі, а в константах HTTP_SERVER і HTTPS_SERVER у двох файлах config.php; після правки чистіть system/storage/cache.

Крок 3. Пройтись по шаблону і плагінах

Частина адрес у базу ніколи не потрапляла - вони зашиті в коді теми. Знаходяться по SSH одним рядком:

grep -rn "http://" wp-content/themes/ wp-content/plugins/ | grep -v "schema.org\|w3.org\|xmlns"

Другий grep прибирає адреси зі схем і просторів імен - вони не завантажуються і на змішаний контент не впливають. Усе інше дивіться очима: http://fonts.googleapis.com, http://code.jquery.com, стара CDN-адреса слайдера.

Раніше радили писати адреси без протоколу - //example.com/file.js. Зараз так робити не варто: сенсу вже немає, а кеші й аудит-інструменти такі адреси плутають. Пишіть https:// явно. Якщо SSH не ваш формат, те саме робиться через файловий менеджер у панелі - там є пошук по вмісту файлів.

Крок 4. Зовнішні ресурси, які ви не контролюєте

Найнеприємніша категорія: сайт тягне чужий файл, а той віддається лише по http. Варіантів рівно три.

  • Перевірити, чи є https-версія. У 99 випадках зі 100 вона є: підставте https в адресу і відкрийте. Google Fonts, jQuery CDN, YouTube, Google Maps давно працюють лише по https.
  • Забрати файл до себе. Картинку чи шрифт простіше скачати і покласти у свою папку. Заодно сторінка стане швидшою - менше зовнішніх з'єднань, про це докладніше в статті, чому сайт повільний.
  • Прибрати повністю. Лічильник, який не оновлювався з 2016 року, віджет партнера, банер померлої біржі - дія одна: видалити рядок.

Окремо про підозрілі знахідки: якщо в коді є http-скрипт з адреси, якої ви туди не ставили, справа може бути не в переїзді на https, а в зламі. Тоді послідовність інша - я розписав її в статті «Сайт зламали: порядок дій, поки не пізно».

Крок 5. Закрити http на рівні сервера

Примусовий редирект

Поки стара http-версія відкривається, на неї ведуть посилання зі старих листів, візиток і чужих сайтів. У DirectAdmin писати код не треба: у налаштуваннях домену є перемикач Примусовий SSL із https переадресацією (Force SSL with https redirect). Увімкнули - і весь http-трафік іде на https на рівні веб-сервера, ще до запуску PHP.

Якщо редирект робите в .htaccess, правило має стояти першим, до правил CMS:

RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

Пастка, на яку натикаються регулярно: якщо перед сайтом стоїть Cloudflare у режимі Flexible, сервер бачить запит як http навіть тоді, коли відвідувач прийшов по https. Правило вище кидає його на https, Cloudflare повертає назад - і виходить нескінченна петля з помилкою ERR_TOO_MANY_REDIRECTS. Лікується так: режим SSL у Cloudflare перемикається на Full (strict), а в умові використовується %{HTTP:X-Forwarded-Proto} !https.

upgrade-insecure-requests - латка, а не ремонт

Є спосіб змусити браузер самому підставляти https усім ресурсам сторінки - заголовок Content-Security-Policy: upgrade-insecure-requests, у .htaccess це рядок Header always set Content-Security-Policy "upgrade-insecure-requests". Виглядає як чарівна кнопка, але чесно про обмеження: якщо ресурсу по https не існує, він так само не завантажиться, а старі адреси лишаються в базі й вилізуть знову при переїзді чи зміні теми.

Те саме стосується плагінів на кшталт Really Simple SSL: вони підміняють адреси на льоту, при кожному завантаженні сторінки. Нормальна швидка допомога, коли сайт треба підняти просто зараз, але це зайва обробка і проблема, залишена в даних. Правильний порядок - полагодити базу і шаблон, а латку потім зняти.

HSTS - в останню чергу

HSTS наказує браузеру ходити на ваш домен виключно по https протягом заданого часу. Вмикати його можна лише тоді, коли змішаного контенту вже немає: помилилися - швидко відкотити не вийде, браузери запам'ятали правило. Перемикач є в тих самих налаштуваннях домену, поруч із примусовим SSL.

Що ще підтягнути після переходу

Сторінки полагодили - лишається те, що забувають найчастіше. Внутрішні посилання з http:// у меню й банерах сюди теж входять: замок вони не ламають, але кожен клік іде через зайвий редирект.

Що перевіритиДеОзнака, що не зроблено
Канонічні адреси і og-тегиналаштування SEO-плагіна, шаблону коді сторінки canonical з http
Карта сайтуsitemap.xmlадреси в <loc> починаються з http
Search Consoleкабінет Google: http і https - різні ресурсистатистика обірвалась у день переїзду
Кешплагін кешування, Cloudflare, браузерправки зроблені, а сторінка стара

Чек-лист: перевірити за 15 хвилин

  1. Відкрийте головну, одну статтю і сторінку контактів, натисніть F12 → Console. Порожньо - добре.
  2. Окремо перевірте сторінку з формою, кошиком або картою: там ховаються заблоковані скрипти і фрейми.
  3. Прогоніть сайт по карті сайту командою з Кроку 1.
  4. Зробіть бекап і запустіть wp search-replace спершу з --dry-run.
  5. Перевірте grep -rn "http://" по темі й плагінах.
  6. Переконайтесь, що в налаштуваннях WordPress обидві адреси - з https.
  7. Увімкніть примусовий SSL у панелі й відкрийте http://-версію: має перекинути на https.
  8. Почистіть кеш (плагін, CDN, браузер) і перевірте сторінки ще раз.
  9. Перевірте sitemap.xml, канонічні адреси та Search Console.
  10. HSTS вмикайте останнім, коли попередні дев'ять пунктів зелені.

Якщо на якомусь пункті сайт зламався або незрозуміло, звідки береться конкретна http-адреса, - напишіть мені у Telegram: подивлюсь сторінку і скажу, де сидить джерело - у базі, у темі чи в чужому скрипті.

Підсумок

Змішаний контент - це не проблема сертифіката і не помилка хостингу, а старі http-адреси всередині вашого сайту. Порядок лікування завжди один: знайти через консоль браузера, масово замінити в базі правильним інструментом, пройтись по шаблону, розібратися із зовнішніми скриптами, закрити http редиректом на рівні сервера.

З мого боку все потрібне стоїть на всіх тарифах однаково: безкоштовний Let's Encrypt з автопродовженням, перемикачі примусового SSL і HSTS у DirectAdmin 1.710, PHP 8.3, WP-CLI для масових замін, щоденні офсайт-бекапи і захист Cloudflare. Доступність заявлена на рівні 99.9%, «сто відсотків» не буває ні в кого. А прибрати http-адреси з вашого контенту за вас не може ніхто - це робота всередині сайту: або ви, або я руками.

Тарифи відрізняються лише потужністю: Старт - 1 ядро, 2 GB RAM і 10 GB NVMe на 1 сайт за 142 грн/міс, Оптимал - 2 ядра, 3 GB RAM і 20 GB на 3 сайти за 229 грн/міс, Бізнес - 3 ядра, 4 GB RAM і 30 GB на 5 сайтів за 350 грн/міс, Pro - 4 ядра, 8 GB RAM і 100 GB на 10 сайтів за 800 грн/міс. Ціни за річної оплати, помісячно Старт виходить 479 грн/міс. Порівняння планів - на сторінці тарифів хостингу, а оформити Старт - це кілька хвилин.

І остання порада: після кожного великого оновлення сайту витрачайте п'ять хвилин на F12 → Console. Половина «раптових» проблем зі змішаним контентом - це не переїзд на https, а новий плагін із чужим http-скриптом усередині.