Cara Menjalankan Aplikasi Python dengan Gunicorn dan Nginx di VPS
Deploy aplikasi Flask atau FastAPI di Ubuntu dengan virtualenv, layanan systemd Gunicorn, reverse proxy Nginx, TLS, berkas lingkungan, pencatatan, dan daftar periksa untuk kesalahan 502 serta ukuran worker.

Server Flask bawaan dan uvicorn --reload untuk pengembangan. Di VPS publik Anda ingin pengelola proses yang merestart worker, mengikat ke localhost, dan duduk di belakang reverse proxy yang menangani TLS serta klien lambat. Gunicorn adalah pilihan WSGI biasa untuk Flask dan Django. FastAPI dapat berjalan di bawah Gunicorn dengan kelas worker Uvicorn. Nginx (atau Caddy) mengakhiri HTTPS dan meneruskan ke 127.0.0.1:8000.
Panduan ini menelusuri tata letak yang bertahan setelah reboot: proyek di /srv/app, virtualenv, .env dengan rahasia, unit gunicorn.service, blok server Nginx, Let's Encrypt, dan berkas log yang benar-benar bisa Anda grep. Kita juga akan membahas jumlah worker, timeout, dan mengapa 502 Bad Gateway hampir tidak pernah 'Nginx rusak' — hampir selalu Gunicorn tidak berjalan, terikat ke soket yang salah, atau crash saat impor. Contoh memakai Flask, dengan catatan FastAPI di mana perintah berbeda.
Mengapa tumpukan ini
Gunicorn melakukan prefork proses worker. Setiap worker menangani satu permintaan pada satu waktu kecuali Anda memakai kelas worker berbeda. Nginx menyangga klien lambat agar worker tidak macet mengirim byte ke jaringan seluler. systemd merestart aplikasi jika ia mati. Bersama-sama ini membosankan, yang merupakan apa yang Anda inginkan pukul 3 pagi.
- Gunicorn: model proses WSGI/ASGI yang stabil
- systemd: mulai saat boot, restart saat crash, log journald
- Nginx: TLS, berkas statis, batas ukuran permintaan, gzip
- venv: Python sistem tetap bersih
- ikatan localhost: aplikasi tidak dapat dijangkau kecuali lewat Nginx
Persyaratan
Python 3.10+ di Ubuntu 22.04/24.04 baik-baik saja. Jangan jalankan pip sebagai root ke site-packages sistem. Anda butuh domain untuk TLS. Jika Anda lebih suka Caddy sebagai proxy, unit Gunicorn di artikel ini tetap sama — hanya konfigurasi front-end yang berubah (lihat artikel Caddy).
- VPS Ubuntu 22.04 atau 24.04
- Aplikasi Anda dengan requirements.txt atau setara
- Titik masuk WSGI (untuk Flask: app:app) atau ASGI (untuk FastAPI: app:app dengan worker uvicorn)
- Rekaman A domain untuk HTTPS
Langkah 1: Paket sistem, pengguna, dan direktori proyek
Buat pengguna sistem yang tidak bisa login interaktif, miliki kode, dan jalankan Gunicorn. Menginstal python3-venv dan alat build menghindari kegagalan pip pada paket yang masih mengompilasi ekstensi C.
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/Langkah 2: Virtualenv dan dependensi
Buat venv sebagai appuser agar kepemilikan berkas benar. Sematkan versi di produksi. Setelah instal, pastikan Anda dapat mengimpor aplikasi dalam gunicorn --check-config sekali atau impor Python. Kesalahan impor di sini adalah kesalahan yang sama yang menjadi 502 kemudian.
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')"Langkah 3: Berkas lingkungan
Jangan hardcode SECRET_KEY atau URL basis data di unit systemd dengan cara yang berakhir di git yang dapat dibaca dunia. Gunakan EnvironmentFile. chmod 640, pemilik root, grup appuser (atau pemilik appuser jika Anda lebih suka).
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/.envLangkah 4: Layanan systemd Gunicorn
Ikat ke 127.0.0.1:8000, bukan 0.0.0.0, kecuali Anda punya alasan melewati Nginx. Jumlah worker sering (2 x CPU) + 1 untuk worker sinkron; di VPS 2 vCPU itu 5, yang mungkin terlalu banyak jika setiap worker memuat model ML berat — lalu pakai 2–3. Untuk FastAPI, atur --worker-class uvicorn.workers.UvicornWorker dan instal uvicorn. Timeout harus melebihi permintaan jujur paling lambat Anda, bukan 30 detik jika Anda punya ekspor 2 menit.
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 8000Langkah 5: Reverse proxy Nginx dan TLS
Nginx mendengar di 80/443 dan memproksi ke Gunicorn. client_max_body_size penting untuk unggahan. proxy_read_timeout harus cocok atau melebihi timeout Gunicorn. Setelah blok server bekerja di HTTP, terbitkan sertifikat dengan Certbot (atau ganti front ke Caddy). Cuplikan di bawah hanya HTTP agar Anda bisa menguji; lalu jalankan Certbot yang dapat mengedit berkas.
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.comLangkah 6: Berkas statis, izin, dan kekhususan Flask/FastAPI
Nginx seharusnya menyajikan berkas statis jika bisa; lebih cepat daripada Gunicorn. Django collectstatic, Flask send_from_directory untuk aplikasi kecil, atau CDN nanti. appuser harus bisa membaca pohon. Jika Anda memakai FastAPI, instal uvicorn[standard] di venv dan ubah ExecStart untuk memakai UvicornWorker. Jika Anda memakai soket Unix alih-alih TCP, arahkan proxy_pass ke soket dan cocokkan izin agar www-data dapat menulis ke sana.
# 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/appWorker, memori, dan reload tanpa downtime
Worker Gunicorn sinkron sederhana dan cukup untuk API permintaan/respons yang ringan CPU. Jika Anda butuh banyak tunggu I/O lambat bersamaan, pertimbangkan gevent atau worker ASGI — ukur, jangan menebak. Setiap worker memuat aplikasi Anda; RAM ~= worker dikali RSS aplikasi. VPS 2 GB dengan 8 worker dari aplikasi 300 MB akan swap dan terasa 'lambat secara acak'. systemctl reload gunicorn (HUP) dapat merestart worker dengan kode baru jika Anda mendeploy berkas di tempat; restart penuh lebih jelas ketika dependensi berubah.
# 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/healthMemecahkan 502 dan crash senyap
502 berarti Nginx tidak bisa mendapatkan respons valid dari upstream. journalctl -u gunicorn -e adalah perintah pertama. Penyebab umum: nama module:app salah, kunci .env hilang, Postgres tidak berjalan, terikat ke 127.0.0.1 tetapi Nginx di host lain, SELinux (jarang di Ubuntu), atau aplikasi hanya mendengar IPv6. 504 adalah timeout. Loop 301 terjadi ketika aplikasi mengalihkan ke HTTP sementara X-Forwarded-Proto diabaikan.
- systemctl status gunicorn — apakah aktif?
- journalctl -u gunicorn -n 100 — ImportError, env hilang, basis data
- curl -v http://127.0.0.1:8000/ dari VPS — jika ini gagal, Nginx tidak bersalah
- nginx -t dan error.log — upstream prematurely closed connection
- ss -tulpn | grep 8000 — tidak ada yang mendengar
- Disk penuh — worker crash dengan cara misterius
Keamanan
Aplikasi tidak pernah mengikat secara publik. Rahasia tetap di .env. Jaga venv dan OS tetap di-patch. Jangan jalankan Gunicorn sebagai root. Jika Anda menangani login, atur cookie sesi Secure dan SameSite, dan konfigurasi kerangka untuk mempercayai X-Forwarded-Proto dari Nginx saja. Batasi laju rute login di Nginx atau di aplikasi.
- ikat 127.0.0.1 atau soket unix
- chmod 600 .env
- User= non-root di systemd
- TLS lewat Certbot atau Caddy
- Nonaktifkan mode debug dan muat ulang otomatis di produksi
Tips
- Tambahkan rute /health yang memeriksa konektivitas DB untuk Compose dan penyeimbang beban
- Kirim aset statis dengan header cache-control di Nginx
- Gunakan worker atau antrean terpisah (Redis + systemd) untuk email dan pekerjaan berat
- Sematkan versi gunicorn dan uvicorn di requirements.txt
- Ambil snapshot sebelum cutover produksi pertama
Aplikasi Python di VPS siap produksi ketika berjalan di bawah Gunicorn sebagai layanan systemd, hanya mengikat ke localhost, dan dijangkau lewat Nginx atau Caddy dengan TLS. Taruh rahasia di EnvironmentFile, ukur worker menurut RAM bukan rumus blog, dan debug 502 dari jurnal Gunicorn dulu. Setelah jalur ini terdokumentasi untuk repositori Anda, setiap deploy berikutnya adalah rsync atau git pull, pip install, dan systemctl restart gunicorn.