ตั้ง Apache Virtual Hosts หลายโดเมนบน VPS
Apache Virtual Hosts ช่วยให้ VPS หนึ่งเครื่องรัน website หลายโดเมนบน IP เดียวกัน แต่ละ domain มี document root, config และ SSL certificate แยกกัน เป็นพื้นฐานที่ต้องรู้ก่อนจะ deploy website หลายเว็บบน VPS เดียว
ติดตั้ง Apache
sudo apt update
sudo apt install apache2 -y
sudo systemctl enable --now apache2
สร้าง Virtual Host
# สร้าง document root
sudo mkdir -p /var/www/example.com/public_html
sudo chown -R $USER:www-data /var/www/example.com
sudo chmod -R 755 /var/www/example.com
# สร้าง index page ทดสอบ
echo "example.com is working
" > /var/www/example.com/public_html/index.html
# สร้าง virtual host config
sudo nano /etc/apache2/sites-available/example.com.conf
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com/public_html
<Directory /var/www/example.com/public_html>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/example.com-error.log
CustomLog ${APACHE_LOG_DIR}/example.com-access.log combined
</VirtualHost>
# เปิด site
sudo a2ensite example.com.conf
sudo a2enmod rewrite # สำหรับ .htaccess redirect
sudo systemctl reload apache2
เพิ่ม SSL ด้วย Let's Encrypt
sudo apt install certbot python3-certbot-apache -y
# ออก certificate (DNS ต้องชี้มาที่ VPS แล้ว)
sudo certbot --apache -d example.com -d www.example.com
# Certbot จะแก้ config อัตโนมัติ เพิ่ม HTTPS redirect และ SSL config
# ดู config ที่แก้แล้ว
cat /etc/apache2/sites-available/example.com-le-ssl.conf
PHP-FPM แยกต่อ Virtual Host
สำหรับ security การแยก PHP-FPM pool ต่อ website ดีกว่าการใช้ pool เดียวร่วมกัน:
sudo apt install php8.3-fpm -y
sudo a2enmod proxy_fcgi setenvif
sudo a2enconf php8.3-fpm
# ใน VirtualHost block เพิ่ม:
<FilesMatch \.php$>
SetHandler "proxy:unix:/run/php/php8.3-fpm.sock|fcgi://localhost"
</FilesMatch>
/etc/php/8.3/fpm/pool.d/www.conf เป็นชื่อ site แล้วเปลี่ยน user, group และ listen socket ใหม่ ทำให้ PHP ของแต่ละ site รันด้วย user คนละตัว
ตรวจสอบว่า Virtual Host ทำงานถูกต้อง
# ตรวจ config syntax
sudo apache2ctl configtest
# ดู virtual hosts ที่ active
sudo apache2ctl -S
# ทดสอบด้วย curl
curl -H "Host: example.com" http://localhost
# ตรวจ log errors
sudo tail -f /var/log/apache2/example.com-error.log
apache2ctl -S แสดง virtual host ทั้งหมดพร้อม port และ DocumentRoot ช่วย debug ได้ว่า request เข้า vhost ไหน
แก้ปัญหาที่พบบ่อย
เปิดเว็บแล้วเห็น Apache default page แทนที่จะเป็น site
Default site ยังเปิดอยู่หรือ ServerName ไม่ตรงกับ request DNS ต้องปิด default site:
sudo a2dissite 000-default.conf
sudo systemctl reload apache2
# ตรวจ vhost matching
sudo apache2ctl -S | grep "example.com"
403 Forbidden เมื่อเข้า site
Permission ของ directory ผิด หรือ AllowOverride ไม่ถูกตั้ง:
# ตรวจ permission
ls -la /var/www/example.com/public_html/
# ตั้ง permission ให้ถูก
sudo chown -R www-data:www-data /var/www/example.com
sudo chmod -R 755 /var/www/example.com
# ตรวจ SELinux (ถ้ามี)
ls -laZ /var/www/example.com/
.htaccess ไม่ทำงาน
AllowOverride ต้องเป็น All และ mod_rewrite ต้องเปิด:
sudo a2enmod rewrite
sudo systemctl reload apache2
# ตรวจ AllowOverride ใน config
grep -r "AllowOverride" /etc/apache2/sites-enabled/
SSL certificate ของ site หนึ่งใช้แทน site อื่น
Apache ใช้ SNI เลือก certificate ตาม ServerName ตรวจว่า ServerName ใน SSL config ถูกต้อง:
# ดู SSL virtual hosts
sudo apache2ctl -S | grep "*:443"
# ตรวจ certificate ที่ site ใช้
echo | openssl s_client -servername example.com -connect localhost:443 2>/dev/null | openssl x509 -noout -subject
คำถามที่พบบ่อย
Apache กับ Nginx อันไหนดีกว่าสำหรับ Virtual Hosting?
ทั้งสองทำ virtual hosting ได้ Nginx ใช้ memory น้อยกว่าและเหมาะกับ static file/reverse proxy Apache เด่นกว่าเรื่อง .htaccess ที่ WordPress/PHP app จำนวนมากต้องใช้ ถ้าใช้ PHP app เยอะและต้องการ .htaccess รองรับ Apache สะดวกกว่า
รัน Virtual Host ได้กี่ domain บน VPS เดียว?
ในทางทฤษฎีไม่จำกัด แต่จะถูกจำกัดโดย RAM และ CPU หนึ่ง VPS 1GB RAM รัน 10-20 WordPress sites ได้สบายถ้า traffic ไม่มาก แต่ถ้า site มี traffic สูงต้องคำนึงถึงทรัพยากรให้เพียงพอ
แต่ละ Virtual Host แยก log กันได้ไหม?
ได้ แต่ละ VirtualHost block ตั้ง ErrorLog และ CustomLog แยกกันได้ ทำให้ debug แต่ละ site ได้ง่ายโดยไม่ต้อง filter log รวม ควรตั้งแยกทุก site