📢Đang tải nội dung...

🌐 Hướng dẫn cấu hình web lên VPS Ubuntu

Triển khai ứng dụng Python Flask bằng Gunicorn, Nginx và HTTPS. Ví dụ dùng Ubuntu 24.04 LTS, tên miền proxy.timecloud.vn, thư mục /srv/webapp và cổng nội bộ 8000. Thay tên miền bằng tên miền bạn quản lý trong mọi cấu hình bên dưới.

Luồng truy cập: Trình duyệt → Nginx (80/443) → Gunicorn (127.0.0.1:8000) → Flask.

Luồng truy cập từ trình duyệt đến ứng dụng Flask
Luồng truy cập từ trình duyệt đến ứng dụng Flask · Nhấn ảnh để xem lớn.
1. Chuẩn bị VPS và tên miền
  • Đăng nhập SSH bằng tài khoản có quyền sudo; xem hướng dẫn truy cập VPS Ubuntu.
  • Tạo bản ghi DNS A trỏ tên miền về IPv4 của VPS. Nếu có AAAA, IPv6 cũng phải trỏ đúng; bỏ bản ghi IPv6 cũ nếu không sử dụng.
  • Mở TCP 80, 443 và cổng SSH thực tế trên firewall của nhà cung cấp VPS. Không cần công khai cổng 8000.
  • Hướng dẫn dành cho một ứng dụng Flask có app.py chứa biến app, cùng templates/ và static/. Web dùng công nghệ khác cần lệnh chạy riêng.
Minh họa bản ghi DNS A cho tên miền phụ proxy
Minh họa bản ghi DNS A cho tên miền phụ proxy · Nhấn ảnh để xem lớn.
2. Cài công cụ trên Ubuntu
sudo apt update
sudo apt install -y python3 python3-venv python3-pip nginx curl ufw nano certbot python3-certbot-nginx
sudo systemctl enable --now nginx

Nếu dùng UFW, cho phép đúng cổng SSH trước khi bật firewall. Ví dụ dưới đây dùng SSH cổng 22; thay bằng cổng thực tế nếu khác.

sudo ufw allow 22/tcp
sudo ufw allow 'Nginx Full'
sudo ufw enable
sudo ufw status
3. Tạo tài khoản chạy web và đưa mã nguồn lên VPS

Với lần cài mới, tạo tài khoản dịch vụ riêng:

sudo adduser --system --group --home /srv/webapp webapp
sudo install -d -o webapp -g webapp -m 0755 /srv/webapp

Dùng SFTP của Bitvise để tải mã nguồn vào thư mục tạm ~/webapp-upload của tài khoản SSH. Chỉ tải app.py, templates/, static/ và các module ứng dụng cần; không tải môi trường Python hoặc thư mục cache từ Windows. Sau đó:

sudo cp -r ~/webapp-upload/. /srv/webapp/
sudo chown -R webapp:webapp /srv/webapp
sudo -u webapp test -f /srv/webapp/app.py

Nếu mã đã nằm ở /root/code, có thể dùng sudo cp -r /root/code/. /srv/webapp/ thay bước sao chép trên. Sau đó vẫn cần gán quyền và tạo môi trường Python mới trên Ubuntu.

Cấu trúc thư mục ứng dụng trên VPS Ubuntu
Cấu trúc thư mục ứng dụng trên VPS Ubuntu · Nhấn ảnh để xem lớn.
4. Tạo môi trường Python riêng
sudo -u webapp python3 -m venv /srv/webapp/.venv
sudo -u webapp /srv/webapp/.venv/bin/python -m pip install --upgrade pip
sudo -u webapp /srv/webapp/.venv/bin/python -m pip install Flask gunicorn 'requests[socks]'

Lệnh trên cài các thư viện cần cho app.py của website hiện tại. Nếu mã nguồn dùng thêm thư viện, bổ sung vào môi trường này. Không cài trực tiếp requirements.txt hiện tại trên Ubuntu: file có các gói Windows như pywin32, PyGetWindow và các gói desktop không cần cho web.

Dùng venv để tránh lỗi externally-managed-environment; không dùng sudo pip hoặc --break-system-packages. Khi đã kiểm tra hoạt động, lưu phiên bản để lần cài sau dùng lại:

sudo -u webapp /srv/webapp/.venv/bin/python -m pip freeze | sudo tee /srv/webapp/requirements-ubuntu.txt > /dev/null
5. Chạy thử Gunicorn ở cổng 8000
cd /srv/webapp
sudo -u webapp /srv/webapp/.venv/bin/gunicorn --worker-class gthread --workers 1 --threads 8 --bind 127.0.0.1:8000 --timeout 120 --access-logfile - --error-logfile - app:app

app:app nghĩa là biến app trong module app.py. Cấu hình dùng gthread để xử lý đồng thời các yêu cầu và luồng kiểm tra proxy; số worker/thread cần điều chỉnh theo tải thực tế. Cách này không cần cài gevent.

Giữ terminal đang chạy, mở terminal SSH thứ hai để thử:

curl -I http://127.0.0.1:8000/docs

Nếu nhận HTTP 200, quay lại terminal đầu và nhấn Ctrl + C để dừng chạy thử trước khi tạo dịch vụ; tránh hai tiến trình cùng chiếm cổng 8000.

6. Tạo dịch vụ tự khởi động bằng systemd

Mở file dịch vụ:

sudo nano /etc/systemd/system/webapp.service
[Unit]
Description=Gunicorn for Flask webapp
After=network.target

[Service]
User=webapp
Group=webapp
WorkingDirectory=/srv/webapp
Environment=PYTHONUNBUFFERED=1
ExecStart=/srv/webapp/.venv/bin/gunicorn --worker-class gthread --workers 1 --threads 8 --bind 127.0.0.1:8000 --timeout 120 --access-logfile - --error-logfile - app:app
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

Trong nano: Ctrl + O, Enter để lưu, rồi Ctrl + X để thoát. Khởi động và kiểm tra:

sudo systemctl daemon-reload
sudo systemctl enable --now webapp
sudo systemctl status webapp --no-pager
curl -I http://127.0.0.1:8000/docs

Kết quả mong đợi: dịch vụ active (running) và HTTP 200. Khi sửa file service, dùng daemon-reload rồi restart; không cần daemon-reexec.

7. Cấu hình Nginx chuyển tiếp vào Gunicorn

Mở file cấu hình (dùng tên miền thật của bạn):

sudo nano /etc/nginx/sites-available/proxy.timecloud.vn
server {
    listen 80;
    server_name proxy.timecloud.vn;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_http_version 1.1;
        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_set_header Connection "";
        proxy_buffering off;
        proxy_cache off;
        proxy_read_timeout 300s;
    }
}

proxy_buffering off cho phép nhận kết quả SSE từng phần; proxy_read_timeout giới hạn khoảng chờ giữa các lần nhận dữ liệu từ ứng dụng. Nginx và Gunicorn đều dùng 8000.

Kích hoạt site một lần; nếu liên kết đã tồn tại, kiểm tra cấu hình hiện có trước khi thay:

sudo ln -s /etc/nginx/sites-available/proxy.timecloud.vn /etc/nginx/sites-enabled/proxy.timecloud.vn
sudo nginx -t

Chỉ khi kiểm tra báo thành công, nạp lại Nginx:

sudo systemctl reload nginx
curl -I -H 'Host: proxy.timecloud.vn' http://127.0.0.1/docs
curl -I http://proxy.timecloud.vn/docs

Cả hai lần thử cần trả HTTP 200 trước khi cấp SSL. Site mặc định có thể giữ lại khi tên miền khớp server_name. Không xóa cấu hình site khác đang phục vụ trên VPS.

8. Cấp HTTPS và thử gia hạn

Khi DNS đã trỏ đúng và cổng 80 truy cập được từ Internet, chạy Certbot, nhập email, đọc và chấp nhận điều khoản nếu đồng ý:

sudo certbot --nginx -d proxy.timecloud.vn --redirect
sudo certbot renew --dry-run
sudo systemctl list-timers --all | grep certbot
curl -I https://proxy.timecloud.vn/docs

Mong đợi HTTPS trả HTTP 200 và kiểm tra gia hạn thành công. Với cách cài Certbot qua apt ở trên, kiểm tra timer đã được lên lịch; nếu chưa, bật bằng sudo systemctl enable --now certbot.timer. Giữ cổng 80 hoạt động cho xác thực và gia hạn HTTP-01. Nếu DNS đi qua proxy/CDN, cần bảo đảm yêu cầu xác thực đến đúng VPS.

Các kết quả cần đạt khi kiểm tra triển khai
Các kết quả cần đạt khi kiểm tra triển khai · Nhấn ảnh để xem lớn.
9. Cập nhật mã nguồn và kiểm tra lỗi

Sao lưu bản đang chạy, đưa mã mới lên, gán lại quyền nếu cần rồi khởi động lại ứng dụng. Nếu chỉ đổi Python hoặc template, không cần restart Nginx.

sudo systemctl restart webapp
sudo systemctl status webapp --no-pager
sudo journalctl -u webapp -n 100 --no-pager
sudo tail -n 100 /var/log/nginx/error.log
sudo ss -ltnp 'sport = :8000'
Hiện tượngCách kiểm tra
502 Bad GatewayKiểm tra webapp đang chạy và Nginx trỏ đúng 127.0.0.1:8000.
ModuleNotFoundErrorCài thư viện còn thiếu bằng Python trong /srv/webapp/.venv.
Permission denied / lỗi thư mụcKiểm tra WorkingDirectory và quyền của tài khoản webapp.
Address already in useXem tiến trình bằng ss; dừng terminal chạy thử hoặc đúng dịch vụ đang chiếm cổng. Không dùng kill -9 khi chưa xác định tiến trình.
SSL thất bạiKiểm tra DNS A/AAAA, cổng 80 và domain trong Nginx.
Kết quả kiểm tra không cập nhật dầnKiểm tra buffering ở Nginx và proxy/CDN nếu có.

Hoàn tất khi: webapp active, nginx -t thành công, HTTPS hoạt động và renew --dry-run thành công. Mở web và thử chức năng kiểm tra proxy để xác nhận ứng dụng hoạt động thực tế.

Tài liệu tham khảo