Ứng dụng chạy tốt trên máy bạn không có nghĩa là sẵn sàng cho môi trường thật. Bài này đi qua cách triển khai một ứng dụng Python lên máy chủ Linux theo mô hình phổ biến nhất: Nginx đứng trước, Gunicorn chạy ứng dụng, systemd quản lý tiến trình.
Vì sao cần cả Nginx lẫn Gunicorn?
Máy chủ phát triển đi kèm framework (app.run() của Flask, runserver của Django) chỉ xử lý một request tại một thời điểm và không được thiết kế để chịu tải. Nó cũng không nên tiếp xúc trực tiếp với internet.
Gunicorn là máy chủ WSGI thật: nó chạy nhiều tiến trình worker song song, tự khởi động lại worker bị treo, và xử lý được tải thực tế.
Nginx đứng trước Gunicorn để lo những việc Gunicorn làm không tốt: kết thúc TLS, phục vụ file tĩnh, nén nội dung, giới hạn tốc độ request, và đệm dữ liệu từ các client kết nối chậm.
Chuẩn bị trên máy chủ
sudo adduser --system --group --home /opt/myapp myapp
sudo -u myapp python3 -m venv /opt/myapp/.venv
sudo -u myapp /opt/myapp/.venv/bin/pip install -r requirements.txt gunicorn
Chạy ứng dụng bằng một user riêng, không dùng root và cũng không dùng chung user với ứng dụng khác. Nếu ứng dụng bị chiếm quyền, thiệt hại sẽ giới hạn trong phạm vi user đó.
Chạy thử Gunicorn
/opt/myapp/.venv/bin/gunicorn \
--workers 4 \
--bind unix:/run/myapp/gunicorn.sock \
myapp.wsgi:application
Với ứng dụng FastAPI, bạn cần worker class của Uvicorn:
gunicorn main:app \
--workers 4 \
--worker-class uvicorn.workers.UvicornWorker \
--bind unix:/run/myapp/gunicorn.sock
Dùng Unix socket thay vì cổng TCP khi Nginx và Gunicorn nằm cùng máy — nhanh hơn và không mở thêm cổng ra ngoài.
Chọn số worker
Công thức thường được nhắc là (2 × số_lõi) + 1. Đây chỉ là điểm khởi đầu. Ứng dụng nặng CPU cần ít worker hơn; ứng dụng chủ yếu chờ I/O có thể dùng nhiều hơn hoặc chuyển sang worker bất đồng bộ. Hãy đo bằng tải thật thay vì tin vào công thức.
Quản lý bằng systemd
Tạo file /etc/systemd/system/myapp.service:
[Unit]
Description=Ứng dụng myapp
After=network.target
[Service]
Type=notify
User=myapp
Group=myapp
RuntimeDirectory=myapp
WorkingDirectory=/opt/myapp
Environment="PATH=/opt/myapp/.venv/bin"
EnvironmentFile=/opt/myapp/.env
ExecStart=/opt/myapp/.venv/bin/gunicorn \
--workers 4 \
--bind unix:/run/myapp/gunicorn.sock \
myapp.wsgi:application
ExecReload=/bin/kill -s HUP $MAINPID
Restart=always
RestartSec=5
# Siết quyền của tiến trình
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/myapp/media
[Install]
WantedBy=multi-user.target
Những dòng ở cuối đáng chú ý: ProtectSystem=strict khiến toàn bộ hệ thống tệp ở chế độ chỉ đọc đối với tiến trình này, trừ những đường dẫn bạn liệt kê trong ReadWritePaths. Nếu ứng dụng bị khai thác, kẻ tấn công không ghi được file ra ngoài thư mục cho phép.
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
sudo systemctl status myapp
Cấu hình Nginx
server {
listen 80;
server_name myapp.example.com;
client_max_body_size 20M;
location /static/ {
alias /opt/myapp/static/;
expires 30d;
access_log off;
}
location / {
proxy_pass http://unix:/run/myapp/gunicorn.sock;
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_redirect off;
}
}
Bốn header proxy_set_header là bắt buộc. Thiếu X-Forwarded-Proto, ứng dụng sẽ tưởng request đến qua HTTP và sinh ra các đường dẫn chuyển hướng sai giao thức.
Bật HTTPS
sudo certbot --nginx -d myapp.example.com --redirect
Sau khi cấp chứng chỉ, hãy kiểm tra cơ chế tự gia hạn thực sự hoạt động — đây là chỗ rất dễ bị bỏ quên cho tới khi chứng chỉ hết hạn:
sudo systemctl is-enabled certbot.timer
sudo certbot renew --dry-run
Biến môi trường và bí mật
Đừng đưa mật khẩu database hay khóa bí mật vào mã nguồn. Đặt chúng trong file .env chỉ user chạy ứng dụng đọc được:
sudo chown myapp:myapp /opt/myapp/.env
sudo chmod 600 /opt/myapp/.env
Với Django, nhớ tắt chế độ gỡ lỗi trước khi lên môi trường thật. Để DEBUG = True ngoài internet đồng nghĩa với việc phơi bày cấu hình, đường dẫn hệ thống và một phần mã nguồn cho bất kỳ ai gây được lỗi trên trang.
Cập nhật ứng dụng
cd /opt/myapp
sudo -u myapp git pull
sudo -u myapp .venv/bin/pip install -r requirements.txt
sudo -u myapp .venv/bin/python manage.py migrate
sudo -u myapp .venv/bin/python manage.py collectstatic --noinput
sudo systemctl reload myapp
Lệnh reload gửi tín hiệu HUP, Gunicorn sẽ khởi động worker mới rồi mới tắt worker cũ. Người dùng đang truy cập không bị ngắt giữa chừng.