網址列的鎖頭圖示不見了、Chrome 跳出「連線不是私人連線」——SSL 憑證明明已經裝好,後台一般設定裡的兩個網址欄位也早就改成 https,這種警告卻還在。清過瀏覽器快取,退出重新登入,甚至換一個乾淨的佈景主題測試,畫面上的警示照樣跳出來。
問題通常不在 SSL 憑證本身,而在 mixed content(混合內容),也就是頁面主體用 HTTPS 載入,裡頭卻還混著幾個透過 http 抓回來的資源,可能是一張圖片的絕對網址,也可能是選單裡一個沒人記得的自訂連結。瀏覽器看到這種情況,輕則只是拿掉鎖頭當提醒,重則直接把整段程式碼或整支表單擋下來,讓網站的某個功能整個失效。
排查的關鍵不是把出現警告的每一頁都手動改一遍,而是先弄清楚這些殘留的 http 網址實際存在資料庫裡的哪一張表、哪一個欄位,再挑一個不會把序列化資料弄壞的方式去修。
mixed content 是什麼?決定瀏覽器擋不擋的可升級與可封鎖內容
Google 官方的 web.dev 對 mixed content 的定義很直接,指一個頁面最初的 HTML 透過安全的 HTTPS 連線載入,但頁面內其他資源,像是圖片、影片、樣式表、指令碼,卻是透過不安全的 HTTP 通訊協定抓回來的,這種情況就叫混合內容。瀏覽器會依照資源類型把混合內容分成兩種處理方式,這個分類直接決定警告跳出來之後,網站的某個功能會不會真的壞掉。
第一種是可升級內容:透過 src 屬性載入的圖片、CSS 的 background-image/border-image、<audio>/<video> 的 src,瀏覽器偵測到是 http 就自動幫你升級成 https 再嘗試載入,抓不到才顯示破圖或播放失敗,通常不會讓整頁的功能跟著掛掉。第二種是可封鎖內容,涵蓋的範圍廣得多:<script> 的 src、<link>(含樣式表)的 href、<iframe> 的 src、fetch()/XMLHttpRequest 這類非同步請求、CSS 裡所有的 url() 值(@font-face、cursor 等)、透過 srcset 或 <picture> 載入的圖片,還有網頁字型,瀏覽器直接擋下,不會嘗試升級。選單跑版、表單送不出去、外掛的某個互動功能突然失靈,多半就是可封鎖內容被擋掉的結果。
還有一個容易忽略的例外,目標網址如果是純 IP 位址而不是網域名稱,即使原本屬於可升級的類型,一樣會被直接擋下,不會嘗試升級。同樣是 <img> 的 src,寫成網域名稱會被升級成 https 再載入,寫成 IP 位址就直接擋掉。想知道自己的網站目前踩到哪一種,不必用猜的,Chrome 開發人員工具裡的「問題」(Issues)分頁,每偵測到一筆混合內容被升級或被封鎖都會記錄下來,是實際排查時最先該打開的地方。

兩個網址欄位改成 https,警告卻沒有跟著全部消失
後台「設定→一般」裡其實只有兩個網址欄位:「WordPress 位址(URL)」與「網站位址(URL)」。WordPress 官方文件把兩者的分工講得很清楚:「網站位址」是使用者在瀏覽器打進去、實際瀏覽這個網站時用的網址;「WordPress 位址」則是 WordPress 核心程式檔案實際存放的位置。多數安裝兩者是同一個網域,但功能不同,兩個欄位都該用 https:// 開頭,結尾也不要留斜線。
很多人以為把這兩格改成 https、SSL 憑證也裝好,問題就解決了,結果警告還在。原因很直接,這兩個欄位只決定 WordPress 從現在開始產生新連結時要用哪個開頭,並不會回頭去改資料庫裡早就存好的舊網址。文章裡三年前貼的一張圖、選單裡兩年前加的一個自訂連結,協定欄位當時打的是 http,不會因為改了這兩格設定就自動變成 https。
先裝一個標榜能一次解決 SSL 問題的外掛,效果也類似。這類外掛多半是在頁面輸出的那一刻,把偵測到的 http 網址即時換成 https 再送到瀏覽器,原始資料本身完全沒有被清乾淨。它能讓警告暫時消失,卻只是暫時貼上的膏藥,一旦哪天換版型、換外掛,原本被攔截改寫的邏輯不在了,警告很可能一夕之間全部重新冒出來。真正一勞永逸的做法,還是得找到資料庫裡確切殘留的位置,把來源本身改掉。
瀏覽器主控台的警告訊息指出確切的殘留網址
與其憑印象亂猜是哪張圖還在用 http,不如直接讓瀏覽器告訴你答案。在頁面上按右鍵選「檢查」,或用鍵盤快捷鍵 Ctrl+Shift+I(Mac 用 Cmd+Option+I)打開開發人員工具,切到「主控台」(Console)分頁後重新整理頁面。MDN 官方文件說明,開發人員主控台會在混合內容被升級或被封鎖時顯示警告訊息,這是用來除錯的標準做法,訊息裡通常會完整列出那個以 http:// 開頭的網址,直接複製下來即可,不必自己拼湊。
如果主控台的訊息不夠明確,可以改看「問題」(Issues)分頁或「網路」(Network)分頁。web.dev 中文版指出,Chrome 每偵測到一筆混合內容或自動升級一筆被動內容,都會記錄在開發人員工具的「問題」分頁裡,通常能看得更完整。
拿到那個網址之後,先做一個簡單的測試,把 http:// 換成 https://,直接貼到網址列打開看看。打得開,代表這個資源本身有 https 版本,問題單純是資料庫或設定裡的網址沒更新,後面按部就班處理即可;打不開,代表這個資源根本沒有 https 版本,得另外想辦法,像是換一個支援 https 的來源,或者乾脆移除。
排查時別只測首頁。舊文章、有嵌入內容或表單的頁面往往各自有各自的殘留網址,首頁乾淨不代表整站乾淨,值得把主要版型都各測一次再下結論。
文章內文、選單和小工具裡各自藏著的殘留網址
排查完瀏覽器丟出來的線索,下一步是回頭找這個網址原本存在後台的哪裡。三個最常見的角落是文章與頁面內文、選單自訂連結、小工具,三者有一個共通點,多半不會出現在後台一般的「搜尋文章」欄位裡,編輯畫面上肉眼也看不出通訊協定,得先知道各自實際存在哪張資料表、哪個欄位,才找得到。以下逐一拆開來看。

頁面內文與自訂 HTML 區塊裡的絕對網址殘留
這是最容易被找到、也最多人以為這樣就找完了的一種。寫在文章正文、圖片區塊、自訂 HTML 區塊裡的絕對網址,存在資料庫 wp_posts 資料表的 post_content 欄位裡,多半是 SSL 裝之前就貼進去的圖片網址或內嵌程式碼。打開內容編輯器用 Ctrl+F 搜尋 http://,常常一下子就找得到好幾筆。
WordPress 官方文件示範過對這一欄直接下 SQL 取代的寫法:
UPDATE wp_posts SET post_content = REPLACE(post_content,'www.domain.com/wp-content/uploads','www.domain.com/images');Code language: SQL (Structured Query Language) (sql)
這種直接 REPLACE 的做法之所以在這裡安全,是因為 post_content 存的是純文字內容,不是序列化資料,不會遇到後面小工具那種欄位會踩到的問題。同一份文件也特別警告,wp_posts 裡還有一個 guid 欄位絕對不能碰,因為它是 RSS 訂閱閱讀器判斷一篇文章有沒有被讀過的唯一依據,一旦改掉,原本訂閱這個網站的讀者,閱讀器裡的舊文章可能會被當成新文章重新推播一次。
選單自訂連結藏在文章搜尋觸及不到的欄位裡
這是排查過程中最容易讓人白忙一場的一關。外觀選單裡加的「自訂連結」,網址不是存在選單項目的標題或內文裡,而是存在 wp_postmeta 資料表裡,meta_key 叫做 _menu_item_url。WordPress 官方函式手冊裡,wp_setup_nav_menu_item() 這支函式的原始碼寫得很清楚:當一個選單項目不是文章、不是文章類型彙整頁、也不是分類法時,就會被視為自訂連結,網址直接用 get_post_meta() 從 _menu_item_url 這個欄位讀出來,完全不經過 post_content 或 post_title。這也是為什麼在文章列表用關鍵字搜尋,或用一般的「搜尋貼文」功能,永遠都找不到選單裡的殘留網址。
想找出來,得直接查 wp_postmeta 這張表,或是回到外觀選單介面,把每一個自訂連結項目展開,一筆一筆肉眼核對協定。如果佈景主題用的是區塊主題(FSE),選單交給「導覽」區塊管理,情況會不太一樣,網址直接寫在對應的 wp_navigation 貼文的區塊內容裡,等於回到跟內文一樣的處理方式,不是存在 _menu_item_url 裡。動手之前先確認自己用的是哪一種選單機制,才不會查錯地方。
小工具序列化陣列包住的網址看不出通訊協定
文字小工具、自訂 HTML 小工具這類的設定值,整個是以 PHP 序列化陣列的形式存在 wp_options 資料表裡,選項名稱通常是 widget_text、widget_custom_html 這類。麻煩的地方在於,一筆設定裡常常同時包著標題、內文、對齊方式好幾個欄位,全部揉在同一個字串值裡。打開後台的小工具編輯畫面雖然看得到文字內容,畫面上卻完全看不出這段文字裡有一個網址還在用 http,得回到資料庫層級才看得出協定。
WordPress 官方文件也提到,除了核心的 siteurl、home 這兩個選項之外,wp_options 裡通常還有其他 option_value 需要更新,常見的像是上傳路徑,以及依實際安裝的外掛而定的項目,例如小工具、統計外掛、留言板、網站地圖產生器留下的設定。文件建議,要修正含舊網址的小工具,可以直接回到外觀→小工具的介面裡編輯,但直接對這整個欄位做 SQL 取代其實暗藏風險,貿然一行指令下去很可能弄壞序列化資料。
資料庫裡的殘留網址,查詢盤點優先於直接替換
找到大概的位置之後,不要急著馬上執行取代。正確的排查順序是先看有多少、在哪裡,再決定要用哪一種方式修。有 phpMyAdmin 或主機商提供的資料庫管理介面的話,直接對 wp_posts、wp_postmeta、wp_options 這三張表分別下 SELECT 查詢,用 LIKE '%http://你的網域%' 抓出命中的資料列,先看筆數與內容範圍,確認命中的都是真的該改的,而不是一開始就下 UPDATE。
主機環境如果裝了 WP-CLI,可以先跑一次預覽:
wp search-replace 'http://你的網域' 'https://你的網域' --dry-runCode language: Bash (bash)
WordPress 官方的 WP-CLI 指令文件說明,--dry-run 會執行整個搜尋取代流程並顯示報告,但不寫回資料庫,等於是資料庫層級的預覽,列出哪些表、哪些欄位、預計要改幾筆。看過這份報告、確認命中範圍合理,再考慮下一步要用哪種工具真正執行取代。
序列化資料的長度前綴讓一行 SQL 替換悄悄壞掉頁面
前面之所以一直強調先查、再決定用哪種方式,而不是直接對 wp_options 下一句 UPDATE ... REPLACE,是因為 PHP 的序列化格式裡藏著一個容易被忽略的機關。序列化字串會把每個值連同它的位元組長度一起記下來,例如一段內容可能被包成 s:12:"http://..." 這樣的形式,前面的數字 12 代表後面雙引號裡字串的實際長度。
如果直接對這種欄位下 UPDATE wp_options SET option_value = REPLACE(option_value, 'http://', 'https://'),http:// 換成 https:// 會讓字串多出一個字元,但序列化格式裡原本記錄的長度數字不會跟著自動更新,於是這筆資料的長度標記跟實際內容就對不起來。WordPress 官方文件的原文警告講得很直接,對整個資料庫做搜尋取代,可能會造成資料序列化的問題,因為部分佈景主題與小工具會把資料的長度記錄下來一起儲存,長度一變東西就壞了。
這種壞法特別讓人摸不著頭緒,因為它不會跳出任何錯誤訊息。WordPress 讀到長度對不上的序列化資料時會直接解析失敗,實際看到的症狀往往是那個小工具或選單設定憑空消失,或是整個跳回空白的預設狀態,畫面上沒有半點提示告訴出了什麼事,很容易被誤以為是別的原因造成的,查了老半天才發現源頭是那一行看似無害的 SQL。

懂序列化格式的工具才能安全修改資料庫裡的殘留網址
知道問題出在哪之後,做法就簡單了,換一個懂得序列化格式的工具,而不是賭一次手動 SQL。有後台操作權限的話,Better Search Replace 這類搜尋取代外掛是最直接的路;有伺服器操作權限、主機商裝了 WP-CLI 的話,wp search-replace 指令更適合批次處理。兩條路都行,差別只在有沒有後台以外的操作權限。
不管走哪一條,開始前都要先備份資料庫,並且先跑一次只看報告、不寫入的預覽。外掛通常有 dry run 選項,WP-CLI 就是前面提到的 --dry-run,確認命中筆數合理之後再真正執行。
WP-CLI 還有幾個實用的參數,想改得更保險可以搭配使用。--precise 會強制改用 PHP 逐筆處理,而不是純 SQL,處理巢狀的序列化結構更徹底,只是速度比較慢;--recurse-objects 用來處理巢狀物件,預設就是開啟的;--skip-columns=guid 則是明確排除 guid 欄位,避免不小心改到前面提過絕對不能動的識別碼。官方指令文件也提到,search-replace 這個指令本身就會以智慧的方式處理 PHP 序列化資料,而且不會更動主鍵值,這正是它比純 SQL 更安全的原因。
改完之後別忘了清一次快取,頁面快取外掛、物件快取、CDN 快取都各自清一次,否則舊版頁面內容還會留在快取裡,讓警告看起來像沒被修好。
外掛、佈景主題與外部資源之外,反向代理也是警告來源
資料庫都清乾淨了,警告卻還在,問題多半出在資料庫以外的地方。外掛或佈景主題的程式碼裡如果寫死了某個 http 網址,例如載入字型、追蹤碼、外部素材的程式碼,資料庫怎麼查都查不到,因為這個網址是程式執行當下才即時印出來的。遇到這種情況,得回頭檢查外掛或佈景主題的設定裡有沒有可以修改的欄位,或是看看有沒有更新版本已經修掉這個問題。
第三方嵌入內容,像是外部影片、地圖、字型、徽章類的小工具,如果來源本身根本不支援 https,資料庫端沒有辦法解決,只能換一個支援 https 的來源,或者整段移除。MDN 官方文件建議,避免混合內容最好的做法是把所有內容都改用 HTTPS 提供:自家網域的資源改成相對連結或 HTTPS 連結,外部資源如果有 HTTPS 版本也一併改用,真的沒有的話就得考慮這個資源還要不要留著。
站台前面如果架了反向代理或 CDN 服務,情況會更複雜一些。這類架構有時候會讓 WordPress 判斷不出目前這個請求是不是 HTTPS,於是自己生成的連結又跑出 http 開頭,這種情況要檢查的是 HTTPS 偵測相關的設定,不是資料庫內容本身。想從根本上解決,也可以參考 MDN 提到的 CSP 的 upgrade-insecure-requests 指示詞,讓瀏覽器主動把所有請求升級成 HTTPS,連原本會被封鎖的可封鎖內容都一併涵蓋進去。
清乾淨之後的驗證步驟與長期預防習慣
修完不代表結束,還要確認真的清乾淨了。頁面快取外掛、物件快取、CDN 快取都各自清過一輪之後,打開無痕視窗重新整理,主控台再檢查一次,確認沒有殘留的 http 訊息跳出來。MDN 官方文件也把瀏覽網站各頁並檢查開發人員主控台的混合內容警告,列為驗證網站是否乾淨的做法之一,搭配在瀏覽器裡停用所有混合內容後測試頁面是否還能正常運作一起做,更能確認功能沒有被悄悄擋掉。
不要只測首頁。有嵌入內容、有表單、流量比較大的幾個頁面,值得各自都測一次,前面查過就知道,殘留網址常常只集中在特定版型或特定區塊,首頁乾淨不等於整站乾淨。
往後新增內容、安裝或更新外掛與佈景主題之後,養成打開主控台快速看一眼的習慣,比事後大海撈針省事得多。多數警告在剛冒出來的當下,通常只牽涉一兩個新加的資源,定位起來比事後累積一堆才回頭查快很多。
mixed content 從來不是裝好 SSL 就一次解決、從此不會再出現的狀態。網站的內容持續在長,舊文章會被翻出來分享,選單和小工具也會隨時被編輯,只要有人在某個角落貼回一個 http 開頭的絕對網址,警告就可能重新冒出來。把先看瀏覽器怎麼分類,再回頭定位是資料庫哪一張表、哪一個欄位,最後才挑一個不會傷到序列化資料的工具去修這套順序記住,下次再看到網址列的鎖頭消失,就不必從頭猜起。
