Beranda Blog Store
Software Development

Deploy Aplikasi Django ke Produksi: Gunicorn, Nginx, systemd, dan Checklist Keamanan

23 Agu 2026 Hartono 5 menit baca 11 Dilihat

Halo, Rekan ArtonLabs. Ini rujukan yang kami tulis untuk diri sendiri, dan kami rasa berguna untuk Anda juga.

Kami men-deploy aplikasi Django hampir setiap bulan, dan urutan langkahnya nyaris selalu sama. Tulisan ini menuliskannya secara lengkap sebagai rujukan: dari server kosong sampai aplikasi hidup dengan HTTPS, lengkap dengan kesalahan yang pernah kami buat supaya Anda tidak mengulanginya. Contohnya memakai Ubuntu, Gunicorn, Nginx, dan systemd; kalau Anda memakai VinzaPanel, sebagian besar langkah ini dikerjakan plugin Python Deploy, tetapi memahami apa yang terjadi di baliknya tetap berguna.

Daftar Isi

Menyiapkan Server dan Pengguna

Jangan jalankan aplikasi sebagai root. Buat pengguna khusus, dan taruh kode di folder home-nya:

sudo adduser --disabled-password --gecos "" webapp
sudo apt install -y python3-venv python3-dev build-essential nginx
sudo -u webapp -H bash -c 'git clone https://github.com/org/aplikasi.git ~/app'

Pengguna terpisah berarti kalau aplikasi disusupi, penyerang hanya mendapat hak pengguna itu. Beri folder home izin 750 dan jangan pernah menjalankan chmod 777 untuk "mengatasi" galat izin; cari tahu proses mana yang butuh akses dan beri hanya itu. Kalau Nginx perlu membaca staticfiles, cukup masukkan pengguna Nginx ke grup pengguna aplikasi. Ini juga yang dilakukan VinzaPanel secara otomatis untuk setiap website.

Memasang Aplikasi dan Dependensi

sudo -u webapp -H bash -c '
cd ~/app
python3 -m venv .venv
.venv/bin/pip install -r requirements.lock
.venv/bin/pip install gunicorn'

Perhatikan requirements.lock, bukan requirements.txt dengan batas bawah versi. Kami pernah mendapat aplikasi yang salah hitung hanya karena pip menarik pandas versi mayor baru yang mengubah perilaku bawaan. Kunci versi persis untuk produksi; cara menyusunnya dibahas di artikel tentang dependensi Python.

Pengaturan Produksi

Tiga pengaturan yang wajib: DEBUG = False, ALLOWED_HOSTS berisi domain Anda, dan SECRET_KEY yang unik dan tidak ada di Git. Simpan semuanya di berkas .env milik pengguna aplikasi dengan izin 600, dan muat di settings.py dengan python-dotenv atau os.environ. Lalu:

.venv/bin/python manage.py check --deploy
.venv/bin/python manage.py migrate
.venv/bin/python manage.py collectstatic --noinput

check --deploy mencetak daftar peringatan keamanan; perlakukan setiap barisnya sebagai pekerjaan rumah. Untuk berkas statis, WhiteNoise adalah pilihan sederhana yang menyajikan berkas terkompresi dari Gunicorn; alternatifnya, biarkan Nginx menyajikan folder staticfiles langsung.

Gunicorn sebagai Layanan systemd

sudo tee /etc/systemd/system/app.service > /dev/null <<'EOF'
[Unit]
Description=Aplikasi Django
After=network.target

[Service]
User=webapp
Group=webapp
WorkingDirectory=/home/webapp/app
EnvironmentFile=/home/webapp/app/.env
ExecStart=/home/webapp/app/.venv/bin/gunicorn config.wsgi:application \
  --workers 3 --bind 127.0.0.1:8001 --timeout 120 \
  --access-logfile /home/webapp/logs/access.log --error-logfile /home/webapp/logs/error.log
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF
sudo mkdir -p /home/webapp/logs && sudo chown webapp:webapp /home/webapp/logs
sudo systemctl daemon-reload
sudo systemctl enable --now app
systemctl status app

Jumlah worker: aturan umum 2 kali jumlah inti ditambah satu, tetapi kalau aplikasi menyimpan keadaan di memori proses, pakai satu worker atau pindahkan keadaannya ke database. Gunicorn mengikat ke localhost saja; Nginx yang menghadap ke luar.

Nginx sebagai Reverse Proxy

server {
    listen 80;
    server_name contoh.com www.contoh.com;

    location /static/ { alias /home/webapp/app/staticfiles/; expires 30d; }
    location /media/  { alias /home/webapp/app/media/; }

    location / {
        proxy_pass http://127.0.0.1:8001;
        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 120s;
    }
}

Dua jebakan yang pernah menggigit kami: direktif alias harus diakhiri garis miring kalau lokasinya diakhiri garis miring, kalau tidak /media/x.jpg dicari sebagai /home/webapp/app/mediax.jpg; dan X-Forwarded-Proto wajib diteruskan kalau Anda menyalakan SECURE_PROXY_SSL_HEADER di Django, kalau tidak login akan berputar-putar karena cookie aman tidak pernah terkirim.

HTTPS dengan Let's Encrypt

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d contoh.com -d www.contoh.com

Certbot mengubah konfigurasi Nginx, menambahkan pengalihan ke HTTPS, dan memasang timer pembaruan otomatis. Pastikan www sudah mengarah ke server sebelum menyertakannya, atau permintaan gagal seluruhnya. Setelah HTTPS hidup, nyalakan SESSION_COOKIE_SECURE, CSRF_COOKIE_SECURE, dan SECURE_HSTS_SECONDS di Django.

Checklist Keamanan

  • DEBUG=False, SECRET_KEY unik, ALLOWED_HOSTS eksplisit, CSRF_TRUSTED_ORIGINS berisi domain HTTPS.
  • Berkas .env izin 600, tidak pernah masuk Git.
  • Tidak ada berkas .bak atau cadangan di folder static; collectstatic akan menyajikannya ke publik.
  • Validator sandi aktif, dan pembatas percobaan login di jalur masuk.
  • Log aplikasi dirotasi dengan copytruncate, bukan dibuat ulang dengan pemilik lain; kami pernah melihat Gunicorn gagal menulis log setelah logrotate mengganti pemiliknya.
  • Firewall hanya membuka 22, 80, 443; port Gunicorn tidak pernah terbuka.
  • Backup database dan media terjadwal, dan pernah dicoba dipulihkan.

Log dan Pemantauan

Log akses Gunicorn adalah alat diagnosis yang sering diremehkan. Dengan format yang memuat ukuran jawaban dan durasi, Anda bisa menjawab pertanyaan seperti "apakah simpanan pengguna benar-benar mengubah sesuatu" tanpa menebak: bandingkan byte halaman sebelum dan sesudah POST. Tambahkan --access-logformat dengan %(L)s (durasi) dan alamat dari X-Forwarded-For; ingat, di unit systemd tanda persen harus ditulis ganda (%%). Untuk peringatan, pemantau uptime sederhana yang memeriksa satu URL tiap menit sudah menangkap sebagian besar masalah sebelum pengguna melapor.

Worker Antrean dan Tugas Terjadwal

Kalau aplikasi punya pekerjaan latar (Django-Q2, Celery), itu layanan systemd terpisah dengan pengguna yang sama dan Restart=always; jangan menjalankannya dari terminal. Untuk cron, pakai crontab pengguna aplikasi, bukan root, dan panggil Python dari .venv dengan jalur lengkap karena cron tidak memuat profil shell Anda. Satu kebiasaan kecil: setiap tugas latar menulis stempel waktu keberhasilan terakhir ke suatu tempat, dan pemantau memeriksa stempel itu tidak terlalu tua.

Alur Pembaruan

sudo -u webapp -H bash -c 'cd ~/app && git pull && .venv/bin/pip install -r requirements.lock && .venv/bin/python manage.py migrate && .venv/bin/python manage.py collectstatic --noinput'
sudo systemctl restart app

Kalau ada yang tidak beres, git checkout ke commit sebelumnya dan ulangi. Untuk aplikasi yang penting, jalankan dulu di salinan uji. Dan ingat: mengubah berkas .py tidak berpengaruh sampai layanan di-restart; itu penyebab paling umum dari "perbaikannya kok belum jalan".

Semoga deploy berikutnya berjalan lancar, Rekan ArtonLabs. Kalau ada langkah yang menurut Anda kurang, beri tahu kami lewat halaman kontak.