กลับไปหน้าบล็อก
สิงหาคม 19, 2026คู่มือ

วิธีรันแอป Python ด้วย Gunicorn และ Nginx บน VPS

ดีพลอยแอป Flask หรือ FastAPI บน Ubuntu ด้วย virtualenv บริการ systemd ของ Gunicorn รีเวิร์สพร็อกซี Nginx TLS ไฟล์สภาพแวดล้อม การบันทึกล็อก และรายการตรวจข้อผิดพลาด 502 กับขนาด worker

วิธีรันแอป Python ด้วย Gunicorn และ Nginx บน VPS

เซิร์ฟเวอร์ Flask ในตัวและ uvicorn --reload มีไว้สำหรับพัฒนา บน VPS สาธารณะคุณต้องการตัวจัดการโปรเซสที่รีสตาร์ต worker ผูก localhost และนั่งหลังรีเวิร์สพร็อกซีที่จัดการ TLS กับไคลเอนต์ช้า Gunicorn คือตัวเลือก WSGI ปกติสำหรับ Flask และ Django FastAPI รันใต้ Gunicorn ด้วยคลาส worker Uvicorn ได้ Nginx (หรือ Caddy) จบ HTTPS แล้วส่งต่อไป 127.0.0.1:8000

คู่มือนี้พาโครงที่รอดหลังรีบูต: โปรเจกต์ใน /srv/app, virtualenv, .env พร้อมความลับ, ยูนิต gunicorn.service, บล็อกเซิร์ฟเวอร์ Nginx, Let's Encrypt และไฟล์ล็อกที่คุณ grep ได้จริง เราจะพูดถึงจำนวน worker, หมดเวลา และทำไม 502 Bad Gateway เกือบไม่เคยเป็น 'Nginx พัง' — เกือบเสมอคือ Gunicorn ไม่รัน ผูกซ็อกเก็ตผิด หรือพังตอน import ตัวอย่างใช้ Flask พร้อมบันทึก FastAPI เมื่อคำสั่งต่าง

ทำไมสแตกนี้

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

  • Gunicorn: โมเดลโปรเซส WSGI/ASGI ที่เสถียร
  • systemd: เริ่มตอนบูต รีสตาร์ตตอนพัง ล็อก journald
  • Nginx: TLS ไฟล์สแตติก ขีดจำกัดขนาดคำขอ gzip
  • venv: Python ของระบบสะอาด
  • ผูก localhost: แอปถึงได้เฉพาะผ่าน Nginx

ความต้องการ

Python 3.10+ บน Ubuntu 22.04/24.04 ใช้ได้ อย่า pip เป็น root เข้า site-packages ของระบบ คุณต้องมีโดเมนสำหรับ TLS ถ้าชอบ Caddy เป็นพร็อกซี ยูนิต Gunicorn ในบทความนี้คงเดิม — เปลี่ยนแค่คอนฟิกด้านหน้า (ดูบทความ Caddy)

  • VPS Ubuntu 22.04 หรือ 24.04
  • แอปพลิเคชันที่มี requirements.txt หรือเทียบเท่า
  • จุดเข้า WSGI (Flask: app:app) หรือ ASGI (FastAPI: app:app พร้อม worker uvicorn)
  • เรคคอร์ด A ของโดเมนสำหรับ HTTPS

ขั้นตอนที่ 1: แพ็กเกจระบบ ผู้ใช้ และไดเรกทอรีโปรเจกต์

สร้างผู้ใช้ระบบที่ล็อกอินโต้ตอบไม่ได้ เป็นเจ้าของโค้ด และรัน Gunicorn การติดตั้ง python3-venv กับเครื่องมือบิลด์ช่วยเลี่ยง pip ล้มเหลวบนแพ็กเกจที่ยังคอมไพล์ส่วนขยาย C

bash
ssh root@YOUR_VPS_IP
apt update && apt -y upgrade
apt -y install python3 python3-venv python3-pip python3-dev build-essential nginx curl

adduser --system --group --home /srv/app appuser
mkdir -p /srv/app
chown appuser:appuser /srv/app

# Copy your code (example):
# rsync -a --delete ./myproject/ appuser@YOUR_VPS_IP:/srv/app/

ขั้นตอนที่ 2: Virtualenv และส่วนที่ต้องพึ่ง

สร้าง venv เป็น appuser เพื่อให้ความเป็นเจ้าของไฟล์ถูกต้อง ปักเวอร์ชันในโปรดักชัน หลังติดตั้ง ยืนยันว่าอิมพอร์ตแอปได้ใน gunicorn --check-config ครั้งเดียวหรืออิมพอร์ต Python ข้อผิดพลาดอิมพอร์ตที่นี่คือข้อผิดพลาดเดียวกันที่กลายเป็น 502 ทีหลัง

bash
sudo -u appuser -H bash -lc '
cd /srv/app
python3 -m venv /srv/app/venv
/srv/app/venv/bin/pip install --upgrade pip
/srv/app/venv/bin/pip install -r /srv/app/requirements.txt gunicorn
'

# Flask example check:
sudo -u appuser -H /srv/app/venv/bin/python -c "from app import app; print('import ok')"

ขั้นตอนที่ 3: ไฟล์สภาพแวดล้อม

อย่าฮาร์ดโค้ด SECRET_KEY หรือ URL ฐานข้อมูลในยูนิต systemd ในทางที่จบใน git ที่โลกอ่านได้ ใช้ EnvironmentFile chmod 640 เจ้าของ root กลุ่ม appuser (หรือเจ้าของ appuser ถ้าชอบ)

bash
cat >/srv/app/.env <<'EOF'
FLASK_ENV=production
SECRET_KEY=replace-with-openssl-rand-hex-32
DATABASE_URL=postgresql://app:password@127.0.0.1:5432/app
EOF
chown appuser:appuser /srv/app/.env
chmod 600 /srv/app/.env

ขั้นตอนที่ 4: บริการ systemd ของ Gunicorn

ผูก 127.0.0.1:8000 ไม่ใช่ 0.0.0.0 เว้นแต่มีเหตุผลข้าม Nginx จำนวน worker มักเป็น (2 x CPU) + 1 สำหรับ worker แบบซิงค์ บน VPS 2 vCPU คือ 5 ซึ่งอาจมากไปถ้าแต่ละ worker โหลดโมเดล ML หนัก — ตอนนั้นใช้ 2–3 สำหรับ FastAPI ตั้ง --worker-class uvicorn.workers.UvicornWorker และติดตั้ง uvicorn หมดเวลาควรเกินคำขอที่ซื่อสัตย์ช้าที่สุด ไม่ใช่ 30 วินาทีถ้าคุณมีการส่งออก 2 นาที

bash
cat >/etc/systemd/system/gunicorn.service <<'EOF'
[Unit]
Description=Gunicorn for the web app
After=network.target

[Service]
User=appuser
Group=appuser
WorkingDirectory=/srv/app
EnvironmentFile=/srv/app/.env
ExecStart=/srv/app/venv/bin/gunicorn --workers 3 --bind 127.0.0.1:8000 --timeout 60 --access-logfile - --error-logfile - app:app
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable --now gunicorn
systemctl status gunicorn --no-pager
ss -tulpn | grep 8000

ขั้นตอนที่ 5: รีเวิร์สพร็อกซี Nginx และ TLS

Nginx ฟัง 80/443 และพร็อกซีไป Gunicorn client_max_body_size สำคัญกับการอัปโหลด proxy_read_timeout ควรตรงหรือเกินหมดเวลาของ Gunicorn หลังบล็อกเซิร์ฟเวอร์ใช้ได้บน HTTP ให้ออกใบรับรองด้วย Certbot (หรือสลับด้านหน้าเป็น Caddy) สนิปเป็ตด้านล่างเป็น HTTP อย่างเดียวเพื่อทดสอบ แล้วรัน Certbot ซึ่งแก้ไฟล์ได้

bash
cat >/etc/nginx/sites-available/app <<'EOF'
server {
    listen 80;
    server_name app.example.com;
    client_max_body_size 20m;

    location /static/ {
        alias /srv/app/static/;
    }

    location / {
        proxy_pass http://127.0.0.1:8000;
        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_read_timeout 90s;
    }
}
EOF

ln -sf /etc/nginx/sites-available/app /etc/nginx/sites-enabled/app
nginx -t && systemctl reload nginx

ufw allow OpenSSH
ufw allow 'Nginx Full'
ufw enable

apt -y install certbot python3-certbot-nginx
certbot --nginx -d app.example.com

ขั้นตอนที่ 6: ไฟล์สแตติก สิทธิ์ และรายละเอียด Flask/FastAPI

Nginx ควรเสิร์ฟไฟล์สแตติกถ้าทำได้ เร็วกว่า Gunicorn Django collectstatic, Flask send_from_directory สำหรับแอปจิ๋ว หรือ CDN ทีหลัง appuser ต้องอ่านต้นไม้ได้ ถ้าใช้ FastAPI ให้ติดตั้ง uvicorn[standard] ใน venv และเปลี่ยน ExecStart ให้ใช้ UvicornWorker ถ้าใช้ซ็อกเก็ต Unix แทน TCP ให้ชี้ proxy_pass ไปซ็อกเก็ตและจับสิทธิ์ให้ www-data เขียนได้

bash
# FastAPI ExecStart example:
# ExecStart=/srv/app/venv/bin/gunicorn -k uvicorn.workers.UvicornWorker --workers 2 --bind 127.0.0.1:8000 app:app

# Unix socket variant:
# --bind unix:/run/gunicorn/app.sock
# Nginx: proxy_pass http://unix:/run/gunicorn/app.sock:

chown -R appuser:appuser /srv/app

Worker หน่วยความจำ และการโหลดใหม่โดยไม่ downtime

Worker Gunicorn แบบซิงค์เรียบง่ายและพอสำหรับ API คำขอ/ตอบที่เบา CPU ถ้าต้องการรอ I/O ช้าจำนวนมากพร้อมกัน ให้พิจารณา gevent หรือ worker ASGI — วัด อย่าเดา แต่ละ worker โหลดแอปของคุณ RAM ~= จำนวน worker คูณ RSS ของแอป VPS 2 GB กับ worker 8 ตัวของแอป 300 MB จะสลับและรู้สึก 'ช้าแบบสุ่ม' systemctl reload gunicorn (HUP) รีสตาร์ต worker ด้วยโค้ดใหม่ได้ถ้าคุณดีพลอยไฟล์ที่เดิม การรีสตาร์ตเต็มชัดกว่าเมื่อส่วนที่ต้องพึ่งเปลี่ยน

bash
# After git pull / rsync of new code:
sudo -u appuser -H /srv/app/venv/bin/pip install -r /srv/app/requirements.txt
systemctl restart gunicorn
curl -I https://app.example.com/health

แก้ 502 และการพังเงียบ

502 หมายความ Nginx ไม่ได้คำตอบที่ใช้ได้จากอัปสตรีม journalctl -u gunicorn -e คือคำสั่งแรก สาเหตุทั่วไป: ชื่อ module:app ผิด คีย์ .env หาย Postgres ไม่รัน ผูก 127.0.0.1 แต่ Nginx อยู่โฮสต์อื่น SELinux (หายากบน Ubuntu) หรือแอปฟังเฉพาะ IPv6 504 คือหมดเวลา ลูป 301 เกิดเมื่อแอปเปลี่ยนทางไป HTTP ในขณะที่ X-Forwarded-Proto ถูกละเลย

  • systemctl status gunicorn — active หรือยัง
  • journalctl -u gunicorn -n 100 — ImportError env หาย ฐานข้อมูล
  • curl -v http://127.0.0.1:8000/ จาก VPS — ถ้าอันนี้ล้มเหลว Nginx ไม่ผิด
  • nginx -t และ error.log — upstream prematurely closed connection
  • ss -tulpn | grep 8000 — ไม่มีใครฟัง
  • ดิสก์เต็ม — worker พังแบบลึกลับ

ความปลอดภัย

แอปไม่ผูกสาธารณะ ความลับอยู่ใน .env แพตช์ venv และ OS อย่ารัน Gunicorn เป็น root ถ้าจัดการล็อกอิน ให้ตั้งคุกกี้เซสชัน Secure และ SameSite และตั้งเฟรมเวิร์กให้เชื่อ X-Forwarded-Proto จาก Nginx เท่านั้น จำกัดอัตราเส้นทางล็อกอินใน Nginx หรือในแอป

  • ผูก 127.0.0.1 หรือซ็อกเก็ต unix
  • chmod 600 .env
  • User= ที่ไม่ใช่ root ใน systemd
  • TLS ผ่าน Certbot หรือ Caddy
  • ปิดโหมดดีบักและการโหลดใหม่อัตโนมัติในโปรดักชัน

เคล็ดลับ

  • เพิ่มเส้นทาง /health ที่ตรวจการเชื่อมต่อ DB สำหรับ Compose และโหลดบาลานเซอร์
  • ส่งสินทรัพย์สแตติกพร้อมส่วนหัว cache-control ใน Nginx
  • ใช้ worker หรือคิวแยก (Redis + systemd) สำหรับอีเมลและงานหนัก
  • ปักเวอร์ชัน gunicorn และ uvicorn ใน requirements.txt
  • ถ่ายสแนปช็อตก่อนตัดโปรดักชันครั้งแรก

แอป Python บน VPS พร้อมโปรดักชันเมื่อรันใต้ Gunicorn เป็นบริการ systemd ผูกเฉพาะ localhost และถึงได้ผ่าน Nginx หรือ Caddy พร้อม TLS ใส่ความลับใน EnvironmentFile กำหนดขนาด worker ตาม RAM ไม่ใช่สูตรบล็อก และดีบัก 502 จากบันทึก Gunicorn ก่อน เมื่อเส้นนี้ถูกบันทึกสำหรับที่เก็บของคุณ ทุกการดีพลอยหลังจากนี้คือ rsync หรือ git pull, pip install และ systemctl restart gunicorn