Quay lại blog
Tháng Tám 19, 2026Hướng dẫn

Cách chạy ứng dụng Python với Gunicorn và Nginx trên VPS

Triển khai ứng dụng Flask hoặc FastAPI trên Ubuntu với virtualenv, dịch vụ systemd Gunicorn, reverse proxy Nginx, TLS, tệp môi trường, nhật ký, và danh sách kiểm tra lỗi 502 cùng kích thước worker.

Cách chạy ứng dụng Python với Gunicorn và Nginx trên VPS

Máy chủ Flask tích hợp và uvicorn --reload dành cho phát triển. Trên VPS công cộng bạn muốn trình quản lý tiến trình khởi động lại worker, gắn localhost, và ngồi sau reverse proxy xử lý TLS cùng client chậm. Gunicorn là lựa chọn WSGI thường gặp cho Flask và Django. FastAPI có thể chạy dưới Gunicorn với lớp worker Uvicorn. Nginx (hoặc Caddy) kết thúc HTTPS và chuyển tới 127.0.0.1:8000.

Hướng dẫn này đi qua bố cục sống sót sau khởi động lại: dự án trong /srv/app, virtualenv, .env với bí mật, đơn vị gunicorn.service, khối server Nginx, Let's Encrypt, và tệp nhật ký bạn thực sự grep được. Chúng ta cũng nói về số worker, timeout, và vì sao 502 Bad Gateway gần như không bao giờ là 'Nginx hỏng' — gần như luôn là Gunicorn không chạy, gắn sai socket, hoặc crash khi import. Ví dụ dùng Flask, với ghi chú FastAPI khi lệnh khác.

Vì sao ngăn xếp này

Gunicorn prefork tiến trình worker. Mỗi worker xử lý một yêu cầu một lúc trừ khi bạn dùng lớp worker khác. Nginx đệm client chậm để worker không kẹt gửi byte ra mạng di động. systemd khởi động lại ứng dụng nếu nó chết. Cùng nhau thì nhàm chán, đúng thứ bạn muốn lúc 3 giờ sáng.

  • Gunicorn: mô hình tiến trình WSGI/ASGI ổn định
  • systemd: chạy lúc khởi động, khởi động lại khi crash, nhật ký journald
  • Nginx: TLS, tệp tĩnh, giới hạn kích thước yêu cầu, gzip
  • venv: Python hệ thống giữ sạch
  • gắn localhost: ứng dụng không tới được trừ qua Nginx

Yêu cầu

Python 3.10+ trên Ubuntu 22.04/24.04 là ổn. Đừng pip bằng root vào site-packages hệ thống. Bạn cần tên miền cho TLS. Nếu thích Caddy làm proxy, đơn vị Gunicorn trong bài này giữ nguyên — chỉ cấu hình front thay đổi (xem bài Caddy).

  • VPS Ubuntu 22.04 hoặc 24.04
  • Ứng dụng có requirements.txt hoặc tương đương
  • Điểm vào WSGI (Flask: app:app) hoặc ASGI (FastAPI: app:app với worker uvicorn)
  • Bản ghi A tên miền cho HTTPS

Bước 1: Gói hệ thống, người dùng và thư mục dự án

Tạo người dùng hệ thống không đăng nhập tương tác, sở hữu mã, và chạy Gunicorn. Cài python3-venv và công cụ build tránh pip thất bại trên gói vẫn biên dịch phần mở rộng 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/

Bước 2: Virtualenv và phụ thuộc

Tạo venv bằng appuser để quyền sở hữu tệp đúng. Ghim phiên bản trên production. Sau cài, xác nhận bạn import được ứng dụng trong gunicorn --check-config một lần hoặc import Python. Lỗi import ở đây chính là lỗi sau này thành 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')"

Bước 3: Tệp môi trường

Đừng hardcode SECRET_KEY hoặc URL cơ sở dữ liệu trong đơn vị systemd theo cách kết thúc trong git thế giới đọc được. Dùng EnvironmentFile. chmod 640, chủ root, nhóm appuser (hoặc chủ appuser nếu thích).

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

Bước 4: Dịch vụ systemd Gunicorn

Gắn 127.0.0.1:8000, không 0.0.0.0, trừ khi có lý do bỏ Nginx. Số worker thường là (2 x CPU) + 1 với worker sync; trên VPS 2 vCPU là 5, có thể quá nhiều nếu mỗi worker tải mô hình ML nặng — lúc đó dùng 2–3. Với FastAPI, đặt --worker-class uvicorn.workers.UvicornWorker và cài uvicorn. Timeout nên vượt yêu cầu trung thực chậm nhất, không phải 30 giây nếu bạn có xuất 2 phút.

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

Bước 5: Reverse proxy Nginx và TLS

Nginx lắng nghe 80/443 và proxy tới Gunicorn. client_max_body_size quan trọng với tải lên. proxy_read_timeout nên khớp hoặc vượt timeout của Gunicorn. Sau khi khối server chạy HTTP, cấp chứng chỉ bằng Certbot (hoặc chuyển front sang Caddy). Đoạn dưới chỉ HTTP để thử; rồi chạy Certbot có thể sửa tệp.

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

Bước 6: Tệp tĩnh, quyền và chi tiết Flask/FastAPI

Nginx nên phục vụ tệp tĩnh nếu được; nhanh hơn Gunicorn. Django collectstatic, Flask send_from_directory cho ứng dụng nhỏ, hoặc CDN sau. appuser phải đọc được cây. Nếu dùng FastAPI, cài uvicorn[standard] trong venv và đổi ExecStart dùng UvicornWorker. Nếu dùng socket Unix thay TCP, trỏ proxy_pass tới socket và khớp quyền để www-data ghi được.

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, bộ nhớ và reload không downtime

Worker Gunicorn sync đơn giản và đủ cho API request/response nhẹ CPU. Nếu cần nhiều chờ I/O chậm đồng thời, cân nhắc gevent hoặc worker ASGI — đo, đừng đoán. Mỗi worker tải ứng dụng; RAM ~= số worker nhân RSS ứng dụng. VPS 2 GB với 8 worker của ứng dụng 300 MB sẽ swap và cảm giác 'chậm ngẫu nhiên'. systemctl reload gunicorn (HUP) có thể khởi động lại worker với mã mới nếu bạn triển khai tệp tại chỗ; khởi động lại đầy đủ rõ hơn khi phụ thuộc đổi.

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

Khắc phục 502 và crash im lặng

502 nghĩa là Nginx không nhận phản hồi hợp lệ từ upstream. journalctl -u gunicorn -e là lệnh đầu. Nguyên nhân thường: sai tên module:app, thiếu khóa .env, Postgres không chạy, gắn 127.0.0.1 nhưng Nginx trên host khác, SELinux (hiếm trên Ubuntu), hoặc ứng dụng chỉ lắng nghe IPv6. 504 là hết thời gian. Vòng 301 xảy ra khi ứng dụng chuyển hướng HTTP trong khi X-Forwarded-Proto bị bỏ qua.

  • systemctl status gunicorn — có đang active?
  • journalctl -u gunicorn -n 100 — ImportError, thiếu env, cơ sở dữ liệu
  • curl -v http://127.0.0.1:8000/ từ VPS — nếu cái này thất bại, Nginx vô tội
  • nginx -t và error.log — upstream prematurely closed connection
  • ss -tulpn | grep 8000 — không ai lắng nghe
  • Đĩa đầy — worker crash theo cách bí ẩn

Bảo mật

Ứng dụng không bao giờ gắn công cộng. Bí mật ở .env. Giữ venv và OS đã vá. Đừng chạy Gunicorn bằng root. Nếu xử lý đăng nhập, đặt cookie phiên Secure và SameSite, và cấu hình khung chỉ tin X-Forwarded-Proto từ Nginx. Giới hạn tốc độ đường đăng nhập trong Nginx hoặc trong ứng dụng.

  • gắn 127.0.0.1 hoặc socket unix
  • chmod 600 .env
  • User= không phải root trong systemd
  • TLS qua Certbot hoặc Caddy
  • Tắt chế độ debug và tự reload trên production

Mẹo

  • Thêm đường /health kiểm tra kết nối DB cho Compose và bộ cân bằng tải
  • Gửi tài sản tĩnh với header cache-control trong Nginx
  • Dùng worker hoặc hàng đợi riêng (Redis + systemd) cho email và việc nặng
  • Ghim phiên bản gunicorn và uvicorn trong requirements.txt
  • Chụp snapshot trước lần chuyển production đầu

Ứng dụng Python trên VPS sẵn sàng production khi chạy dưới Gunicorn như dịch vụ systemd, chỉ gắn localhost, và được tới qua Nginx hoặc Caddy với TLS. Đặt bí mật trong EnvironmentFile, chọn kích thước worker theo RAM chứ không theo công thức blog, và gỡ 502 từ journal Gunicorn trước. Khi đường này được ghi cho kho của bạn, mỗi lần triển khai sau là rsync hoặc git pull, pip install, và systemctl restart gunicorn.