ลดความยุ่งยากในการใช้คลาวด์ พูดคุยกับเจ้าหน้าที่

Home แก้ปัญหา Mixed Content หลังเปลี่ยนเว็บ WordPress เป็น HTTPS

แก้ปัญหา Mixed Content หลังเปลี่ยนเว็บ WordPress เป็น HTTPS

หลังติดตั้ง SSL และเปลี่ยนเว็บ WordPress เป็น HTTPS หลายเว็บพบว่าแถบที่อยู่ของเบราว์เซอร์ไม่ขึ้นสถานะปลอดภัย บางเว็บรูปภาพหาย ฟอนต์ไม่โหลด หรือปุ่มบางปุ่มกดไม่ได้ สาเหตุคือ Mixed Content หมายถึงหน้าเว็บโหลดผ่าน https:// แต่ภายในหน้ายังเรียกไฟล์ย่อย เช่น รูป สคริปต์ หรือ CSS ผ่าน http://

เบราว์เซอร์แบ่ง Mixed Content เป็น 2 ประเภท ประเภทที่อันตรายน้อย เช่น รูป เสียง และวิดีโอ เบราว์เซอร์รุ่นใหม่จะพยายามเปลี่ยนเป็น HTTPS ให้เอง ถ้าไม่ได้ก็ไม่โหลด ส่วนประเภทที่อันตราย เช่น JavaScript, CSS และ iframe จะถูกบล็อกทันที ซึ่งทำให้หน้าเว็บพังได้ บทความนี้อธิบายวิธีหาต้นตอและแก้ให้หมดอย่างถาวร

สิ่งที่ต้องเตรียม

ขั้นตอนที่ 1: หาว่าไฟล์ไหนยังเป็น HTTP

ใช้ Developer Tools ของเบราว์เซอร์

  1. เปิดหน้าที่มีปัญหาด้วย Chrome, Edge หรือ Firefox
  2. กด F12 (macOS กด Cmd + Option + I) แล้วเลือกแท็บ Console
  3. โหลดหน้าใหม่ จะเห็นข้อความลักษณะนี้
Mixed Content: The page at 'https://example.com/' was loaded over HTTPS,
but requested an insecure script 'http://example.com/wp-content/plugins/slider/js/app.js'.
This request has been blocked; the content must be served over HTTPS.

URL ที่อยู่ท้ายข้อความคือไฟล์ต้นเหตุ ให้จดไว้ทั้งหมด และสังเกตโดเมนของไฟล์ ว่าเป็นโดเมนของเราเองหรือโดเมนภายนอก

ใช้คำสั่งค้นหาจาก Command Line

curl -s https://example.com/ | grep -Eo '(src|href)="http://[^"]+"' | sort -u

คำสั่งนี้แสดงลิงก์ src และ href ที่ยังเป็น HTTP ในหน้าแรก (ลิงก์ href ไปหน้าอื่นไม่นับเป็น Mixed Content แต่ควรแก้ไปพร้อมกัน) ข้อจำกัดคือไม่เห็นไฟล์ที่ถูกเรียกจาก CSS หรือ JavaScript ภายหลัง จึงควรตรวจด้วย Developer Tools ควบคู่

ขั้นตอนที่ 2: แก้ URL หลักของเว็บ

ตรวจว่า home และ siteurl เป็น HTTPS แล้ว

wp option get home
wp option get siteurl

หากยังเป็น http:// ให้แก้ที่ Settings > General หรือด้วย wp option update ตามวิธีใน เปลี่ยน URL เว็บไซต์ WordPress

ขั้นตอนที่ 3: แทนที่ URL แบบ HTTP ในฐานข้อมูล

บทความและรูปที่อัปโหลดก่อนเปลี่ยนเป็น HTTPS ยังเก็บ URL แบบเก่าในฐานข้อมูล สำรองแล้วแทนที่ด้วย WP-CLI

wp db export ~/before-https.sql
wp search-replace 'http://example.com' 'https://example.com' --skip-columns=guid --dry-run

ตรวจตัวเลขในบรรทัดสรุป แล้วรันจริงโดยตัด --dry-run ออก หากเว็บเคยใช้ www ให้รันซ้ำกับ 'http://www.example.com' ด้วย และหากใช้ Page Builder ที่เก็บ URL แบบ Escape ให้รันกับ 'http:\/\/example.com' อีกครั้ง

wp search-replace 'http:\/\/example.com' 'https:\/\/example.com' --skip-columns=guid

ผู้ที่ใช้ Elementor มีเครื่องมือในตัวที่เมนู Elementor > Tools > Replace URL (ตำแหน่งอาจต่างกันตามเวอร์ชัน) และหลังเปลี่ยนให้กด Regenerate Files & Data ในหน้าเดียวกัน เพื่อสร้างไฟล์ CSS ใหม่ที่ไม่มี URL แบบ HTTP

หากไม่มี SSH ใช้ปลั๊กอิน Better Search Replace แทนได้ ห้ามใช้ SQL REPLACE() กับทั้งตาราง เพราะจะทำให้ข้อมูลแบบ Serialized เสีย

ขั้นตอนที่ 4: แก้ URL ที่ฝังอยู่ในไฟล์ธีมและปลั๊กอิน

หาก Console ยังแจ้งไฟล์ที่เป็นของเว็บเราเอง ต้นเหตุมักอยู่ในไฟล์ธีม เช่น header.php หรือไฟล์ CSS

cd /var/www/example.com
grep -rn "http://example.com" wp-content/themes/ wp-content/plugins/ --include=*.php --include=*.css --include=*.js

แก้ไฟล์ที่พบให้เป็น https:// หรือใช้ฟังก์ชันของ WordPress แทนการพิมพ์ URL ตรง เช่น get_template_directory_uri() หากไฟล์อยู่ในธีมหลัก การแก้จะถูกเขียนทับเมื่ออัปเดตธีม ควรแก้ผ่าน Child Theme หรือแจ้งผู้พัฒนาธีม

ไฟล์จากโดเมนภายนอก

หากไฟล์มาจากโดเมนภายนอก เช่น สคริปต์ของบริการเก่า ให้ลองเปิด URL เดียวกันด้วย https:// ถ้าเปิดได้ให้แก้ลิงก์เป็น HTTPS ถ้าเปิดไม่ได้ แปลว่าบริการนั้นไม่รองรับ HTTPS ต้องเลิกใช้หรือหาบริการอื่นแทน

ขั้นตอนที่ 5: กรณีเว็บอยู่หลัง Cloudflare หรือ Reverse Proxy

ถ้า SSL จบที่ Cloudflare หรือ Load Balancer แล้วส่งต่อมาที่เซิร์ฟเวอร์ด้วย HTTP ฟังก์ชัน is_ssl() ของ WordPress จะเข้าใจว่าเป็น HTTP และสร้างลิงก์ไฟล์เป็น http:// หรือทำให้ Redirect วนซ้ำ วิธีที่ดีที่สุดคือใช้ HTTPS ตลอดเส้นทาง (Cloudflare โหมด Full (strict)) ดู เลือก SSL/TLS Mode บน Cloudflare ให้ถูกต้อง หากเลี่ยงไม่ได้ ให้เพิ่มใน wp-config.php ก่อนบรรทัด /* That's all, stop editing! */

if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https' ) {
    $_SERVER['HTTPS'] = 'on';
}

ใช้โค้ดนี้เฉพาะเมื่อเซิร์ฟเวอร์รับการเชื่อมต่อจาก Proxy ที่เชื่อถือได้เท่านั้น เพราะผู้ใดก็ส่งหัว X-Forwarded-Proto มาเองได้

ขั้นตอนที่ 6: ตัวช่วยชั่วคราว upgrade-insecure-requests

ระหว่างที่ยังแก้ไม่ครบ สามารถสั่งให้เบราว์เซอร์เปลี่ยนคำขอ HTTP ภายในหน้าเป็น HTTPS อัตโนมัติด้วย Header นี้ บน Nginx

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

บน Apache (ต้องเปิด mod_headers)

Header always set Content-Security-Policy "upgrade-insecure-requests"

วิธีนี้ได้ผลเฉพาะไฟล์ที่มีเวอร์ชัน HTTPS อยู่แล้ว และไม่ได้แก้ข้อมูลจริง ควรใช้เป็นทางผ่านระหว่างทำขั้นตอนที่ 3 และ 4 ให้ครบ หากเว็บมี Content-Security-Policy อยู่แล้ว ให้เพิ่มคำสั่งนี้ลงในค่าเดิม ไม่ใช่เพิ่มหัวใหม่ซ้อน ดูเพิ่มเติมที่ ตั้งค่า HTTP Security Headers ให้เว็บไซต์

ตรวจสอบผลลัพธ์

  • ล้าง Cache ของปลั๊กอิน Cache, CDN และเบราว์เซอร์ แล้วรัน wp cache flush
  • เปิดหน้าเว็บหลายประเภท (หน้าแรก บทความ หน้าสินค้า หน้าติดต่อ) ด้วยแท็บ Console เปิดไว้ ต้องไม่มีข้อความ Mixed Content
  • รันคำสั่ง curl ในขั้นตอนที่ 1 อีกครั้ง ต้องไม่พบลิงก์ src="http://
  • ค้นหาในฐานข้อมูล wp db search 'http://example.com' --stats ควรเหลือเฉพาะคอลัมน์ GUID

ปัญหาที่พบบ่อย

แก้ครบแล้วแต่ยังเห็น Mixed Content

หน้าที่เห็นอาจมาจาก Cache เก่า ทั้งปลั๊กอิน Cache, ไฟล์ CSS ที่รวมไฟล์ไว้ (Minify/Combine) และ CDN ให้ล้างทุกชั้นแล้วทดสอบใหม่ด้วยหน้าต่าง Incognito

Redirect วนซ้ำหลังเปลี่ยนเป็น HTTPS

มักเกิดจากตั้ง Cloudflare เป็นโหมด Flexible ร่วมกับการบังคับ HTTPS ที่เซิร์ฟเวอร์ ให้แก้ตามขั้นตอนที่ 5

รูปในบทความเก่ายังเป็น HTTP แม้รัน search-replace แล้ว

ตรวจว่ารันครบทุกรูปแบบของโดเมน (มีและไม่มี www) และปลั๊กอินที่เก็บข้อมูลในตารางของตัวเองต้องเพิ่ม --all-tables-with-prefix

หากทำตามขั้นตอนแล้วยังติดปัญหา สามารถติดต่อทีมสนับสนุนของ THAI DATA CLOUD ได้ที่ https://thaidata.cloud/contact/

ผู้ให้บริการคลาวด์ไทย
เพื่อธุรกิจของคนไทย

"มุ่งมั่น" และ "มั่นคง"
พร้อมรับมือทุกการเติบโต
Trust Cloud
คลาว์ที่ปลอดภัย
คือรากฐานที่มั่นคง
cloud security