Bumalik sa blog
Agosto 19, 2026Mga Gabay

Paano Magpatakbo ng Python Apps gamit ang Gunicorn at Nginx sa VPS

I-deploy ang Flask o FastAPI application sa Ubuntu na may virtualenv, Gunicorn systemd service, Nginx reverse proxy, TLS, environment files, logging, at checklist para sa 502 errors at worker sizing.

Paano Magpatakbo ng Python Apps gamit ang Gunicorn at Nginx sa VPS

Para sa development ang built-in Flask server at uvicorn --reload. Sa public VPS gusto mo ng process manager na nagre-restart ng workers, nagba-bind sa localhost, at nakaupo sa likod ng reverse proxy na humahawak ng TLS at mabagal na clients. Karaniwang WSGI choice ang Gunicorn para sa Flask at Django. Pwede tumakbo ang FastAPI sa ilalim ng Gunicorn na may Uvicorn worker class. Tinatapos ng Nginx (o Caddy) ang HTTPS at ipinapasa sa 127.0.0.1:8000.

Nilalakad ng gabay na ito ang layout na nabubuhay sa reboots: project sa /srv/app, virtualenv, .env na may secrets, gunicorn.service unit, Nginx server block, Let's Encrypt, at log files na kayang i-grep talaga. Pag-uusapan din natin ang worker counts, timeouts, at kung bakit halos hindi kailanman 'sira ang Nginx' ang 502 Bad Gateway — halos palaging hindi tumatakbo ang Gunicorn, naka-bind sa maling socket, o nagcra-crash sa import. Flask ang example, na may notes para sa FastAPI kung saan naiiba ang command.

Bakit ang stack na ito

Nagpu-prefork ang Gunicorn ng worker processes. Isang request sa isang pagkakataon ang hinahawakan ng bawat worker maliban kung gumamit ka ng ibang worker class. Binubu-buffer ng Nginx ang mabagal na clients para hindi ma-stuck ang workers sa pagpapadala ng bytes sa mobile network. Nire-restart ng systemd ang app kung namatay ito. Magkasama, boring ito, na siyang gusto mo sa alas-tres ng umaga.

  • Gunicorn: matatag na WSGI/ASGI process model
  • systemd: magsimula sa boot, mag-restart sa crash, journald logs
  • Nginx: TLS, static files, request size limits, gzip
  • venv: malinis ang system Python
  • localhost bind: hindi maaabot ang app maliban sa Nginx

Mga kailangan

Ayos ang Python 3.10+ sa Ubuntu 22.04/24.04. Huwag mag-pip bilang root papunta sa system site-packages. Kailangan mo ng domain para sa TLS. Kung mas gusto mo ang Caddy bilang proxy, pareho ang Gunicorn unit sa artikulong ito — ang front-end config lang ang nagbabago (tingnan ang Caddy article).

  • Ubuntu 22.04 o 24.04 VPS
  • Ang application mo na may requirements.txt o katumbas
  • WSGI entrypoint (para sa Flask: app:app) o ASGI (para sa FastAPI: app:app na may uvicorn workers)
  • Domain A record para sa HTTPS

Hakbang 1: System packages, user, at project directory

Gumawa ng system user na hindi makakapag-log in nang interactive, magmay-ari ng code, at magpatakbo ng Gunicorn. Ang pag-install ng python3-venv at build tools ay umiiwas sa pip failures sa packages na nagko-compile pa ng C extensions.

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/

Hakbang 2: Virtualenv at dependencies

Gawin ang venv bilang appuser para tama ang file ownership. I-pin ang versions sa production. Pagkatapos mag-install, kumpirmahin na kaya mong i-import ang app sa one-off gunicorn --check-config o Python import. Ang import errors dito ang parehong errors na magiging 502 mamaya.

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')"

Hakbang 3: Environment file

Huwag i-hardcode ang SECRET_KEY o database URLs sa systemd unit sa paraang magtatapos sa world-readable git. Gumamit ng EnvironmentFile. chmod 640, owner root, group appuser (o owner appuser kung mas gusto mo).

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

Hakbang 4: Gunicorn systemd service

Mag-bind sa 127.0.0.1:8000, hindi 0.0.0.0, maliban kung may dahilan kang laktawan ang Nginx. Madalas (2 x CPU) + 1 ang worker count para sa sync workers; sa 2 vCPU VPS ay 5 iyon, na maaaring sobra kung naglo-load ang bawat worker ng mabigat na ML model — gumamit ng 2–3. Para sa FastAPI, itakda ang --worker-class uvicorn.workers.UvicornWorker at i-install ang uvicorn. Dapat lampasan ng timeouts ang pinakamabagal mong tapat na request, hindi 30 seconds kung may 2-minutong exports ka.

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

Hakbang 5: Nginx reverse proxy at TLS

Nakikinig ang Nginx sa 80/443 at nagpu-proxy papunta sa Gunicorn. Mahalaga ang client_max_body_size para sa uploads. Dapat tumugma o lampasan ng proxy_read_timeout ang timeout ng Gunicorn. Pagkatapos gumana ang server block sa HTTP, mag-issue ng certificate sa Certbot (o palitan ang front ng Caddy). HTTP-only ang snippet sa ibaba para makapag-test ka; tapos patakbuhin ang Certbot na kayang i-edit ang file.

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

Hakbang 6: Static files, permissions, at Flask/FastAPI specifics

Dapat mag-serve ang Nginx ng static files kung kaya; mas mabilis iyon kaysa Gunicorn. Django collectstatic, Flask send_from_directory para sa maliliit na apps, o CDN mamaya. Kailangang mabasa ng appuser ang tree. Kung FastAPI ang gamit mo, i-install ang uvicorn[standard] sa venv at palitan ang ExecStart para gumamit ng UvicornWorker. Kung Unix sockets ang gamit imbes na TCP, ituro ang proxy_pass sa socket at itugma ang permissions para makasulat ang www-data dito.

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

Workers, memory, at zero-downtime reloads

Simple at sapat ang sync Gunicorn workers para sa CPU-light request/response APIs. Kung kailangan mo ng maraming concurrent slow I/O waits, isaalang-alang ang gevent o ASGI worker — sukatin, huwag hulaan. Niloload ng bawat worker ang app mo; RAM ~= workers beses app RSS. Ang 2 GB VPS na may 8 workers ng 300 MB app ay magso-swap at magiging 'randomly slow'. Kaya ng systemctl reload gunicorn (HUP) i-restart ang workers sa bagong code kung nag-deploy ka ng files sa lugar; mas malinaw ang full restart kapag nagbago ang dependencies.

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

Pag-troubleshoot ng 502 at silent crashes

Nangangahulugan ang 502 na hindi nakakuha ang Nginx ng valid response mula sa upstream. Unang command ang journalctl -u gunicorn -e. Karaniwang sanhi: maling module:app name, nawawalang .env key, hindi tumatakbo ang Postgres, naka-bind sa 127.0.0.1 pero Nasa ibang host ang Nginx, SELinux (bihira sa Ubuntu), o IPv6 lang ang pinakikinggan ng app. Timeout ang 504. Nangyayari ang 301 loops kapag nireridirect ng app papunta sa HTTP habang hindi pinapansin ang X-Forwarded-Proto.

  • systemctl status gunicorn — active ba?
  • journalctl -u gunicorn -n 100 — ImportError, nawawalang env, database
  • curl -v http://127.0.0.1:8000/ mula sa VPS — kung nabigo ito, inosente ang Nginx
  • nginx -t at error.log — upstream prematurely closed connection
  • ss -tulpn | grep 8000 — walang nakikinig
  • Puno ang disk — nagcra-crash ang workers sa misteryosong paraan

Security

Hindi kailanman nagba-bind nang publiko ang app. Nasa .env ang secrets. Panatilihing naka-patch ang venv at OS. Huwag magpatakbo ng Gunicorn bilang root. Kung humahawak ka ng logins, itakda ang session cookies Secure at SameSite, at i-configure ang framework na magtiwala lang sa X-Forwarded-Proto mula sa Nginx. I-rate-limit ang login routes sa Nginx o sa app.

  • mag-bind sa 127.0.0.1 o unix socket
  • chmod 600 .env
  • Non-root User= sa systemd
  • TLS sa Certbot o Caddy
  • I-disable ang debug mode at auto-reload sa production

Mga tips

  • Magdagdag ng /health route na nagsu-check ng DB connectivity para sa Compose at load balancers
  • Ipadala ang static assets na may cache-control header sa Nginx
  • Gumamit ng hiwalay na worker o queue (Redis + systemd) para sa emails at mabibigat na jobs
  • I-pin ang gunicorn at uvicorn versions sa requirements.txt
  • Kumuha ng snapshot bago ang unang production cutover

Production-ready ang Python app sa VPS kapag tumatakbo sa ilalim ng Gunicorn bilang systemd service, nagba-bind lang sa localhost, at inaabot sa Nginx o Caddy na may TLS. Ilagay ang secrets sa EnvironmentFile, i-size ang workers ayon sa RAM hindi blog post formula, at i-debug ang 502 mula sa Gunicorn journal muna. Kapag naka-document na ang path na ito para sa repo mo, bawat susunod na deploy ay rsync o git pull, pip install, at systemctl restart gunicorn.