SSD Nodes Learn Hosting plans →
คู่มือ Matt Connorโดย Matt Connor

วิธีติดตั้ง LAMP Stack บน Ubuntu 24.04 พร้อม PHP-FPM

คู่มือติดตั้ง LAMP Stack บน Ubuntu 24.04 ครบถ้วนทั้ง Apache, MariaDB, และ PHP 8.3 ผ่าน PHP-FPM พร้อมวิธีตั้งค่า unix_socket auth และติดตั้ง SSL ฟรีด้วย Certbot อย่างถูกต้อง

สิ่งที่คุณกำลังสร้าง

LAMP stack ประกอบด้วยส่วนประกอบ 4 ส่วนที่ทำงานร่วมกันบนเซิร์ฟเวอร์ Ubuntu 24.04 ได้แก่ Linux เป็นฐาน, Apache ทำหน้าที่ตอบสนอง HTTP, MariaDB ทำหน้าที่จัดเก็บข้อมูล และ PHP 8.3 ทำหน้าที่ประมวลผลโค้ด เมื่อสิ้นสุดกระบวนการนี้ คุณจะมี virtual host แบบอ้างอิงชื่อ (name-based) ที่ให้บริการไดเรกทอรีแอปพลิเคชันจริง, ฐานข้อมูลที่มีผู้ใช้งานเฉพาะซึ่งมีสิทธิ์จำกัด (least-privilege), PHP ที่เชื่อมต่อกับ Apache ผ่าน PHP-FPM และใบรับรอง Let's Encrypt ฟรีที่ติดตั้งไว้ด้านบน

การติดตั้งประกอบด้วยคำสั่ง apt จำนวน 4 คำสั่ง เกือบทุกขั้นตอนในคู่มือนี้คือการเชื่อมต่อส่วนประกอบต่างๆ เข้าด้วยกัน รวมถึงการแก้ไขข้อผิดพลาดเล็กน้อยที่มักเกิดขึ้น ซึ่งอาจทำให้ stack ที่ติดตั้งใหม่แสดงหน้าว่างเปล่า, ส่งซอร์สโค้ดของคุณให้เบราว์เซอร์ดาวน์โหลดแทนการประมวลผล หรือปฏิเสธการเข้าถึงฐานข้อมูลที่คุณเพิ่งติดตั้ง ข้อผิดพลาดแต่ละอย่างจะมีรูปแบบเฉพาะที่คุณสามารถสังเกตได้ และแต่ละรายการจะถูกระบุไว้ด้านล่างพร้อมกับข้อความแจ้งเตือนที่คุณจะพบจริง

ข้อกำหนดเบื้องต้นและข้อควรระวังที่สำคัญ

สมมติว่าคุณใช้ Ubuntu 24.04 KVM VPS ที่ติดตั้งใหม่ โดยมีผู้ใช้ที่มีสิทธิ์ sudo หรือ root และมีที่อยู่ IPv4 สาธารณะ สแต็กขั้นต่ำสามารถทำงานได้ใน RAM ขนาด 1 GB แต่ควรจัดสรรให้เป็น 2 GB ก่อนที่จะนำแอปพลิเคชันที่มีฐานข้อมูลจริงไปใช้งาน เนื่องจากบัฟเฟอร์เริ่มต้นของ MariaDB รวมกับ PHP-FPM worker จำนวนหนึ่งจะใช้พื้นที่ 1 GB แรกไปอย่างรวดเร็ว

มีสองสิ่งที่ต้องดำเนินการให้เรียบร้อยก่อนที่ Certbot จะทำงานได้ในขั้นตอนสุดท้าย ดังนั้นควรเตรียมการไว้ตั้งแต่ตอนนี้ คุณจำเป็นต้องมี ชื่อโดเมน ที่มี A record ชี้ไปยัง IP สาธารณะของ VPS เนื่องจาก Let's Encrypt จะตรวจสอบความถูกต้องผ่าน HTTP ไปยังชื่อนั้น และที่อยู่ IP เปล่าจะไม่สามารถขอใบรับรองได้ นอกจากนี้ พอร์ต 80 และ 443 จะต้องสามารถเข้าถึงได้จากอินเทอร์เน็ต ซึ่งสำหรับผู้ให้บริการหลายราย หมายความว่าคุณต้องเปิดพอร์ตเหล่านี้ในไฟร์วอลล์เครือข่ายที่แผงควบคุม รวมถึง ใน ufw บนตัวเครื่องด้วย การเปลี่ยนแปลง DNS อาจใช้เวลาสูงสุดหนึ่งชั่วโมงในการเผยแพร่ ดังนั้นควรตั้งค่า A record ไว้ก่อน และมันจะพร้อมใช้งานเมื่อถึงเวลาที่คุณต้องใช้มัน

ขั้นตอนที่ 1 - ติดตั้ง Apache และตรวจสอบหน้าเว็บเริ่มต้น

sudo apt update
sudo apt install -y apache2

apt จะเริ่มการทำงานและเปิดใช้งานเซอร์วิสให้คุณโดยอัตโนมัติ ให้ตรวจสอบสถานะด้วยคำสั่ง:

systemctl status apache2

คุณควรเห็นบรรทัดที่ระบุว่า active (running) จากนั้นให้เปิด http://YOUR_SERVER_IP/ ในเว็บเบราว์เซอร์ หากปรากฏหน้า Apache2 Ubuntu Default Page พร้อมป้ายข้อความขนาดใหญ่ว่า "It works!" แสดงว่าการติดตั้งถูกต้อง และเป็นหลักฐานว่า Apache กำลังให้บริการอยู่ ไม่ใช่ข้อผิดพลาด หน้าเว็บดังกล่าวถูกจัดเก็บไว้ที่ /var/www/html/index.html และให้บริการโดยโฮสต์เสมือนเริ่มต้นที่มาพร้อมกับซอฟต์แวร์คือ 000-default.conf คุณจะปิดการใช้งานทั้งสองอย่างนี้ในภายหลัง แต่ในขณะนี้ การที่หน้าเว็บเหล่านี้ปรากฏขึ้นคือสิ่งที่คุณต้องการเห็น

หากหน้าเว็บไม่โหลดเลย แต่ systemctl ระบุว่าโพรเซสกำลังทำงานอยู่ แสดงว่ามีไฟร์วอลล์ขวางกั้นการเชื่อมต่ออยู่ ซึ่งนั่นจะเป็นขั้นตอนถัดไปที่คุณต้องดำเนินการ

ขั้นตอนที่ 2 - เปิดไฟร์วอลล์สำหรับ HTTP และ HTTPS

แพ็กเกจ apache2 จะลงทะเบียนโปรไฟล์แอปพลิเคชัน ufw ไว้สามรายการ ให้แสดงรายการเหล่านั้นด้วยคำสั่ง:

sudo ufw app list

คุณจะเห็น Apache, Apache Full และ Apache Secure โดยที่ Apache คือพอร์ต 80 เท่านั้น, Apache Secure คือพอร์ต 443 เท่านั้น และ Apache Full คือทั้งสองพอร์ต ซึ่งเป็นรายการที่คุณต้องการเนื่องจากคุณจะเพิ่ม TLS ในภายหลัง

sudo ufw allow OpenSSH
sudo ufw allow "Apache Full"
sudo ufw enable

อนุญาต OpenSSH ก่อน ที่คุณจะรัน ufw enable โดยปกติแล้ว ufw จะปฏิเสธการรับส่งข้อมูลขาเข้าทั้งหมดตามค่าเริ่มต้น หากคุณเปิดใช้งานโดยไม่มีกฎ SSH จะทำให้การเชื่อมต่อของคุณถูกตัดทันทีที่เปิดใช้งาน แม้คุณจะยังคงอยู่ในเซสชันปัจจุบันได้ แต่คุณจะไม่สามารถเชื่อมต่อกลับเข้ามาใหม่ได้อีก ตรวจสอบความถูกต้องด้วย sudo ufw status โดยคุณควรเห็น OpenSSH, Apache Full และรายการที่เทียบเท่าในเวอร์ชัน v6 ทั้งหมดแสดงสถานะเป็น ALLOW

ขั้นตอนที่ 3 - ติดตั้ง MariaDB และตั้งค่าความปลอดภัย

sudo apt install -y mariadb-server
systemctl status mariadb

Ubuntu 24.04 มาพร้อมกับ MariaDB 10.11 ซึ่งเป็นรุ่นที่รองรับระยะยาว (LTS) ดังนั้นคุณจึงไม่จำเป็นต้องใช้ที่เก็บซอฟต์แวร์ภายนอก เมื่อบริการทำงานแล้ว ให้ดำเนินการเพิ่มความปลอดภัยดังนี้:

sudo mysql_secure_installation

คุณควรอ่านข้อความแจ้งเตือนแทนการกด Enter รัวๆ เมื่อระบบถามหา current root password ให้กด Enter เนื่องจากยังไม่มีการตั้งรหัสผ่านไว้ เมื่อระบบถามว่า "Switch to unix_socket authentication?" คำตอบของคุณจะไม่มีผลใดๆ เนื่องจากแพ็กเกจนี้เปิดใช้งานไว้แล้ว ให้กด n ตอบ n สำหรับคำถาม "Change the root password?" ด้วยเหตุผลที่จะอธิบายในย่อหน้าถัดไป จากนั้นตอบ Y สำหรับคำถามที่เหลือทั้งหมด ได้แก่ การลบผู้ใช้ที่ไม่ระบุตัวตน, การไม่อนุญาตให้ root ล็อกอินจากระยะไกล, การลบฐานข้อมูลทดสอบ และการโหลดตารางสิทธิ์ใหม่

นี่คือส่วนที่ทำให้หลายคนสับสน ใน MariaDB บน Ubuntu บัญชีฐานข้อมูล root จะใช้การยืนยันตัวตนแบบ unix_socket ไม่ใช่รหัสผ่าน ซึ่งหมายความว่าฐานข้อมูลจะเชื่อถือผู้ใช้ ระบบปฏิบัติการ ที่คุณได้ยืนยันตัวตนไว้แล้ว ดังนั้นคำสั่งนี้จะใช้งานได้เมื่อรันจาก root shell:

sudo mysql

...และระบบจะนำคุณเข้าสู่พรอมต์ MariaDB [(none)]> โดยไม่ถามรหัสผ่าน หากรันคำสั่งเดียวกันในฐานะผู้ใช้ที่ไม่มีสิทธิ์ ระบบจะปฏิเสธการเข้าถึง ซึ่งนี่คือจุดประสงค์หลัก: การเข้าถึง root ของฐานข้อมูลจะผูกติดอยู่กับ sudo บนเครื่อง และไม่มีรหัสผ่านให้ขโมย ทำฟิชชิง หรือสุ่มเดา วิธีนี้ปลอดภัยกว่าการใช้รหัสผ่าน ดังนั้นไม่ต้องเปลี่ยนแปลงค่านี้ กฎที่ตามมาคือ: ห้าม กำหนดให้แอปพลิเคชันใช้งานบัญชี root โดยเด็ดขาด ให้สร้างผู้ใช้เฉพาะสำหรับแต่ละแอปพลิเคชันแทน (ขั้นตอนที่ 7) เนื่องจากแอปที่เชื่อมต่อผ่าน TCP ด้วยชื่อผู้ใช้และรหัสผ่านไม่สามารถใช้การยืนยันตัวตนผ่าน socket ได้อยู่แล้ว และคุณควรจำกัดให้แต่ละแอปเข้าถึงได้เฉพาะฐานข้อมูลของตนเองเท่านั้น

ขั้นตอนที่ 4 - ติดตั้ง PHP 8.3 พร้อมกับ PHP-FPM

PHP เวอร์ชันเริ่มต้นของ Ubuntu 24.04 คือ 8.3 ให้ติดตั้งตัวจัดการกระบวนการ FPM และส่วนขยายที่แอปพลิเคชันทั่วไปจำเป็นต้องใช้:

sudo apt install -y php8.3-fpm php8.3-mysql php8.3-cli \
  php8.3-curl php8.3-xml php8.3-mbstring php8.3-zip

โปรดสังเกตสิ่งที่ ไม่มี อยู่ในรายการนั้น: libapache2-mod-php แพ็กเกจรุ่นเก่าดังกล่าวจะฝังตัวแปลภาษา PHP ไว้ภายในกระบวนการของ Apache ทุกตัว แม้จะใช้งานง่าย แต่กระบวนการทำงาน (worker) ทุกตัวจะแบกรับสำเนาของ PHP ไว้เสมอไม่ว่าจะกำลังให้บริการสคริปต์หรือไฟล์รูปภาพทั่วไป ทั้งสองส่วนนี้มีวงจรชีวิตที่ผูกติดกัน และมันทำงานได้เฉพาะกับ prefork MPM ของ Apache ซึ่งเป็นตัวที่มีประสิทธิภาพต่ำที่สุด ในทางกลับกัน PHP-FPM จะรัน PHP เป็นกลุ่มกระบวนการ (pool) ของตัวเองที่ Apache จะสื่อสารด้วยผ่านซ็อกเก็ต Apache จึงสามารถใช้ event MPM แบบมัลติเธรดสำหรับไฟล์ทั่วไปและส่งเฉพาะคำขอ PHP ไปให้เท่านั้น กลุ่มกระบวนการนี้สามารถปรับแต่งแยกต่างหากจากเว็บเซิร์ฟเวอร์ และการตั้งค่า FPM แบบเดียวกันนี้ยังสามารถนำไปใช้ได้ในภายหลังหากคุณเปลี่ยนไปใช้ nginx อยู่ด้านหน้า ซึ่งถือเป็นค่าเริ่มต้นในปัจจุบันด้วยเหตุผลที่ดี

Apache เชื่อมต่อกับ FPM ผ่านโมดูล proxy_fcgi ให้เปิดใช้งานโมดูลดังกล่าว เปิดใช้งานการตั้งค่าที่แพ็กเกจ FPM ติดตั้งไว้ และรีสตาร์ทบริการ:

sudo a2enmod proxy_fcgi setenvif
sudo a2enconf php8.3-fpm
sudo systemctl restart apache2

a2enconf php8.3-fpm จะเปิดใช้งาน /etc/apache2/conf-available/php8.3-fpm.conf ซึ่งมีกฎที่กำหนดเส้นทางไฟล์ PHP ไปยังซ็อกเก็ตของ FPM หัวใจสำคัญของมันคือการจับคู่ไฟล์ .php ใดๆ และส่งต่อไปยังซ็อกเก็ตที่ /run/php/php8.3-fpm.sock:

<FilesMatch ".+\.ph(ar|p|tml)$">
    SetHandler "proxy:unix:/run/php/php8.3-fpm.sock|fcgi://localhost"
</FilesMatch>

คุณไม่จำเป็นต้องแก้ไขไฟล์นั้นเนื่องจากมีการตั้งค่าที่ถูกต้องมาให้แล้ว แต่การทราบเส้นทางของซ็อกเก็ตจะช่วยให้คุณวินิจฉัยปัญหา "PHP ดาวน์โหลดไฟล์แทนที่จะรัน" และ "Primary script unknown" ได้ในภายหลัง ซึ่งทั้งสองปัญหาเกิดจากการที่ Apache และ FPM ไม่สามารถตกลงกันได้เกี่ยวกับซ็อกเก็ตนี้หรือไฟล์ที่อยู่เบื้องหลังซ็อกเก็ตดังกล่าว

ขั้นตอนที่ 5 - การตั้งค่า virtual host แบบอิงตามชื่อสำหรับแอปพลิเคชันของคุณ

การทำ virtual hosting แบบอิงตามชื่อช่วยให้ IP เดียวสามารถให้บริการได้หลายเว็บไซต์ โดย Apache จะเลือกเว็บไซต์โดยดูจากส่วนหัว Host: ในคำขอ ให้สร้างไดเรกทอรีสำหรับแอปพลิเคชันโดยแยกออกมาจาก /var/www/html ที่เป็นค่าเริ่มต้น:

sudo mkdir -p /var/www/testapp
sudo chown -R www-data:www-data /var/www/testapp
sudo chmod -R 755 /var/www/testapp

ความเป็นเจ้าของไฟล์มีความสำคัญ Apache และ PHP-FPM ทำงานในฐานะผู้ใช้ www-data บน Ubuntu ดังนั้นไฟล์ที่เว็บเซิร์ฟเวอร์ต้องอ่าน และไดเรกทอรีที่แอปพลิเคชันต้องเขียนข้อมูลลงไป เช่น โฟลเดอร์สำหรับอัปโหลด ควรมี www-data เป็นเจ้าของ หากคุณต้องการแก้ไขไฟล์ด้วยผู้ใช้ที่คุณล็อกอินอยู่ รูปแบบที่นิยมคือการเป็นเจ้าของไฟล์ด้วยตัวคุณเองแล้วเพิ่มผู้ใช้ของคุณเข้าไปในกลุ่ม www-data สำหรับการปรับใช้ทั่วไป การใช้ www-data:www-data จะเป็นวิธีที่ตรงไปตรงมาที่สุด

สร้าง virtual host ที่ /etc/apache2/sites-available/testapp.conf:

<VirtualHost *:80>
    ServerName app.example.com
    DocumentRoot /var/www/testapp

    <Directory /var/www/testapp>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/testapp-error.log
    CustomLog ${APACHE_LOG_DIR}/testapp-access.log combined
</VirtualHost>

ตั้งค่า ServerName ให้เป็นโดเมนจริงของคุณ Options -Indexes จะป้องกันไม่ให้ Apache แสดงรายการไฟล์ในไดเรกทอรีเมื่อไม่มีไฟล์ index มิฉะนั้นผู้เข้าชมจะสามารถเรียกดูโครงสร้างซอร์สโค้ดของคุณได้ AllowOverride All ช่วยให้ไฟล์ .htaccess ทำงานได้ ซึ่งแอปพลิเคชัน PHP ส่วนใหญ่ต้องการเพื่อใช้ URL ที่สวยงาม หากแอปพลิเคชันของคุณไม่จำเป็นต้องใช้ ให้เปลี่ยนเป็น None เพื่อเพิ่มความเร็วเล็กน้อย เปิดใช้งานเว็บไซต์นี้ ปิดใช้งานเว็บไซต์เริ่มต้น ตรวจสอบการตั้งค่า และโหลดซ้ำ:

sudo a2ensite testapp
sudo a2dissite 000-default
sudo apache2ctl configtest
sudo systemctl reload apache2

apache2ctl configtest ควรแสดงผลเป็น Syntax OK บรรทัด a2dissite 000-default คือส่วนที่คนมักลืม และเป็นสาเหตุที่ทำให้หน้าเว็บเริ่มต้นยังคงปรากฏอยู่ ซึ่งได้อธิบายไว้ในส่วนของข้อผิดพลาด

ขั้นตอนที่ 6 - ตรวจสอบการทำงานของ PHP แล้วลบไฟล์ทดสอบทิ้ง

สร้างไฟล์ PHP แบบบรรทัดเดียวไว้ในไดเรกทอรีรากของแอปพลิเคชัน:

echo "<?php phpinfo();" | sudo tee /var/www/testapp/info.php

เข้าไปที่ http://app.example.com/info.php ผลลัพธ์ที่ถูกต้องคือตาราง PHP Version 8.3.x สีม่วงและเทา ซึ่งแสดงรายการโมดูลที่โหลดอยู่ โดยบรรทัด Server API จะต้องแสดงค่าเป็น FPM/FastCGI บรรทัดสุดท้ายนี้เป็นการยืนยันว่าคำขอถูกประมวลผลผ่าน PHP-FPM ไม่ใช่ mod_php

จากนั้นให้ลบไฟล์ทิ้งทันที:

sudo rm /var/www/testapp/info.php

phpinfo() จะเปิดเผยเวอร์ชัน PHP ที่คุณใช้งานอยู่ รวมถึงส่วนขยายทั้งหมดที่โหลดไว้ พาธของไฟล์ และรายละเอียดสภาพแวดล้อม ซึ่งเป็นข้อมูลที่เป็นประโยชน์ต่อผู้ที่พยายามตรวจสอบเซิร์ฟเวอร์เพื่อหาช่องโหว่ที่ทราบกันดี ไฟล์นี้เป็นเพียงเครื่องมือทดสอบ ไม่ใช่ฟีเจอร์ของระบบ ให้ลบทิ้งทันทีหลังจากตรวจสอบหน้าเว็บเรียบร้อยแล้ว หากเบราว์เซอร์ของคุณขึ้นข้อความให้ ดาวน์โหลด info.php แทนที่จะแสดงตาราง แสดงว่า PHP ยังไม่ได้เชื่อมต่อกับ Apache ให้ข้ามไปที่ส่วนการแก้ไขปัญหา (failures) ก่อนดำเนินการขั้นตอนอื่นใด

ขั้นตอนที่ 7 - สร้างฐานข้อมูลสำหรับแอปพลิเคชันและผู้ใช้งานที่มีสิทธิ์จำกัด

เปิดฐานข้อมูลด้วยสิทธิ์ root ผ่านการตรวจสอบสิทธิ์แบบ socket:

sudo mysql

จากนั้นสร้างฐานข้อมูลหนึ่งฐานข้อมูลและผู้ใช้งานหนึ่งรายที่จำกัดสิทธิ์ไว้เฉพาะฐานข้อมูลนั้น:

CREATE DATABASE appdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'appuser'@'localhost' IDENTIFIED BY 'a-long-random-password';
GRANT ALL PRIVILEGES ON appdb.* TO 'appuser'@'localhost';
FLUSH PRIVILEGES;
EXIT;

เรามีเหตุผลที่เลือกทำเช่นนี้ 3 ประการ ประการแรก utf8mb4 คือการเข้ารหัส UTF-8 แบบ 4 ไบต์ที่แท้จริง ในขณะที่นามแฝงเก่าอย่าง utf8 จะตัดข้อมูลอีโมจิและอักขระ CJK บางตัวทิ้งโดยไม่แจ้งเตือน ดังนั้นควรใช้ utf8mb4 เสมอ ประการที่สอง การให้สิทธิ์ (grant) จะทำบน appdb.* ไม่ใช่ *.* เพื่อให้ผู้ใช้งานรายนี้เข้าถึงได้เฉพาะฐานข้อมูลของตนเองเท่านั้น หากเกิดช่องโหว่ SQL-injection ในแอปพลิเคชัน ผู้โจมตีจะไม่สามารถอ่านตารางของเว็บไซต์อื่นได้ ประการที่สาม 'appuser'@'localhost' จะจำกัดบัญชีนี้ให้เชื่อมต่อได้เฉพาะจากภายในเครื่องเท่านั้น

ทดสอบการใช้งานด้วยบัญชีผู้ใช้งานดังกล่าว:

mysql -u appuser -p appdb

ระบบจะถามรหัสผ่านและนำคุณเข้าสู่พรอมต์ MariaDB [appdb]> สังเกตว่าไม่มีการใช้แฟล็ก -h ให้ละเว้นแฟล็กนี้ไว้เพื่อให้ไคลเอนต์เชื่อมต่อผ่าน Unix socket ภายในเครื่อง ซึ่งเป็นสิ่งที่ MariaDB นับว่าเป็น localhost สิ่งที่ควรทราบคือ สำหรับ MySQL และ MariaDB นั้น localhost หมายถึง Unix socket และ 127.0.0.1 หมายถึงการเชื่อมต่อผ่าน TCP บน MariaDB ของ Ubuntu 24.04 ค่าเริ่มต้น เซิร์ฟเวอร์ยังคงแปลงการเชื่อมต่อ TCP จาก 127.0.0.1 กลับเป็น localhost ทำให้ทั้งสองกรณีตรงกับบัญชีผู้ใช้งาน แต่บนเซิร์ฟเวอร์ที่เปิดใช้งาน skip-name-resolve (ซึ่งเป็นการปรับแต่งประสิทธิภาพทั่วไปและเป็นค่ามาตรฐานในอิมเมจคอนเทนเนอร์หลายตัว) ทั้งสองจะถูกมองว่าเป็นโฮสต์ที่แตกต่างกัน และแอปพลิเคชันที่เชื่อมต่อผ่าน 127.0.0.1 จะถูกปฏิเสธด้วยข้อผิดพลาด ERROR 1045 (28000): Access denied for user 'appuser'@'127.0.0.1' (using password: YES) แม้รหัสผ่านจะถูกต้องก็ตาม

ดังนั้น ให้ระบุโฮสต์ของแอปพลิเคชันเป็น localhost, ผู้ใช้งานเป็น appuser, และฐานข้อมูลเป็น appdb ห้ามใช้ root โดยเด็ดขาด ทั้ง mysqli และ PDO ของ PHP จะเปลี่ยนไปใช้ Unix socket เมื่อโฮสต์เป็นสตริง localhost ซึ่งตรงกับบัญชีที่คุณเพิ่งสร้างขึ้น หากเฟรมเวิร์กจำเป็นต้องใช้โฮสต์ TCP แบบตัวเลข ให้สร้างผู้ใช้งานให้ตรงกับวิธีการเชื่อมต่อจริง เช่น 'appuser'@'127.0.0.1' หรือ @'%' (ควบคู่ไปกับกฎไฟร์วอลล์) เฉพาะในกรณีที่จำเป็นต้องเข้าถึงฐานข้อมูลจากเครื่องอื่นเท่านั้น

ขั้นตอนที่ 8 - เพิ่ม HTTPS ด้วย Certbot

การให้บริการหน้าล็อกอินผ่าน HTTP แบบปกติจะส่งรหัสผ่านเป็นข้อความธรรมดา และเบราว์เซอร์สมัยใหม่ทุกตัวจะแจ้งเตือนว่าหน้าเว็บนั้น "ไม่ปลอดภัย" (Not secure) Certbot สามารถแก้ไขปัญหานี้ได้ด้วยคำสั่งเดียว ติดตั้ง Certbot พร้อมปลั๊กอินสำหรับ Apache ดังนี้:

sudo apt install -y certbot python3-certbot-apache
sudo certbot --apache

Certbot ใช้ปลั๊กอินสองตัวในขั้นตอนนี้ โดย apache authenticator จะทำหน้าที่ยืนยันว่าคุณเป็นเจ้าของโดเมนจริงผ่านการให้บริการไฟล์ challenge ชั่วคราวบน Apache ที่กำลังทำงานอยู่ จากนั้น apache installer จะเขียนไฟล์ virtual host ของคุณใหม่เพื่อเพิ่มบล็อก 443, ชี้ไปยังใบรับรอง (certificate) ชุดใหม่ และเปลี่ยนเส้นทาง (redirect) ทราฟฟิก HTTP ทั้งหมดไปยัง HTTPS โดยอัตโนมัติ Certbot ไม่ได้ถามคำถามเรื่องการ redirect มาตั้งแต่เวอร์ชัน 1.4.0 (พฤษภาคม 2020) แล้ว ดังนั้นให้ใช้แฟล็ก --no-redirect หากคุณยังต้องการให้บริการผ่าน HTTP แบบปกติอยู่ เนื่องจากคุณได้ตั้งค่า ServerName ที่ถูกต้องไว้ในขั้นตอนที่ 5 แล้ว Certbot จึงตรวจพบโดเมนได้โดยอัตโนมัติ ใบรับรองที่ออกในปัจจุบันมีอายุ 90 วัน (Let's Encrypt จะลดอายุการใช้งานเริ่มต้นเหลือ 64 วันในวันที่ 10 กุมภาพันธ์ 2027 และเหลือ 45 วันในวันที่ 16 กุมภาพันธ์ 2028) และแพ็กเกจจะติดตั้ง systemd timer เพื่อต่ออายุใบรับรองให้โดยอัตโนมัติ คุณสามารถตรวจสอบสถานะของ timer ได้ด้วยคำสั่ง sudo certbot renew --dry-run ซึ่งควรจะแสดงผลลงท้ายด้วย Congratulations, all simulated renewals succeeded

สำหรับคำแนะนำโดยละเอียดเกี่ยวกับกระบวนการตรวจสอบสิทธิ์ (challenge), การตั้งค่า timer สำหรับต่ออายุ, รวมถึงข้อกำหนดด้าน DNS และไฟร์วอลล์ สามารถดูได้จากคู่มือประกอบเรื่อง การออกใบรับรอง TLS ฟรีจาก Let's Encrypt ด้วย Certbot บน Apache

การสำรองข้อมูล การอัปเกรด และการเพิ่มความปลอดภัย

ให้สำรองข้อมูลสองส่วนที่เป็นสถานะของระบบ ได้แก่ ฐานข้อมูลและ web root วิธีที่ง่ายและเชื่อถือได้ที่สุดคือการทำ logical dump ทุกคืนด้วย sudo sh -c 'mysqldump --all-databases --single-transaction | gzip > /root/db-$(date +%F).sql.gz' แล้วคัดลอกไฟล์ออกไปเก็บไว้นอกเซิร์ฟเวอร์ การครอบกระบวนการทั้งหมดด้วย sudo sh -c เป็นสิ่งสำคัญ เพราะหากไม่มีคำสั่งนี้ shell จะรันการ redirect > /root/... ด้วยสิทธิ์ของผู้ใช้ทั่วไปของคุณและจะล้มเหลวด้วยข้อผิดพลาด Permission denied เนื่องจากมีเพียง mysqldump เท่านั้นที่ได้รับสิทธิ์ sudo มา --single-transaction จะช่วยให้คุณได้ snapshot ของตาราง InnoDB ที่สอดคล้องกันโดยไม่ต้องล็อกตารางเหล่านั้น เมื่อใช้งานร่วมกับ tar ของ /var/www และ /etc/apache2/sites-available คุณจะสามารถสร้าง stack ทั้งหมดขึ้นมาใหม่บน VPS เครื่องใหม่จากไฟล์เหล่านี้ได้

การอัปเกรดเป็นเพียง sudo apt update && sudo apt upgrade ตามปกติ สิ่งที่มักจะสร้างปัญหาคือการ อัปเกรดเวอร์ชัน PHP ในระหว่างการอัปเกรด release ของระบบปฏิบัติการ Ubuntu 26.04 LTS มาพร้อมกับ PHP 8.5 เป็นค่าเริ่มต้น และแพ็กเกจ php8.3-* ที่คุณติดตั้งไว้ที่นี่ไม่มีอยู่ใน release นั้น ดังนั้นหลังจากอัปเกรด คุณจะรัน php8.5-fpm ซึ่งส่งผลให้ socket กลายเป็น /run/php/php8.5-fpm.sock ในขณะที่การตั้งค่า Apache ของคุณยังคงชี้ไปที่ socket ของเวอร์ชัน 8.3 ให้เปิดใช้งาน conf ใหม่ (sudo a2enconf php8.5-fpm) และปิดใช้งานอันเก่า มิฉะนั้นเว็บไซต์ของคุณจะเริ่มแสดงข้อผิดพลาด Primary script unknown หลังจากการอัปเกรดที่ควรจะเป็นเรื่องปกติ เนื่องจากรอบการปล่อย PHP นั้นเร็วกว่า LTS distro ให้ตรวจสอบบันทึกการเปลี่ยนแปลง (release notes) ของ PHP เวอร์ชันปัจจุบันแทนการล็อกเวอร์ชัน patch ไว้

มีขั้นตอนการเพิ่มความปลอดภัยสองอย่างที่ควรทำตั้งแต่วันแรก อย่างแรกคือการติดตั้ง Fail2Ban เพื่อเฝ้าระวัง SSH บนเซิร์ฟเวอร์ VPS ที่เปิดสาธารณะจะถูกสุ่มพยายามล็อกอินโดยอัตโนมัติภายในไม่กี่นาที และการตั้งค่า jail เล็กๆ จะช่วยลดจำนวนการพยายามนับพันครั้งให้เหลือเพียงไม่กี่ครั้งก่อนที่จะถูกแบน อย่างที่สอง หากคุณต้องการจัดการ Apache virtual hosts, ฐานข้อมูล MariaDB และผู้ใช้ผ่านเบราว์เซอร์แทนการแก้ไขไฟล์ด้วยตนเอง Webmin ซึ่งเป็นแผงควบคุมบนเว็บ สามารถติดตั้งทับบน stack นี้ได้โดยตรงและจะจัดการไฟล์ config ชุดเดียวกับที่คุณเพิ่งเขียนไป ทั้งสองเครื่องมือนี้ไม่ได้มาแทนที่ความเข้าใจในส่วนประกอบต่างๆ แต่ช่วยลดภาระในการจัดการงานประจำวันได้เป็นอย่างดี

รูปแบบความล้มเหลวและข้อความที่คุณจะพบ

หน้าเว็บเริ่มต้นไม่ยอมหายไป คุณแก้ไข virtual host และ reload แล้ว แต่เบราว์เซอร์ยังคงแสดง "Apache2 Ubuntu Default Page" พร้อมแบนเนอร์ "It works!" Apache จะให้บริการ virtual host แรกที่ตรงกับเงื่อนไข และเมื่อไม่มี ServerName ที่ตรงกับคำขอ ไฟล์คอนฟิกที่เรียงตามตัวอักษรมาก่อนจะถูกเลือกใช้ ซึ่ง 000-default.conf จะถูกจัดลำดับก่อน testapp.conf สาเหตุอาจเกิดจากชื่อโฮสต์ในคำขอไม่ตรงกับ ServerName ของคุณ หรือคุณยังไม่ได้รัน sudo a2dissite 000-default ให้ปิดการใช้งานค่าเริ่มต้นด้วย sudo systemctl reload apache2 และตรวจสอบด้วย apache2ctl -S ซึ่งจะแสดงแผนผัง vhost และระบุว่าไฟล์คอนฟิกใดเป็นตัวกำหนดค่าเริ่มต้น นอกจากนี้ให้ล้างแคชของเบราว์เซอร์ด้วย เพราะเบราว์เซอร์อาจแสดงผล 200 จากหน้าเก่าที่ค้างอยู่ในแคช

ไฟล์ .php ถูกดาวน์โหลดแทนที่จะทำงาน คุณเปิด info.php แล้วเบราว์เซอร์ดาวน์โหลดไฟล์ที่มีซอร์สโค้ด <?php ดิบๆ หรือแสดงเป็นข้อความธรรมดาแทนที่จะประมวลผล Apache กำลังให้บริการไฟล์นั้นในฐานะ static asset เนื่องจากไม่ได้เชื่อมต่อ PHP handler, คุณข้ามขั้นตอน sudo a2enmod proxy_fcgi หรือ sudo a2enconf php8.3-fpm หรือไม่ได้ restart Apache หลังจากตั้งค่า ให้รันคำสั่งทั้งสาม (ในขั้นตอนที่ 4) แล้ว reload ตรวจสอบว่า module ถูกโหลดแล้วด้วย apache2ctl -M | grep fcgi ซึ่งควรแสดงรายการ proxy_fcgi_module ปัญหานี้คือการรั่วไหลของซอร์สโค้ด ไม่ใช่แค่ข้อผิดพลาดด้านการแสดงผล ดังนั้นต้องแก้ไขให้เรียบร้อยก่อนนำข้อมูลจริงขึ้นเซิร์ฟเวอร์

ERROR 1698 (28000): Access denied for user 'root'@'localhost' คุณรัน mysql -u root หรือ mariadb -u root โดยไม่มี sudo บัญชี root ใช้การยืนยันตัวตนแบบ unix_socket จึงจะยอมรับคุณก็ต่อเมื่อผู้ใช้ OS ของคุณคือ root เท่านั้น วิธีแก้ไขคือใช้ sudo mysql โดยไม่ต้องใส่ -u root หรือรหัสผ่าน ข้อความนี้คือพฤติกรรมปกติของการทำงานผ่าน socket auth ไม่ใช่การติดตั้งที่ผิดพลาด

ERROR 1045 (28000): Access denied for user 'appuser'@'127.0.0.1' จากแอปพลิเคชันแม้จะใช้รหัสผ่านที่ถูกต้อง บัญชีผู้ใช้มีอยู่จริงในชื่อ 'appuser'@'localhost' แต่แอปของคุณเชื่อมต่อผ่าน TCP ไปยัง 127.0.0.1 บนเซิร์ฟเวอร์ที่ปิดการทำ host-name resolution ไว้ (skip-name-resolve) ทำให้ MariaDB มองว่าทั้งสองเป็นโฮสต์ที่ต่างกัน โดย localhost คือ Unix socket และ 127.0.0.1 คือ TCP ให้ตั้งค่าแอปไปที่โฮสต์ localhost เพื่อให้ใช้ socket และตรงกับบัญชีผู้ใช้ หรือสร้างบัญชีที่สองเป็น 'appuser'@'127.0.0.1' หาก framework ของคุณรองรับเฉพาะการเชื่อมต่อผ่าน TCP เท่านั้น

AH01071: Got error 'Primary script unknown' ใน /var/log/apache2/testapp-error.log โดยเบราว์เซอร์แสดง File not found. Apache ส่งคำขอไปยัง PHP-FPM แล้ว แต่ FPM หาไฟล์สคริปต์ไม่พบตาม path ที่ Apache ระบุ สาเหตุที่พบบ่อยมีสองประการ: socket ของ FPM ในคอนฟิกชี้ไปยัง PHP เวอร์ชันที่ไม่ได้ติดตั้ง (เช่น socket ของ 8.3 ยังคงอยู่หลังจากการอัปเกรดเวอร์ชันไปเป็น PHP 8.5) หรือไฟล์ไม่มีอยู่จริงเนื่องจาก DocumentRoot และไดเรกทอรีจริงไม่ตรงกัน ให้ตรวจสอบว่า socket มีอยู่จริงด้วย ls -l /run/php/, ยืนยันว่า DocumentRoot ตรงกับตำแหน่งที่ไฟล์วางอยู่ และ restart ทั้ง php8.3-fpm (หรือเวอร์ชัน PHP-FPM ที่ติดตั้งอยู่จริง) และ apache2

AH00558: apache2: Could not reliably determine the server's fully qualified domain name ทุกครั้งที่ restart นี่เป็นเพียงคำเตือนที่ไม่ส่งผลเสีย ไม่ใช่ข้อผิดพลาด Apache กำลังแจ้งว่าไม่มีการตั้งค่า ServerName แบบ global ให้ปิดคำเตือนนี้โดยเขียน ServerName your.domain ลงใน /etc/apache2/conf-available/servername.conf แล้วรัน sudo a2enconf servername

(98)Address already in use: AH00072: make_sock: could not bind to address 0.0.0.0:80 เมื่อ Apache เริ่มทำงาน มีเว็บเซิร์ฟเวอร์อื่นใช้งานพอร์ต 80 อยู่แล้ว ซึ่งมักจะเป็น nginx ที่ค้างอยู่จากการทดลองก่อนหน้า ให้ค้นหาด้วย sudo ss -ltnp | grep :80 จากนั้นหยุดและปิดการใช้งานบริการนั้นก่อนที่จะเริ่ม Apache

FAQ

mod_php หรือ PHP-FPM ควรเลือกใช้อะไร?

ควรใช้ PHP-FPM เพราะ mod_php จะฝังตัวแปลภาษาไว้ในทุกกระบวนการของ Apache และบังคับให้ใช้ prefork MPM ที่ทำงานช้า ส่งผลให้ Apache ต้องแบกรับภาระของ PHP แม้ในขณะที่กำลังส่งไฟล์รูปภาพแบบคงที่ ส่วน PHP-FPM จะรัน PHP เป็นพูลแยกต่างหากที่ปรับแต่งได้อย่างอิสระ ซึ่ง Apache จะเข้าถึงผ่านซ็อกเก็ต ทำให้สามารถทำงานร่วมกับ event MPM แบบมัลติเธรดที่เร็วกว่าได้ และยังสามารถย้ายไปใช้ nginx ได้โดยไม่ต้องแก้ไขโค้ด นี่คือค่าเริ่มต้นมาตรฐานในปัจจุบัน ส่วน mod_php จะเหมาะสมก็ต่อเมื่อเป็นแอปพลิเคชันรุ่นเก่าที่จำเป็นต้องพึ่งพาพฤติกรรมบางอย่างที่ทำงานภายในกระบวนการของเว็บเซิร์ฟเวอร์เท่านั้น

ทำไมเบราว์เซอร์ถึงดาวน์โหลดไฟล์ PHP แทนที่จะรันไฟล์นั้น?

Apache กำลังจัดการไฟล์ .php เป็นไฟล์ดาวน์โหลดแบบคงที่เนื่องจากไม่มีตัวจัดการ PHP เชื่อมต่ออยู่ สำหรับ Ubuntu 24.04 ที่ใช้ FPM หมายความว่าคุณอาจข้ามขั้นตอนใดขั้นตอนหนึ่งระหว่าง sudo a2enmod proxy_fcgi, sudo a2enconf php8.3-fpm หรือการรีสตาร์ท Apache หลังจากนั้น ให้รันคำสั่งทั้งสามขั้นตอนและโหลดการตั้งค่าใหม่ จากนั้นตรวจสอบด้วย apache2ctl -M | grep fcgi ว่ามี proxy_fcgi_module ปรากฏอยู่ในรายการหรือไม่ จนกว่าคุณจะแก้ไขได้ เซิร์ฟเวอร์จะเปิดเผยซอร์สโค้ดออกมา ดังนั้นให้ถือว่าเป็นเรื่องเร่งด่วนที่ต้องจัดการ

ทำไมถึงถูกปฏิเสธการเข้าถึง root ใน MariaDB ทั้งที่รหัสผ่านถูกต้อง?

เนื่องจากไม่มีการตั้งรหัสผ่านไว้ MariaDB บน Ubuntu จึงตรวจสอบสิทธิ์บัญชี root ผ่าน unix_socket ซึ่งผูกไว้กับผู้ใช้ root ของระบบปฏิบัติการ การใช้ mysql -u root จากเชลล์ปกติจะส่งผลลัพธ์เป็น ERROR 1698 (28000): Access denied for user 'root'@'localhost' ตามการออกแบบ ให้เชื่อมต่อด้วย sudo mysql แทน และสร้างผู้ใช้แยกต่างหากที่ตรวจสอบสิทธิ์ด้วยรหัสผ่านสำหรับแอปพลิเคชันใดๆ แทนการใช้บัญชี root ซ้ำ

ฉันจะเพิ่ม HTTPS ให้กับเว็บไซต์ LAMP ของฉันได้อย่างไร?

ติดตั้ง certbot และ python3-certbot-apache ชี้ A record ของโดเมนมาที่เซิร์ฟเวอร์ จากนั้นรัน sudo certbot --apache ตัวตรวจสอบสิทธิ์ของ Apache จะพิสูจน์การควบคุมโดเมนผ่าน Apache ที่กำลังทำงานอยู่ของคุณ และโปรแกรมติดตั้งจะเขียนไฟล์ virtual host ใหม่สำหรับพอร์ต 443 พร้อมตั้งค่าการต่ออายุใบรับรองอัตโนมัติ คำแนะนำฉบับเต็มสำหรับการใช้ Certbot ร่วมกับ Apache จะครอบคลุมถึงขั้นตอนการตรวจสอบสิทธิ์ ตัวตั้งเวลาต่ออายุ และข้อผิดพลาดที่พบบ่อย

#lamp#apache#mariadb#php-fpm#ubuntu