Asosiy kontentga o‘tish

Maqolalar

Darslik · 2026-08-16 · ~6 daqiqa o‘qiladi · o'rta

Nginx amaliyotda: reverse proxy, statika va kelajakdagi 502 lar

#nginx #devops #deploy #django

Mundarija

Avvalgi qismda gunicorn'ni systemd ostida ishga tushirdik, lekin u ataylab 127.0.0.1:8001ga bog'langan — tashqaridan unga kirib bo'lmaydi. Bu tasodif emas: Python-serverni to'g'ridan-to'g'ri internetga ochish yomon odat. Uning oldida nginx turadi va uchta ishni o'z zimmasiga oladi: statik fayllarni Python'ni bezovta qilmasdan beradi, sekin mijozlarning so'rovlarini o'zi qabul qilib gunicorn'ni band qilmaydi, keyinchalik esa TLS'ni ham o'zi ko'taradi.

Konfiguratsiya fayli qayerda yashaydi#

Ubuntu'da nginx saytlari ikki papka bilan boshqariladi: sites-availableda fayllar turadi, sites-enabledda esa ularga symlink qo'yiladi. Sayt o'chirish — symlink'ni olib tashlash, fayl esa joyida qoladi. Yangi sayt uchun fayl ochamiz:

sudo nano /etc/nginx/sites-available/mysite
# /etc/nginx/sites-available/mysite
server {
    listen 80;
    server_name mysite.uz www.mysite.uz;

    # yuklanadigan fayl hajmi chegarasi (default atigi 1M — formalar uchun kam)
    client_max_body_size 10M;

    # ---- statika: Python'ga umuman tegmaydi ----
    location /static/ {
        alias /opt/mysite/staticfiles/;
        # fayl nomlarida hash bor (Django ManifestStaticFilesStorage),
        # shuning uchun ularni "abadiy" keshlash xavfsiz: nom o'zgarsa,
        # bu boshqa fayl degani
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    location /media/ {
        alias /opt/mysite/media/;
        expires 7d;
    }

    # ---- qolgan hamma narsa gunicorn'ga ----
    location / {
        proxy_pass http://127.0.0.1:8001;
        # bu header'larsiz Django "hamma 127.0.0.1'dan kelyapti" deb o'ylaydi
        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;
    }
}

Yoqamiz va tekshiramiz:

sudo ln -s /etc/nginx/sites-available/mysite /etc/nginx/sites-enabled/
sudo nginx -t          # sintaksisni ALBATTA tekshiramiz — xato bo'lsa aytadi
sudo systemctl reload nginx

nginx -tni odat qiling: reload xatoli konfiguratsiya bilan chaqirilsa, nginx eski holatida ishlashda davom etadi, lekin siz buni sezmay "nega o'zgarish tushmayapti" deb yarim soat yo'qotasiz.

Header'lar nega bunchalik muhim#

proxy_set_header qatorlari shunchaki rasm-rusum emas. Ularsiz Django'ga yetib borgan so'rovda mijozning haqiqiy IP'si ham, so'ralgan domen ham, protokol ham yo'q — hammasi nginx'niki bilan almashib ketgan bo'ladi. Buning amaliy oqibatlari esa juda seziladi: log'da hamma mehmon 127.0.0.1 bo'lib chiqadi, rate limiting hammani bitta odam deb hisoblab bloklab qo'yadi, keyinchalik HTTPS yoqilganda esa Django redirect'lari noto'g'ri ishlaydi.

Django tomonida bunga mos ikki sozlama bor:

# config/settings.py
ALLOWED_HOSTS = ["mysite.uz", "www.mysite.uz"]

# X-Forwarded-Proto'ga ishonamiz — TLS qismida kerak bo'ladi
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")

SECURE_PROXY_SSL_HEADER — faqat proxy ortida

Bu sozlama "X-Forwarded-Proto: https ko'rsam, so'rov xavfsiz deb hisoblayman" degani. Ilova to'g'ridan-to'g'ri internetga ochiq bo'lsa, istalgan mijoz shu header'ni o'zi yozib yuborishi mumkin. Bizning sxemada xavfsiz, chunki tashqaridan kelgan header'larni nginx o'ziniki bilan almashtiradi — lekin sxemani o'zgartirsangiz, buni yodda tuting.

Statika: collectstatic bilan kelishuv#

Yuqoridagi location /static/ ishlashi uchun Django fayllarni bitta papkaga yig'ib bergan bo'lishi kerak:

# config/settings.py
STATIC_URL = "/static/"
STATIC_ROOT = "/opt/mysite/staticfiles"
STORAGES = {
    "staticfiles": {
        # fayl nomiga kontent hash'i qo'shiladi: tailwind.4527ba3c.css
        "BACKEND": "django.contrib.staticfiles.storage.ManifestStaticFilesStorage",
    },
}
cd /opt/mysite && .venv/bin/python manage.py collectstatic --noinput

Hash'li nomlar va nginx'dagi immutable juftligi chiroyli ishlaydi: brauzer CSS'ni bir yilga keshlab oladi, deploy'da esa fayl nomi o'zgargani uchun yangi versiya darhol tushadi. "Foydalanuvchida eski dizayn qolibdi, Ctrl+F5 bosing" degan suhbatlar shu ikki qator bilan tarixga aylanadi.

WebSocket'ni o'tkazish#

Loyihada Django Channels bo'lsa, alohida location kerak bo'ladi, chunki WebSocket oddiy proxy orqali o'tmaydi — ulanishni "upgrade" qilish kerak:

    location /ws/ {
        proxy_pass http://127.0.0.1:8001;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        # WebSocket ulanishlari uzoq yashaydi — default 60s uzib qo'ymasin
        proxy_read_timeout 3600s;
    }

Channels maqolasida va'da qilingan qism aynan shu edi — endi to'liq sxema qo'lingizda.

502 chiqdi: qayerga qaraymiz#

Har kim hayotida ko'p marta ko'radigan xato — 502 Bad Gateway. Ma'nosi oddiy: nginx so'rovni qabul qildi, lekin orqadagi gunicorn'dan javob ololmadi. Demak muammo deyarli har doim nginx'da emas, orqada. Tekshirish tartibi:

# 1. Xizmat tirikmi?
systemctl status mysite-web

# 2. Port to'g'rimi? (unit'dagi --bind bilan nginx'dagi proxy_pass bir xilmi)
ss -tlnp | grep 8001

# 3. Nginx aniq nima deyapti?
sudo tail -20 /var/log/nginx/error.log
# "connection refused" — xizmat yotibdi yoki port noto'g'ri
# "upstream timed out" — xizmat tirik, lekin javob bermayapti (qotib qolgan)

Shu uch buyruq 502'larning to'qson foizini yechadi. Qolgan holatlarga — loglarni tizimli o'qishga — alohida qism bag'ishlaymiz.

Yakuniy holat#

Endi zanjir to'liq: foydalanuvchi 80-portga keladi, statikani nginx beradi, dinamikani gunicorn'ga uzatadi, gunicorn'ni systemd qo'riqlaydi, hammasi esa 1-qismda qurilgan firewall ortida. Bitta katta kamchilik qoldi — trafik hali ochiq HTTP'da yurmoqda. Keyingi qismda buni yopamiz: Let's Encrypt bilan bepul TLS sertifikat olish, avtomatik yangilanishi va HTTPS'ga to'g'ri ko'chish.