ตั้งค่าหลาย Server เพื่อ รับโหลดสูง ใน 2025-2026
คู่มือครบวงจร: Nginx Load Balancer + Redis Session + Database Replication สำหรับ Laravel Application รองรับผู้ใช้หลายหมื่นคนพร้อมกัน
แบ่งเบาภาระการทำงานไปยังหลาย Server ทำให้แต่ละ Server ไม่โหลดเกิน รองรับคนเข้าใช้พร้อมกันได้มากขึ้น 3-10 เท่า
ถ้า Server ตัวไหนล่ม ระบบยังทำงานต่อได้ Load Balancer จะส่ง traffic ไปยัง Server ตัวอื่นที่ยังทำงานอยู่อัตโนมัติ
เพิ่ม Server ใหม่ได้ง่ายเมื่อ traffic สูงขึ้น ไม่ต้อง Upgrade Hardware แค่เพิ่ม Server เข้าไปใน Pool
1 Server
2 vCPU, 4GB RAM
2-3+ Servers
4 vCPU, 8GB RAM each
1 Server
2 vCPU, 4GB RAM
1 Master + 1 Slave
4 vCPU, 16GB RAM
ติดตั้ง Laravel และ Nginx บน App Server ทุกตัวให้เหมือนกัน
# Update system
sudo apt update && sudo apt upgrade -y
# Install Nginx
sudo apt install nginx -y
# Install PHP 8.2 and extensions
sudo apt install php8.2-fpm php8.2-mysql php8.2-mbstring \
php8.2-xml php8.2-curl php8.2-zip php8.2-gd \
php8.2-bcmath php8.2-intl php8.2-redis -y
# Install Composer
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
# /etc/nginx/sites-available/laravel
server {
listen 80;
server_name _; # รับจาก Load Balancer
root /var/www/laravel/public;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_hide_header X-Powered-By;
}
location ~ /\.ht {
deny all;
}
# Health check endpoint
location /health {
access_log off;
return 200 "OK";
add_header Content-Type text/plain;
}
}
Redis ใช้เก็บ Session และ Cache ร่วมกันระหว่าง App Servers ทุกตัว
# Install Redis
sudo apt install redis-server -y
# Configure Redis
sudo nano /etc/redis/redis.conf
# /etc/redis/redis.conf
# Bind to all interfaces (หรือ IP ของ network)
bind 0.0.0.0
# ตั้งค่า password (สำคัญมาก!)
requirepass YOUR_STRONG_PASSWORD_HERE
# Max memory
maxmemory 2gb
maxmemory-policy allkeys-lru
# Persistence (optional)
save 900 1
save 300 10
ต้องตั้งค่า Firewall ให้เข้าถึง Redis ได้เฉพาะจาก App Servers เท่านั้น! Redis ที่ไม่มี password และเปิด public เป็นช่องโหว่ร้ายแรง
ตั้งค่า Laravel บน App Server ทุกตัวให้ใช้ Redis สำหรับ Session และ Cache
cd /var/www/laravel
composer require predis/predis
# .env
# Session Driver - ใช้ Redis
SESSION_DRIVER=redis
SESSION_CONNECTION=session
# Cache Driver - ใช้ Redis
CACHE_DRIVER=redis
CACHE_CONNECTION=cache
# Queue Driver - ใช้ Redis (optional)
QUEUE_CONNECTION=redis
# Redis Configuration
REDIS_CLIENT=predis
REDIS_HOST=192.168.1.100 # IP ของ Redis Server
REDIS_PASSWORD=YOUR_STRONG_PASSWORD_HERE
REDIS_PORT=6379
# แยก Database สำหรับ Session และ Cache
REDIS_CACHE_DB=1
REDIS_SESSION_DB=2
// config/database.php
'redis' => [
'client' => env('REDIS_CLIENT', 'predis'),
'default' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', 6379),
'database' => 0,
],
'cache' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', 6379),
'database' => env('REDIS_CACHE_DB', 1),
],
'session' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', 6379),
'database' => env('REDIS_SESSION_DB', 2),
],
],
การแยก Database ของ Redis สำหรับ Session และ Cache ช่วยให้จัดการง่ายขึ้น และป้องกันการชนกันของข้อมูล
ตั้งค่า Nginx เป็น Load Balancer สำหรับกระจาย traffic ไปยัง App Servers
# Install Nginx
sudo apt update
sudo apt install nginx -y
# /etc/nginx/conf.d/load-balancer.conf
# Upstream block - กำหนด App Servers
upstream laravel_backend {
# Load Balancing Methods:
# - ค่า default คือ Round Robin (หมุนเวียน)
# - ip_hash; สำหรับ Sticky Sessions
# - least_conn; สำหรับ Least Connections
# ip_hash; # เปิดใช้ถ้าต้องการ Sticky Sessions
# App Server 1
server 192.168.1.101:80 weight=3 max_fails=3 fail_timeout=30s;
# App Server 2
server 192.168.1.102:80 weight=3 max_fails=3 fail_timeout=30s;
# App Server 3
server 192.168.1.103:80 weight=3 max_fails=3 fail_timeout=30s;
# Backup Server (optional) - ใช้เมื่อ server อื่นล่ม
# server 192.168.1.104:80 backup;
# Keepalive connections
keepalive 32;
}
# HTTP Server - Redirect to HTTPS
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
# Redirect all HTTP to HTTPS
return 301 https://$server_name$request_uri;
}
# HTTPS Server
server {
listen 443 ssl http2;
server_name yourdomain.com www.yourdomain.com;
# SSL Configuration
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
# Security Headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Proxy Settings
location / {
proxy_pass http://laravel_backend;
proxy_http_version 1.1;
# Headers สำหรับ Laravel
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
# Connection headers
proxy_set_header Connection "";
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# Buffer settings
proxy_buffer_size 4k;
proxy_buffers 8 16k;
}
# Health Check Endpoint
location /health {
access_log off;
proxy_pass http://laravel_backend/health;
proxy_connect_timeout 5s;
proxy_read_timeout 5s;
}
# Status Page (internal only)
location /nginx_status {
stub_status on;
allow 127.0.0.1;
allow 192.168.1.0/24;
deny all;
}
}
Laravel ต้องรู้ว่า request มาจาก Load Balancer เพื่อให้ได้ IP และ HTTPS ที่ถูกต้อง
cd /var/www/laravel
composer require fideloper/proxy
// app/Http/Middleware/TrustProxies.php
namespace App\Http\Middleware;
use Illuminate\Http\Middleware\TrustProxies as Middleware;
use Illuminate\Http\Request;
class TrustProxies extends Middleware
{
/**
* trusted Proxy IPs
* ใส่ IP ของ Load Balancer
*/
protected $proxies = [
'192.168.1.100', // Load Balancer IP
// หรือใช้ '*' เพื่อ trust ทุก proxy (ไม่แนะนำสำหรับ production)
// '*',
// หรือใช้ CIDR
// '192.168.1.0/24',
];
/**
* Headers ที่จะ trust
*/
protected $headers =
Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_HOST |
Request::HEADER_X_FORWARDED_PORT |
Request::HEADER_X_FORWARDED_PROTO |
Request::HEADER_X_FORWARDED_AWS_ELB;
}
# .env
# Force HTTPS (สำหรับ production ที่ใช้ Load Balancer)
APP_URL=https://yourdomain.com
# Trust all proxies (ถ้าใช้ cloud provider)
TRUSTED_PROXIES=**
ถ้าใช้ AWS ALB, Google Cloud Load Balancer, หรือ Cloudflare
ให้ใช้ protected $proxies = '*';
เพราะ IP ของ Load Balancer เปลี่ยนแปลงได้
แยกการอ่าน (Read) ไป Slave และการเขียน (Write) ไป Master เพื่อเพิ่มประสิทธิภาพ
// config/database.php
'mysql' => [
'driver' => 'mysql',
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
]) : [],
// Read/Write Split Configuration
'read' => [
'host' => [
env('DB_SLAVE_HOST', '192.168.1.201'), // MySQL Slave
// สามารถเพิ่มหลาย slave ได้
// env('DB_SLAVE_HOST_2', '192.168.1.202'),
],
],
'write' => [
'host' => env('DB_MASTER_HOST', '192.168.1.200'), // MySQL Master
],
'sticky' => true, // ใช้ master สำหรับ read หลังจาก write ใน request เดียวกัน
],
# .env
# Master Database (Write)
DB_MASTER_HOST=192.168.1.200
DB_HOST=192.168.1.200
# Slave Database (Read)
DB_SLAVE_HOST=192.168.1.201
# Database Credentials
DB_DATABASE=laravel_production
DB_USERNAME=laravel_user
DB_PASSWORD=your_secure_password
การตั้งค่า 'sticky' => true
จะทำให้ Laravel ใช้ Master สำหรับ Read operations ใน request เดียวกันหลังจาก Write
ป้องกันปัญหา Replication Lag
หมุนเวียน request ไปยัง server แต่ละตัวตามลำดับ วิธีนี้เป็น default ของ Nginx
upstream backend {
server 192.168.1.101;
server 192.168.1.102;
server 192.168.1.103;
}
client IP เดียวกันจะไปยัง server เดียวกันเสมอ เหมาะสำหรับ session-based apps
upstream backend {
ip_hash;
server 192.168.1.101;
server 192.168.1.102;
}
ส่ง request ไปยัง server ที่มี connection น้อยที่สุด เหมาะสำหรับ long-lived connections
upstream backend {
least_conn;
server 192.168.1.101;
server 192.168.1.102;
}
กำหนดน้ำหนักให้ server ที่แรงกว่ารับ traffic มากกว่า
upstream backend {
server 192.168.1.101 weight=5; # แรงกว่า
server 192.168.1.102 weight=3;
server 192.168.1.103 weight=2; # อ่อนกว่า
}
// routes/web.php
Route::get('/health', function () {
return response()->json([
'status' => 'ok',
'timestamp' => now()->toISOString(),
'server' => gethostname(),
'php_version' => PHP_VERSION,
], 200);
})->withoutMiddleware([\App\Http\Middleware\VerifyCsrfToken::class]);
// Health check แบบละเอียด
Route::get('/health/detailed', function () {
$checks = [
'database' => checkDatabaseConnection(),
'redis' => checkRedisConnection(),
'storage' => checkStorageAccess(),
];
$allHealthy = collect($checks)->every(fn($check) => $check === true);
return response()->json([
'status' => $allHealthy ? 'healthy' : 'unhealthy',
'checks' => $checks,
'server' => gethostname(),
'timestamp' => now()->toISOString(),
], $allHealthy ? 200 : 503);
})->withoutMiddleware([\App\Http\Middleware\VerifyCsrfToken::class]);
// app/helpers.php
function checkDatabaseConnection(): bool
{
try {
DB::connection()->getPdo();
return true;
} catch (\Exception $e) {
return false;
}
}
function checkRedisConnection(): bool
{
try {
Redis::connection()->ping();
return true;
} catch (\Exception $e) {
return false;
}
}
function checkStorageAccess(): bool
{
try {
Storage::disk('local')->put('health-check.txt', 'ok');
Storage::disk('local')->delete('health-check.txt');
return true;
} catch (\Exception $e) {
return false;
}
}
upstream laravel_backend {
# Passive health checks
server 192.168.1.101:80 max_fails=3 fail_timeout=30s;
server 192.168.1.102:80 max_fails=3 fail_timeout=30s;
server 192.168.1.103:80 max_fails=3 fail_timeout=30s;
}
# max_fails=3: ถ้า fail 3 ครั้ง จะ mark เป็น unhealthy
# fail_timeout=30s: รอ 30 วินาทีก่อนลองใหม่
# Install Certbot
sudo apt install certbot python3-certbot-nginx -y
# Obtain SSL Certificate
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
# Test Auto-renewal
sudo certbot renew --dry-run
# /etc/nginx/conf.d/ssl-params.conf
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_stapling on;
ssl_stapling_verify on;
# HSTS (optional - ใช้เมื่อแน่ใจว่าใช้ HTTPS เท่านั้น)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
สาเหตุ: Session ถูกเก็บแยกกันในแต่ละ server
# แก้ไข: ตรวจสอบว่าใช้ Redis Session ถูกต้อง
# ใน .env
SESSION_DRIVER=redis
REDIS_HOST=192.168.1.100 # IP ของ Redis Server ที่ใช้ร่วมกัน
สาเหตุ: Laravel ไม่ trust proxy headers
// แก้ไข: app/Http/Middleware/TrustProxies.php
protected $proxies = '*'; // หรือใส่ IP ของ Load Balancer
protected $headers = Request::HEADER_X_FORWARDED_ALL;
สาเหตุ: Replication Lag ระหว่าง Master และ Slave
// แก้ไข: เปิดใช้ sticky connection
// config/database.php
'mysql' => [
// ...
'sticky' => true, // ใช้ master สำหรับ read หลัง write
],
สาเหตุ: Session ไม่ตรงกันเนื่องจาก load balancing
# แก้ไข: .env
SESSION_SECURE_COOKIE=true
SESSION_SAME_SITE=none # สำหรับ cross-domain
SESSION_DOMAIN=yourdomain.com
Cache query results, view fragments, และ config ใน Redis ลดการ query database ลงได้ 50-80%
ส่ง email, process uploads, และ heavy tasks ไปทำใน background ด้วย Redis Queue + Laravel Horizon
เปิด OPcache ใน PHP-FPM เพื่อ cache compiled PHP code เพิ่มความเร็วได้ 2-3 เท่า
ปรับ pm.max_children, pm.start_servers ให้เหมาะสมกับ RAM ของ server
# /etc/php/8.2/fpm/pool.d/www.conf
# สำหรับ Server 8GB RAM
pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.max_requests = 500
# สำหรับ Server 16GB RAM
pm = dynamic
pm.max_children = 100
pm.start_servers = 20
pm.min_spare_servers = 10
pm.max_spare_servers = 40
pm.max_requests = 1000
ติดตั้ง Nginx + PHP-FPM + Laravel ทุกตัว
สำหรับ Session และ Cache ร่วมกัน
แก้ไข .env และ config/database.php
Config upstream และ proxy settings
ให้ Laravel รู้จัก Load Balancer
แยก Read/Write operations