# 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.

Source: https://zopanel.net/vi/blog/deploy-python-app-vps  
Updated: 2026-10-21

## Ý 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](/vi/blog/point-domain-to-vps).
- **Code nằm trong Git**, có file `requirements.txt` (hoặc `pyproject.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](https://docs.djangoproject.com/en/dev/faq/install/), 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

```bash
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

```bash
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](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/managing-deploy-keys) 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`):

```bash
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](https://docs.djangoproject.com/en/stable/howto/deployment/checklist/) yêu cầu:

```python
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:

```bash
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`:

```ini
[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ụ:

```bash
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](https://docs.gunicorn.org/en/21.0.1/design.html) 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`:

```nginx
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;
    }
}
```

```bash
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

```bash
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](/vi/blog/free-ssl-lets-encrypt).

### Bước 7: Quy trình cập nhật code

Mỗi lần có code mới:

```bash
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](/vi/docs/apps-node-python), 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.

1. 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).
2. 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.
3. ZoPanel nhận diện Django, Flask hoặc FastAPI từ `manage.py`, `requirements.txt`, `pyproject.toml` hay `Pipfile`. 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.
4. Đặt `DJANGO_SECRET_KEY`, `ALLOWED_HOSTS` và 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 qua `127.0.0.1`.
5. Nếu cần chạy `migrate` và `collectstatic` mỗ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](/vi/blog/git-auto-deploy) và tài liệu [Git deploy](/vi/docs/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á](/vi/pricing) 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.
