Deploy Python Django, Flask lên VPS: gunicorn, nginx, SSL
Hướng dẫn deploy Python Django, Flask lên VPS Ubuntu 24.04/Debian 12: venv, gunicorn, systemd, nginx, SSL, làm tay từng bước hoặc bằng Git deploy của ZoPanel.

Ý chính
- Đừng chạy runserver hay flask run trên production; dùng gunicorn (WSGI) hoặc uvicorn (ASGI) phía sau nginx.
- Ubuntu 24.04 có Python 3.12, Debian 12 có Python 3.11, nên Debian 12 chỉ cài được Django 5.2 LTS, không cài được Django 6.x.
- Luôn cài thư viện trong môi trường ảo (venv); pip toàn hệ thống bị chặn theo PEP 668.
- Ứng dụng nên lắng nghe ở 127.0.0.1, đọc cấu hình từ biến môi trường, và chạy dưới user riêng do systemd quản lý.
Để deploy Python Django hoặc Flask lên VPS, bạn cần bốn thứ: môi trường ảo (venv) chứa thư viện của dự án, một application server như gunicorn chạy code Python, systemd giữ tiến trình luôn sống, và nginx đứng trước để lo tên miền, SSL và file tĩnh. Đừng bao giờ đưa manage.py runserver hay flask run ra production: cả Django lẫn Flask đều ghi rõ server phát triển không được thiết kế để an toàn hay ổn định.
Bài này đi qua cách làm tay từng bước trên Ubuntu 24.04 và Debian 12, kèm cấu hình mẫu chạy được, các lỗi hay gặp, và cách làm cùng việc đó bằng Git deploy của ZoPanel khi bạn không muốn tự viết file systemd và nginx cho mỗi dự án.
Kiến trúc khi deploy Python lên VPS trông thế nào?
Một ứng dụng Python trên production thường có ba tầng:
| Tầng | Vai trò | Ví dụ |
|---|---|---|
| Web server / reverse proxy | Nhận request từ internet, xử lý HTTPS, phục vụ file tĩnh, chuyển request động vào trong | nginx |
| Application server | Chạy code Python, nói chuyện với nginx qua HTTP nội bộ | gunicorn (WSGI), uvicorn (ASGI) |
| Ứng dụng | Code của bạn | Django, Flask, FastAPI |
Django và Flask đều là ứng dụng WSGI, nên gunicorn là lựa chọn phổ biến nhất. Nếu bạn dùng FastAPI hoặc Django với tính năng async (websocket, ASGI), hãy dùng uvicorn. Ứng dụng chỉ nên lắng nghe ở 127.0.0.1, không mở thẳng ra internet; mọi lưu lượng đi qua nginx.
Cần chuẩn bị gì trước khi deploy?
- VPS chạy Ubuntu 24.04 hoặc Debian 12, có quyền sudo. Với một ứng dụng Django nhỏ cùng PostgreSQL, 1 GB RAM là đủ chạy; 2 GB thoải mái hơn khi có thêm Redis hoặc Celery.
- Tên miền đã trỏ về IP của VPS (bản ghi A). Nếu chưa, xem bài trỏ tên miền về VPS.
- Code nằm trong Git, có file
requirements.txt(hoặcpyproject.toml) liệt kê thư viện.
Chú ý phiên bản Python
Đây là chỗ nhiều người vấp mà không hiểu vì sao:
| Hệ điều hành | Python mặc định | Django cài được |
|---|---|---|
| Ubuntu 24.04 | 3.12 | 5.2 LTS, 6.0, 6.1 |
| Debian 12 | 3.11 | 5.2 LTS (Django 6.x cần Python 3.12 trở lên) |
Theo bảng tương thích của Django, Django 6.0 và 6.1 chỉ hỗ trợ Python 3.12 trở lên. Nếu máy dev của bạn dùng Python 3.13 và requirements.txt ghim Django==6.1, lệnh pip install trên Debian 12 sẽ báo không tìm thấy phiên bản phù hợp. Hoặc ghim Django 5.2 LTS, hoặc chọn Ubuntu 24.04 cho server mới.
Ngoài ra, cả hai hệ điều hành đều đánh dấu Python hệ thống là "externally managed" (PEP 668): pip install ra ngoài venv sẽ báo lỗi externally-managed-environment. Đừng "sửa" bằng --break-system-packages; hãy dùng venv như bên dưới.
Cách deploy Python Django, Flask lên VPS thủ công
Các lệnh dưới đây giả định dự án Django tên myproject, đặt tại /srv/myproject, chạy dưới user app. Với Flask, chỉ khác dòng lệnh gunicorn (xem bước 4).
Bước 1: Cài gói hệ thống và tạo user riêng
sudo apt update
sudo apt install -y python3-venv python3-dev build-essential nginx git
sudo apt install -y postgresql libpq-dev # nếu dùng PostgreSQL
sudo adduser --system --group --home /srv/myproject app
Chạy ứng dụng dưới user riêng, không phải root. Nếu code có lỗ hổng, kẻ tấn công chỉ có quyền của user app.
Bước 2: Lấy code và tạo môi trường ảo
sudo -u app git clone https://github.com/you/myproject.git /srv/myproject/app
cd /srv/myproject/app
sudo -u app python3 -m venv .venv
sudo -u app .venv/bin/pip install -r requirements.txt gunicorn
Với repository riêng tư, tạo một cặp khoá SSH cho user app và thêm public key vào repository dưới dạng deploy key. Trên GitHub, deploy key mặc định chỉ có quyền đọc và chỉ dùng cho một repository, đúng thứ bạn cần.
Bước 3: Cấu hình cho production
Đưa mọi bí mật ra biến môi trường. Tạo file /srv/myproject/.env (quyền 600, chủ sở hữu app):
DJANGO_SECRET_KEY=chuoi-ngau-nhien-dai
DJANGO_ALLOWED_HOSTS=example.com,www.example.com
DATABASE_URL=postgresql://myproject:matkhau@127.0.0.1:5432/myproject
Trong settings.py, đọc các biến này và đặt các giá trị mà checklist deploy của Django yêu cầu:
import os
DEBUG = False
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
ALLOWED_HOSTS = os.environ["DJANGO_ALLOWED_HOSTS"].split(",")
CSRF_TRUSTED_ORIGINS = [f"https://{h}" for h in ALLOWED_HOSTS]
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
STATIC_ROOT = BASE_DIR / "staticfiles"
SECURE_PROXY_SSL_HEADER chỉ an toàn khi nginx luôn ghi đè header X-Forwarded-Proto, như cấu hình ở bước 5. Sau đó chạy:
cd /srv/myproject/app
sudo -u app bash -c 'set -a; . ../.env; set +a; .venv/bin/python manage.py migrate'
sudo -u app bash -c 'set -a; . ../.env; set +a; .venv/bin/python manage.py collectstatic --noinput'
sudo -u app bash -c 'set -a; . ../.env; set +a; .venv/bin/python manage.py check --deploy'
check --deploy liệt kê các thiết lập bảo mật còn thiếu. Đọc kỹ kết quả, đừng bỏ qua.
Bước 4: Chạy gunicorn bằng systemd
Tạo /etc/systemd/system/myproject.service:
[Unit]
Description=myproject (gunicorn)
After=network.target
[Service]
User=app
Group=app
WorkingDirectory=/srv/myproject/app
EnvironmentFile=/srv/myproject/.env
ExecStart=/srv/myproject/app/.venv/bin/gunicorn myproject.wsgi:application \
--bind 127.0.0.1:8000 --workers 3 --timeout 60
Restart=on-failure
[Install]
WantedBy=multi-user.target
Với Flask, đổi myproject.wsgi:application thành app:app (module app.py, biến app). Bật dịch vụ:
sudo systemctl daemon-reload
sudo systemctl enable --now myproject
sudo systemctl status myproject
curl -I http://127.0.0.1:8000/
Bao nhiêu worker? Tài liệu gunicorn gợi ý điểm xuất phát là (2 × số nhân CPU) + 1, rồi chỉnh theo tải thực tế. Nhưng mỗi worker là một tiến trình Python đầy đủ, có thể tốn từ vài chục MB trở lên tuỳ ứng dụng. Trên VPS 1 GB, 3 worker hợp lý hơn công thức. Thiếu RAM thì hãy giảm worker trước khi nghĩ tới nâng cấp server.
Bước 5: nginx làm reverse proxy
Tạo /etc/nginx/sites-available/myproject:
server {
listen 80;
server_name example.com www.example.com;
client_max_body_size 20m;
location /static/ {
alias /srv/myproject/app/staticfiles/;
}
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
sudo ln -s /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
nginx chạy dưới user www-data, nên thư mục staticfiles và các thư mục cha phải cho phép www-data đọc. Lỗi 403 cho CSS/JS gần như luôn do quyền thư mục. Một cách khác là dùng thư viện WhiteNoise để Django tự phục vụ file tĩnh, khi đó bỏ được khối location /static/.
Bước 6: Cài SSL
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
Certbot sửa cấu hình nginx, thêm chuyển hướng HTTPS và đặt lịch gia hạn tự động. Chi tiết hơn trong bài cài SSL miễn phí Let's Encrypt.
Bước 7: Quy trình cập nhật code
Mỗi lần có code mới:
cd /srv/myproject/app
sudo -u app git pull
sudo -u app .venv/bin/pip install -r requirements.txt
# chạy migrate và collectstatic như bước 3
sudo systemctl restart myproject
Cách này có vài giây gián đoạn khi gunicorn khởi động lại, và nếu bản mới lỗi lúc khởi động thì website sập cho tới khi bạn sửa. Với dự án nhỏ, chấp nhận được. Với dự án có khách hàng thật, bạn cần cơ chế phát hành không gián đoạn và rollback, phần tiếp theo nói về điều đó.
Những lỗi hay gặp khi deploy Django, Flask
| Triệu chứng | Nguyên nhân thường gặp |
|---|---|
| 400 Bad Request ở mọi trang | Tên miền chưa có trong ALLOWED_HOSTS |
| 403 CSRF khi submit form qua HTTPS | Thiếu CSRF_TRUSTED_ORIGINS, hoặc nginx không gửi X-Forwarded-Proto |
| 502 Bad Gateway | gunicorn không chạy hoặc bind sai cổng; xem journalctl -u myproject -n 100 |
| Trang không có CSS | Chưa chạy collectstatic, sai đường dẫn alias, hoặc quyền thư mục |
| Vòng lặp chuyển hướng HTTPS | SECURE_SSL_REDIRECT bật nhưng Django không biết request đã là HTTPS |
Worker bị kill, log ghi WORKER TIMEOUT |
Request chạy quá --timeout; đưa việc nặng sang hàng đợi (Celery, RQ) |
Deploy Python lên VPS bằng ZoPanel
Nếu server của bạn chạy ZoPanel, phần lớn các bước trên được làm tự động, và bạn có thêm phát hành không gián đoạn mà làm tay khá mất công.
- Quản trị viên cài runtime Python ở mục Runtime (dùng Python 3 của hệ thống, kèm môi trường ảo).
- Vào Website → Thêm website, chọn Git deploy, nhập Địa chỉ repository và Nhánh, giữ SSL miễn phí (Let's Encrypt) bật. Với repository riêng tư, lấy khoá ở Deploy → Deploy key (repository riêng tư) và thêm vào repository.
- ZoPanel nhận diện Django, Flask hoặc FastAPI từ
manage.py,requirements.txt,pyproject.tomlhayPipfile. Lệnh cài đặt tạo venv.venv, cài thư viện và tự thêm gunicorn hoặc uvicorn nếu thiếu. Lệnh chạy đề xuất cho Django là.venv/bin/gunicorn <project>.wsgi:application --bind 127.0.0.1:$PORT, cho Flask là.venv/bin/gunicorn app:app --bind 127.0.0.1:$PORT. Bấm Phân tích repository để xem trước. - Đặt
DJANGO_SECRET_KEY,ALLOWED_HOSTSvà chuỗi kết nối database trong Biến môi trường (có nút Dán .env). Database PostgreSQL hoặc MariaDB tạo ở trang Cơ sở dữ liệu, kết nối qua127.0.0.1. - Nếu cần chạy
migratevàcollectstaticmỗi lần deploy, đặt vào Lệnh build, nối bằng&&.
Điểm quan trọng nhất: ứng dụng phải lắng nghe ở 127.0.0.1:$PORT, không ghi cứng cổng, vì ZoPanel dùng hai slot với hai cổng khác nhau. Mỗi bản phát hành được build trong thư mục riêng, khởi động ở slot đang rảnh, phải trả lời Đường dẫn health check với mã dưới 500 trong 90 giây, rồi nginx mới chuyển lưu lượng sang. Bản mới lỗi thì bản cũ vẫn chạy nguyên. Mục Các bản release giữ 5 bản gần nhất để Rollback cũng không gián đoạn.
Vì hai bản chạy song song trong chốc lát, migration nên tương thích ngược (thêm cột trước, xoá cột ở lần phát hành sau). File người dùng tải lên phải nằm trong Đường dẫn lưu trữ cố định (ví dụ media), không nằm trong thư mục release.
Build chạy dưới user của tài khoản hosting, trong giới hạn CPU và RAM của gói, không ghi được ra ngoài thư mục home. Log ứng dụng ở tab Deploy hiển thị 400 dòng output mới nhất; lỗi 502 thường nghĩa là app không lắng nghe ở $PORT, và Chạy chẩn đoán sẽ chỉ ra. Để tự deploy mỗi khi push code, xem bài deploy tự động từ GitHub và tài liệu Git deploy.
Kết luận
Deploy Python lên VPS không khó khi bạn nắm rõ ba tầng: nginx ở ngoài, gunicorn ở giữa, ứng dụng ở trong, tất cả dưới một user không phải root và do systemd quản lý. Làm tay một lần là cách tốt để hiểu hệ thống. Khi số dự án tăng lên, hoặc bạn cần phát hành không gián đoạn và rollback một cú nhấp, một panel làm sẵn những việc đó sẽ tiết kiệm nhiều giờ mỗi tháng.
Gói Free của ZoPanel cho 3 tài khoản hosting, 10 website và 10 database, không giới hạn thời gian, và Git deploy chạy đầy đủ trên Free, đủ để thử với dự án Django hoặc Flask của bạn. Xem bảng giá khi cần nhiều hơn.
Câu hỏi thường gặp
Có nên dùng python manage.py runserver trên VPS không?
Không. Server phát triển của Django và Flask chỉ dành cho máy dev, không được thiết kế để an toàn, ổn định hay hiệu quả. Trên production, dùng gunicorn (WSGI) hoặc uvicorn (ASGI) phía sau nginx.
Nên chọn gunicorn hay uvicorn?
Django và Flask thông thường là WSGI, dùng gunicorn. FastAPI, Starlette hoặc Django dùng websocket và ASGI thì dùng uvicorn. Cả hai đều phải đứng sau nginx.
Vì sao pip install báo lỗi externally-managed-environment?
Ubuntu 24.04 và Debian 12 đánh dấu Python hệ thống là do apt quản lý (PEP 668), để pip không ghi đè thư viện mà hệ điều hành cần. Hãy tạo venv bằng python3 -m venv .venv và cài thư viện vào đó, đừng dùng --break-system-packages trên server lâu dài.
VPS bao nhiêu RAM thì chạy được Django?
Một ứng dụng Django nhỏ với 2 đến 3 worker gunicorn và PostgreSQL chạy được trên 1 GB RAM. Mỗi worker là một tiến trình riêng, nên RAM tăng gần tuyến tính theo số worker; thêm Redis, Celery hoặc nhiều dự án thì nên có từ 2 GB.
Deploy Flask khác Django ở điểm nào?
Gần như giống hệt: venv, gunicorn, systemd, nginx. Khác ở điểm vào WSGI (app:app thay vì myproject.wsgi:application) và Flask không có collectstatic, file tĩnh nằm sẵn trong thư mục static. Nếu chạy sau proxy, dùng ProxyFix của Werkzeug để Flask nhận đúng scheme và IP.
Đọc tiếp

Deploy tự động từ GitHub lên VPS: webhook, Actions, không downtime
Ba cách deploy tự động từ GitHub lên VPS: GitHub Actions qua SSH, webhook, hoặc Git deploy của panel, kèm script release, health check và rollback.

Deploy ASP.NET Core lên VPS Linux
Cách deploy .NET lên Linux: cài .NET 10 trên Ubuntu 24.04, publish, chạy Kestrel bằng systemd sau nginx với SSL, làm tay hoặc bằng ZoPanel.

Deploy Laravel lên VPS: queue, scheduler và cập nhật không gián đoạn
Cách deploy Laravel lên VPS Ubuntu 24.04: PHP-FPM, nginx, .env, queue worker, scheduler và release không downtime, làm tay hoặc với ZoPanel.