Deploy Python Flask/Django บน VPS ด้วย uWSGI และ Nginx
การ deploy Python web application บน VPS นั้นมีหลายทางเลือก แต่ combination ที่ได้รับความนิยมและมีประสิทธิภาพสูงที่สุดคือการใช้ uWSGI เป็น WSGI server คู่กับ Nginx เป็น reverse proxy บทความนี้จะพาคุณผ่านทุกขั้นตอนตั้งแต่เริ่มต้นจนถึง production-ready สำหรับทั้ง Flask และ Django
ทำไมต้องใช้ uWSGI + Nginx?
Python web framework อย่าง Flask และ Django มี built-in development server แต่ ไม่ควรใช้ใน production เด็ดขาด เพราะไม่ปลอดภัยและรองรับ concurrent request ได้น้อย การใช้งานจริงจึงต้องมี stack ที่เหมาะสม ดังนี้
- Nginx — รับ HTTP/HTTPS request จาก internet จัดการ static files, SSL termination และ load balancing
- uWSGI — แปลง request จาก Nginx ให้ Python app เข้าใจได้ผ่าน WSGI protocol จัดการ worker processes และ threading
- Flask/Django — ประมวลผล business logic และ return response
ขั้นตอนที่ 1: เตรียม VPS และ Python Environment
เริ่มต้นด้วยการอัพเดทระบบและติดตั้ง package ที่จำเป็น (สมมติว่าใช้ Ubuntu 22.04 LTS)
# อัพเดทระบบก่อน
sudo apt update && sudo apt upgrade -y
# ติดตั้ง Python, pip, virtualenv และ build tools
sudo apt install -y python3 python3-pip python3-venv python3-dev \
build-essential libssl-dev libffi-dev nginx
# ตรวจสอบ version
python3 --version
nginx -v
จากนั้นสร้าง user แยกต่างหากสำหรับรัน application (best practice ด้านความปลอดภัย)
sudo useradd -m -s /bin/bash appuser
sudo su - appuser
ขั้นตอนที่ 2: สร้างและ Deploy Python App
สำหรับ Flask
# สร้าง project directory
mkdir -p /home/appuser/myapp
cd /home/appuser/myapp
# สร้าง virtual environment
python3 -m venv venv
source venv/bin/activate
# ติดตั้ง Flask และ uWSGI
pip install flask uwsgi
# สร้างไฟล์ application หลัก
cat > app.py << 'EOF'
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return 'Hello from Flask on VPS!
'
@app.route('/health')
def health():
return {'status': 'ok'}
if __name__ == '__main__':
app.run()
EOF
สำหรับ Django
# ติดตั้ง Django และ uWSGI
pip install django uwsgi
# สร้าง Django project ใหม่
django-admin startproject myproject .
# ปรับค่า ALLOWED_HOSTS ใน settings.py
# ALLOWED_HOSTS = ['your-domain.com', 'your-server-ip']
# Run migrations
python manage.py migrate
app:app ด้วย myproject.wsgi:application ในขั้นตอน uWSGI configuration ด้านล่าง
ขั้นตอนที่ 3: ตั้งค่า uWSGI
สร้างไฟล์ configuration สำหรับ uWSGI
cat > /home/appuser/myapp/uwsgi.ini << 'EOF'
[uwsgi]
# สำหรับ Flask
module = app:app
# สำหรับ Django ใช้บรรทัดนี้แทน:
# module = myproject.wsgi:application
master = true
processes = 4
threads = 2
# Socket file สำหรับ communicate กับ Nginx
socket = /tmp/myapp.sock
chmod-socket = 660
vacuum = true
# Log
logto = /var/log/uwsgi/myapp.log
# เพิ่ม stability
die-on-term = true
EOF
สร้าง log directory และทดสอบ uWSGI
sudo mkdir -p /var/log/uwsgi
sudo chown appuser:www-data /var/log/uwsgi
# ทดสอบว่า uWSGI ทำงานได้
cd /home/appuser/myapp
source venv/bin/activate
uwsgi --ini uwsgi.ini
unable to load configuration ให้ตรวจสอบว่า path ใน uwsgi.ini ถูกต้องและ virtual environment ถูก activate แล้ว
ขั้นตอนที่ 4: ตั้งค่า systemd Service
สร้าง systemd service เพื่อให้ uWSGI รันอัตโนมัติเมื่อ server restart
sudo nano /etc/systemd/system/myapp.service
วางเนื้อหาต่อไปนี้ในไฟล์
[Unit]
Description=uWSGI instance for myapp
After=network.target
[Service]
User=appuser
Group=www-data
WorkingDirectory=/home/appuser/myapp
Environment="PATH=/home/appuser/myapp/venv/bin"
ExecStart=/home/appuser/myapp/venv/bin/uwsgi --ini uwsgi.ini
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
# เปิดใช้งานและเริ่ม service
sudo systemctl daemon-reload
sudo systemctl enable myapp
sudo systemctl start myapp
# ตรวจสอบสถานะ
sudo systemctl status myapp
ขั้นตอนที่ 5: ตั้งค่า Nginx
สร้าง Nginx virtual host configuration
sudo nano /etc/nginx/sites-available/myapp
server {
listen 80;
server_name your-domain.com www.your-domain.com;
# Logging
access_log /var/log/nginx/myapp_access.log;
error_log /var/log/nginx/myapp_error.log;
# Static files (สำหรับ Django)
location /static/ {
alias /home/appuser/myapp/staticfiles/;
expires 30d;
add_header Cache-Control "public, no-transform";
}
# Media files (สำหรับ Django)
location /media/ {
alias /home/appuser/myapp/media/;
expires 7d;
}
# Proxy ทุก request ไปหา uWSGI
location / {
include uwsgi_params;
uwsgi_pass unix:/tmp/myapp.sock;
uwsgi_read_timeout 60;
uwsgi_connect_timeout 60;
}
# 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;
}
# เปิดใช้งาน site
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
# ตรวจสอบ syntax
sudo nginx -t
# Reload Nginx
sudo systemctl reload nginx
ขั้นตอนที่ 6: เพิ่ม SSL ด้วย Let's Encrypt
การใช้ HTTPS เป็นสิ่งจำเป็นใน production ใช้ Certbot เพื่อขอ SSL certificate ฟรีจาก Let's Encrypt
# ติดตั้ง Certbot
sudo apt install -y certbot python3-certbot-nginx
# ขอ certificate (แทนที่ด้วย domain จริง)
sudo certbot --nginx -d your-domain.com -d www.your-domain.com
# ทดสอบ auto-renewal
sudo certbot renew --dry-run
ขั้นตอนที่ 7: ตั้งค่า Static Files (Django)
สำหรับ Django จำเป็นต้อง collectstatic ก่อน deploy ทุกครั้ง
cd /home/appuser/myapp
source venv/bin/activate
# ตั้งค่าใน settings.py
# STATIC_ROOT = '/home/appuser/myapp/staticfiles/'
# รวม static files
python manage.py collectstatic --noinput
# Restart service
sudo systemctl restart myapp
การ Monitor และ Debug
คำสั่งที่ใช้บ่อยในการตรวจสอบและแก้ไขปัญหา
# ดู log ของ uWSGI แบบ real-time
sudo journalctl -u myapp -f
# ดู log ของ Nginx
sudo tail -f /var/log/nginx/myapp_error.log
sudo tail -f /var/log/nginx/myapp_access.log
# ตรวจสอบ socket file
ls -la /tmp/myapp.sock
# Restart service หลังแก้โค้ด
sudo systemctl restart myapp
# ดูสถานะ service ทั้งหมด
sudo systemctl status myapp nginx
การ Deploy Code ใหม่ (Workflow)
- อัพโหลดหรือ pull code ใหม่มายัง server
- เข้า virtual environment:
source venv/bin/activate - ติดตั้ง dependencies ใหม่:
pip install -r requirements.txt - สำหรับ Django รัน migration:
python manage.py migrate - สำหรับ Django รวม static files:
python manage.py collectstatic --noinput - Restart uWSGI service:
sudo systemctl restart myapp
sudo systemctl reload myapp หรือพิจารณาใช้ blue-green deployment
Checklist ความปลอดภัยก่อน Production
- ปิด
DEBUG = Falseใน Django settings - ตั้งค่า
SECRET_KEYเป็นค่าสุ่มที่ไม่ซ้ำกัน ไม่ควร hardcode ไว้ในโค้ด - ใช้ environment variable สำหรับ sensitive config (
python-dotenv) - ตั้งค่า
ALLOWED_HOSTSให้เฉพาะเจาะจง ไม่ใช้* - เปิดใช้ HTTPS และ redirect HTTP ทั้งหมด
- ตั้งค่า firewall ให้เปิดเฉพาะ port 80, 443 และ 22
- อัพเดท dependency เป็นประจำเพื่อแก้ไข security vulnerability
สรุป
การ deploy Python Flask หรือ Django บน VPS ด้วย uWSGI และ Nginx เป็น stack ที่เสถียร ปลอดภัย และ scale ได้ดีสำหรับ production environment ขั้นตอนหลักที่ต้องจำคือ
- เตรียม environment — Python venv, ติดตั้ง dependencies
- ตั้งค่า uWSGI — สร้าง
uwsgi.iniและ systemd service - ตั้งค่า Nginx — virtual host ที่ proxy ไปยัง uWSGI socket
- เพิ่ม SSL — ใช้ Let's Encrypt ผ่าน Certbot
- ทดสอบและ monitor — ตรวจสอบ log และ service status
เมื่อทำครบทุกขั้นตอนแล้ว Python app ของคุณจะพร้อมรับ traffic จริงได้อย่างมั่นคง หากต้องการ scale เพิ่มเติมในอนาคต สามารถเพิ่มจำนวน processes ใน uWSGI config หรือใช้ Nginx เป็น load balancer ระหว่างหลาย server ได้ทันที
ตรวจสอบว่า Python App ทำงานถูกต้อง
# ตรวจสอบสถานะ uWSGI service
sudo systemctl status myapp
# ตรวจสอบว่า socket file สร้างแล้ว
ls -la /tmp/myapp.sock
# ทดสอบ app ตอบสนองผ่าน Nginx
curl -I http://localhost/
# ควรได้ HTTP/1.1 200 OK
# ดู log real-time เพื่อตรวจ error
sudo journalctl -u myapp -f --no-pager | head -20
# ดู Nginx error log
sudo tail -20 /var/log/nginx/myapp_error.log
systemctl status myapp แสดง active (running) และ socket file มีอยู่ แต่ยังได้ 502 ให้ตรวจ group permission ของ socket — Nginx ต้องมีสิทธิ์อ่าน socketแก้ปัญหาที่พบบ่อย
Error: 502 Bad Gateway จาก Nginx
Nginx ส่ง request ไปยัง uWSGI socket ไม่ได้ มักเกิดจาก socket permission หรือ path ไม่ตรง
# ตรวจสอบ socket มีอยู่และ Nginx อ่านได้
ls -la /tmp/myapp.sock
# ควรเห็น srwxrwx--- หรือ permission ที่ www-data อ่านได้
# แก้ permission ใน uwsgi.ini
# chmod-socket = 664
# chown-socket = appuser:www-data
# ตรวจ Nginx error log เพื่อดู error จริง
sudo tail -20 /var/log/nginx/myapp_error.log
Error: "ModuleNotFoundError" หรือ "ImportError" ตอน uWSGI start
uWSGI ไม่ได้ใช้ Python จาก virtual environment — PATH ผิดใน systemd service
# ตรวจสอบว่า virtualenv path ถูกต้องใน myapp.service
cat /etc/systemd/system/myapp.service | grep "Environment\|ExecStart"
# ต้องมี:
# Environment="PATH=/home/appuser/myapp/venv/bin"
# ExecStart=/home/appuser/myapp/venv/bin/uwsgi --ini uwsgi.ini
# ถ้าแก้ไข service file ต้อง reload daemon ก่อน
sudo systemctl daemon-reload
sudo systemctl restart myapp
Django: "DisallowedHost" error
ALLOWED_HOSTS ใน settings.py ไม่ได้ระบุ domain หรือ IP ของ server
# แก้ settings.py
ALLOWED_HOSTS = ['your-domain.com', 'www.your-domain.com', 'YOUR_SERVER_IP']
# หรือใช้ environment variable
import os
ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '').split(',')
# Restart uWSGI หลังแก้ settings
sudo systemctl restart myapp
Django: "collectstatic" error — STATIC_ROOT ไม่ถูกตั้ง
ต้องกำหนด STATIC_ROOT ใน settings.py ก่อนรัน collectstatic
# เพิ่มใน settings.py
STATIC_ROOT = '/home/appuser/myapp/staticfiles/'
STATICFILES_DIRS = [BASE_DIR / 'static']
# รัน collectstatic
cd /home/appuser/myapp
source venv/bin/activate
python manage.py collectstatic --noinput
# ตรวจสอบว่า Nginx location /static/ ชี้ถูก path
grep -A2 "location /static" /etc/nginx/sites-available/myapp
uWSGI Crash loop — "unable to load configuration" หรือ respawn too fast
ตรวจสอบ error ใน log ก่อน บ่อยครั้งเกิดจาก syntax error ใน Python code หรือ uwsgi.ini
# รัน uWSGI manual เพื่อดู error โดยตรง
cd /home/appuser/myapp
source venv/bin/activate
uwsgi --ini uwsgi.ini
# ตรวจสอบว่า Python app import ได้
python -c "from app import app; print('OK')" # Flask
python -c "import myproject.wsgi; print('OK')" # Django
คำถามที่พบบ่อย
ควรใช้ uWSGI หรือ Gunicorn?
ทั้งคู่ใช้ได้ดี Gunicorn ง่ายกว่า ตั้งค่าน้อยกว่า เหมาะกับโปรเจกต์เริ่มต้น uWSGI มีฟีเจอร์มากกว่า เช่น async workers, Emperor mode จัดการหลาย app พร้อมกัน และ performance สูงกว่าในบางกรณี สำหรับ Flask/Django ทั่วไป Gunicorn ก็เพียงพอแล้ว ใช้คำสั่ง gunicorn app:app -w 4 --bind unix:/tmp/myapp.sock แทน uwsgi ได้เลย
ทำไมต้องใช้ virtual environment?
Virtual environment แยก Python packages ของแต่ละโปรเจกต์ออกจากกัน ป้องกัน version conflict เช่น App A ต้องการ Django 4.2 ส่วน App B ต้องการ Django 5.0 ถ้าไม่มี venv ทั้งสองจะใช้ Django version เดียวกัน ซึ่งทำให้หนึ่งในนั้นพัง นอกจากนี้ยังป้องกันการ install package ใน system Python ที่ Ubuntu/Debian ใช้สำหรับ system tools ด้วย
zero-downtime deploy ทำอย่างไร?
uWSGI รองรับ graceful reload ด้วย signal SIGHUP — workers ใหม่จะขึ้นมาก่อนที่ workers เก่าจะหยุดรับ request
# Graceful reload — ไม่มี downtime
sudo systemctl reload myapp
# หรือส่ง signal ตรงๆ
sudo kill -HUP $(cat /tmp/myapp.pid)