ArrayList و HashMap در جاوا؛ آموزش Collections با مثال
آرایه در جاوا اندازه ثابت دارد، اما دادههای برنامه واقعی مدام بزرگ و کوچک میشوند. Collections Framework با ArrayList، HashMap، …
پروژه جنگو شما روی لپتاپ با runserver کار میکند، اما برای اینکه کاربران با دامنه و HTTPS به آن برسند، باید آن را درست روی سرور مستقر کنید. در این آموزش قدمبهقدم یاد میگیرید پروژه را برای محیط تولید آماده کنید، Gunicorn را با systemd اجرا کنید، Nginx را جلوی آن بگذارید، گواهی رایگان Let's Encrypt بگیرید و خطاهای رایج را رفع کنید.
آخرین بهروزرسانی: ۲۰ آبان ۱۴۰۵ — منابع بررسیشده در این روز: چکلیست دیپلوی و راهنمای Gunicorn در مستندات جنگو ۶٫۱، یادداشت انتشار جنگو ۶٫۱، صفحه دانلود جنگو، مستندات و اخبار Gunicorn، مستندات ماژول proxy در Nginx و راهنمای Certbot.
⚡ پاسخ کوتاه
برای دیپلوی جنگو روی لینوکس، تنظیمات تولید را جدا کنید (DEBUG خاموش، SECRET_KEY در متغیر محیطی، ALLOWED_HOSTS درست)، با manage.py check --deploy آن را بسنجید، Gunicorn را با یک سوکت و سرویس systemd اجرا کنید، Nginx را برای سرو فایلهای استاتیک و پروکسی به Gunicorn تنظیم کنید و با Certbot گواهی HTTPS بگیرید. ما تنظیمات جنگو و Gunicorn را با Django 6.1.3 و Gunicorn 27.0.0 آزمایش کردهایم.
📑 فهرست مطالب
دیپلوی یا استقرار یعنی پروژهای که روی لپتاپ شما کار میکند، روی یک سرور واقعی اجرا شود تا کاربران با دامنه و HTTPS به آن دسترسی داشته باشند. در زمان توسعه با دستور runserver کار میکنیم. این دستور راحت است، فایلهای استاتیک را خودش سرو میکند و با هر تغییر کد دوباره بالا میآید. اما مستندات جنگو صریح میگوید runserver برای محیط واقعی طراحی نشده و باید آن را با یک سرور WSGI یا ASGI آماده تولید عوض کرد. در این آموزش از چیدمانی استفاده میکنیم که سالهاست برای پروژههای جنگو رایج است: Gunicorn برنامه پایتونی را اجرا میکند، Nginx جلوی آن میایستد و درخواستها را تحویل میگیرد، systemd مراقب است Gunicorn همیشه روشن باشد و Certbot گواهی رایگان Let's Encrypt را برای HTTPS میگیرد. اگر هنوز پروژهای نساختهاید، اول ساخت اپلیکیشن در جنگو را ببینید و اگر ساختار پوشهها برایتان تازه است، ساختار MVT در جنگو را مرور کنید. همه تنظیمات جنگو و Gunicorn این مقاله را با Django 6.1.3 و Gunicorn 27.0.0 روی پایتون امتحان کردهایم: دستور check --deploy را اجرا کردیم، collectstatic را زدیم و Gunicorn را روی سوکت یونیکس بالا آوردیم و با درخواست واقعی آزمودیم. پیکربندی Nginx و systemd را بر اساس نمونههای رسمی Gunicorn نوشتهایم، اما روی سرور واقعی شما باید با nginx -t و systemctl status بررسی شوند.

| قطعه | کار اصلی | اگر نباشد چه میشود؟ |
|---|---|---|
| Nginx | گرفتن درخواست روی پورت 80 و 443، سرو فایلهای استاتیک و رسانه، پروکسی به Gunicorn | Gunicorn مستقیم در برابر کلاینتهای کند قرار میگیرد و فایلهای استاتیک سرو نمیشوند |
| Gunicorn | اجرای برنامه جنگو از طریق wsgi.py با چند ورکر | جنگو فقط با runserver اجرا میشود که برای تولید طراحی نشده |
| systemd | بالا آوردن Gunicorn بعد از روشن شدن سرور و اجرای دوباره در صورت خرابی | با بستن ترمینال یا ریاستارت سرور، سایت از دسترس خارج میشود |
| Certbot | گرفتن و تمدید خودکار گواهی رایگان Let's Encrypt | سایت بدون HTTPS میماند و کوکیهای امن کار نمیکنند |
| PostgreSQL | پایگاه داده تولید | SQLite برای ترافیک همزمان زیاد مناسب نیست |
بیشتر مشکلات دیپلوی قبل از رسیدن به سرور ساخته میشوند. سه کار را روی همان سیستم خودتان انجام دهید: تنظیمات تولید را جدا کنید، رمزها را از کد بیرون بکشید و فهرست وابستگیها را دقیق بنویسید.
سادهترین روش برای پروژههای کوچک و متوسط این است که کنار settings.py یک فایل settings_prod.py بسازید که تنظیمات اصلی را وارد کند و فقط چیزهای مخصوص سرور را تغییر دهد. فرض کنید نام پوشه تنظیمات شما config است:
# config/settings_prod.py
import os
from .settings import * # noqa
DEBUG = False
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS", "").split(",")
CSRF_TRUSTED_ORIGINS = [f"https://{h}" for h in ALLOWED_HOSTS if h]
STATIC_URL = "static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
MEDIA_URL = "media/"
MEDIA_ROOT = BASE_DIR / "media"
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 3600
DEBUG در محیط واقعی هرگز نباید روشن باشد. مستندات جنگو توضیح میدهد که صفحه خطای حالت دیباگ تکههایی از کد، متغیرهای محلی، تنظیمات و کتابخانهها را نشان میدهد؛ یعنی دقیقاً همان چیزهایی که مهاجم دوست دارد ببیند. وقتی DEBUG خاموش شود، جنگو بدون مقدار درست برای ALLOWED_HOSTS اصلاً کار نمیکند و به درخواستهایی که هدرشان نام دامنه شما را ندارد، خطای 400 برمیگرداند. ما همین را آزمودیم: درخواست با هدر Host برابر example.com جواب 200 گرفت و درخواست با یک دامنه دیگر خطای 400. خط SECURE_PROXY_SSL_HEADER برای چیدمان ما ضروری است. HTTPS در Nginx تمام میشود و Gunicorn درخواست را روی سوکت داخلی و بدون رمزنگاری میگیرد. اگر Nginx با هدر X-Forwarded-Proto به جنگو نگوید که درخواست اصلی HTTPS بوده، جنگو با SECURE_SSL_REDIRECT مدام کاربر را به HTTPS هدایت میکند و حلقه بیپایان ریدایرکت میسازید. این تنظیم فقط وقتی امن است که Gunicorn از بیرون مستقیم در دسترس نباشد و فقط Nginx به آن وصل شود.
کلید مخفی جنگو برای امضای کوکیها، توکنهای بازیابی رمز و چیزهای مشابه به کار میرود. مستندات جنگو پیشنهاد میکند آن را از متغیر محیطی یا یک فایل جدا بخوانید و هرگز در مخزن گیت ثبت نکنید. همین قاعده برای رمز پایگاه داده و ایمیل هم برقرار است. این همان اصلی است که در فاکتور سوم دوازدهفاکتور؛ پیکربندی توضیح دادهایم: کد در همه محیطها یکی است و فقط پیکربندی عوض میشود. برای ساختن یک کلید تصادفی تازه، این دستور را در محیط مجازی پروژه اجرا کنید و خروجی را فقط در فایل محیطی سرور بگذارید:
python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"
اگر روزی کلید را عوض کردید، جنگو تنظیم SECRET_KEY_FALLBACKS را دارد تا کلید قبلی برای مدتی همچنان پذیرفته شود و همه کاربران یکباره از حسابشان بیرون نیفتند. فقط یادتان باشد کلید قدیمی را بعد از مدتی از این فهرست حذف کنید.
اینجا یک نکته تازه وجود دارد که در آموزشهای قدیمیتر نمیبینید. جنگو ۶٫۱ تنظیم MAILERS را اضافه کرده که مثل DATABASES و CACHES، چند سرویس ایمیل را با نام جدا تعریف میکند. پروژهای که با startproject نسخه ۶٫۱ میسازید، بهطور پیشفرض یک mailer با بکاند console دارد که ایمیل را فقط در ترمینال چاپ میکند. وقتی ما check --deploy را روی تنظیمات تولید اجرا کردیم، جنگو همین را خطای mail.E001 گزارش کرد و گفت بکاند فقط برای توسعه است. راهحل این است که در تنظیمات تولید یک بکاند واقعی مثل SMTP بگذارید:
MAILERS = {
"default": {
"BACKEND": "django.core.mail.backends.smtp.EmailBackend",
"OPTIONS": {
"host": os.environ.get("EMAIL_HOST", "localhost"),
"port": int(os.environ.get("EMAIL_PORT", "587")),
"username": os.environ.get("EMAIL_USER", ""),
"password": os.environ.get("EMAIL_PASSWORD", ""),
"use_tls": True,
},
},
}
اگر پروژه شما قدیمیتر است و هنوز EMAIL_BACKEND و تنظیمات EMAIL_ را دارد، طبق یادداشت انتشار نسخه ۶٫۱ آنها فعلاً کار میکنند و قرار است در جنگو ۷ جایشان را به MAILERS بدهند. اگر سایت شما اصلاً ایمیل نمیفرستد، باز هم بهتر است این هشدار را جدی بگیرید؛ چون فرم بازیابی رمز و گزارش خطا به ایمیل وابستهاند.
نسخه بستهها را در فایل requirements.txt ثابت کنید تا سرور دقیقاً همان چیزی را نصب کند که شما آزمودهاید:
Django==6.1.3
gunicorn==27.0.0
psycopg[binary]==3.*
سطر سوم فقط وقتی لازم است که از PostgreSQL استفاده کنید. نسخه دقیق آن را خودتان بعد از نصب ثابت کنید. بعد دستور زیر را با تنظیمات تولید اجرا کنید. این دستور بخش بزرگی از چکلیست امنیتی جنگو را خودکار بررسی میکند:
export DJANGO_SETTINGS_MODULE=config.settings_prod
export DJANGO_SECRET_KEY="paste-a-long-random-key-here"
export DJANGO_ALLOWED_HOSTS="example.com,www.example.com"
python manage.py check --deploy
در آزمایش ما، بعد از درست کردن MAILERS فقط دو هشدار ماند: W005 درباره SECURE_HSTS_INCLUDE_SUBDOMAINS و W021 درباره SECURE_HSTS_PRELOAD. این دو را عمداً خاموش گذاشتیم و در بخش امنیت توضیح میدهیم چرا نباید روز اول روشنشان کنید.
برای یک سایت کوچک تا متوسط، یک سرور مجازی (VPS) با لینوکس Ubuntu یا Debian کافی است. پیش از خرید، اشتباهات رایج در انتخاب هاست را بخوانید؛ موقعیت سرور، منابع و پشتیبانی روی سرعت و پایداری اثر مستقیم دارند. دستورهای این بخش برای توزیعهای مبتنی بر Debian نوشته شدهاند.
پروژه را با کاربر root اجرا نکنید. یک کاربر مخصوص برنامه بسازید تا اگر روزی در کد مشکلی پیدا شد، دسترسی مهاجم به همان کاربر محدود بماند. بعد فقط پورتهای SSH، HTTP و HTTPS را باز بگذارید:
sudo adduser --system --group --home /srv/myshop myshop
sudo apt update && sudo apt install -y python3-venv python3-pip nginx git
sudo ufw allow OpenSSH
sudo ufw allow "Nginx Full"
sudo ufw enable
قبل از روشن کردن دیوار آتش مطمئن شوید قانون OpenSSH را اضافه کردهاید؛ وگرنه ممکن است اتصال خودتان به سرور قطع شود. پروفایل Nginx Full بعد از نصب بسته nginx در ufw تعریف میشود و پورتهای 80 و 443 را باز میکند.
کد را از مخزن گیت در پوشه /srv/myshop بگیرید، محیط مجازی بسازید و وابستگیها را نصب کنید. جنگو ۶٫۱ طبق یادداشت انتشارش پایتون ۳٫۱۲ تا ۳٫۱۵ را پشتیبانی میکند؛ پس قبل از هر چیز نسخه پایتون سرور را با python3 --version ببینید.
cd /srv/myshop
sudo -u myshop git clone https://example.com/your/repo.git app
cd app
sudo -u myshop python3 -m venv /srv/myshop/venv
sudo -u myshop /srv/myshop/venv/bin/pip install -r requirements.txt
SQLite برای تمرین و سایتهای خیلی کمترافیک قابل استفاده است، اما برای فروشگاه یا سایتی که چند کاربر همزمان در آن مینویسند، PostgreSQL انتخاب امنتری است. هر کدام را انتخاب کردید، مستندات جنگو یادآوری میکند که رمز پایگاه داده را مثل SECRET_KEY محافظت کنید، پایگاه داده فقط از سرور برنامه اتصال بپذیرد و همین امروز برای آن پشتیبانگیری تنظیم کنید. برای مرور مدلها و کوئریها، آموزش Django ORM را ببینید.
متغیرهای محیطی را در یک فایل جدا بگذارید که فقط root و کاربر برنامه بتوانند بخوانند. systemd بعداً همین فایل را برای Gunicorn بارگذاری میکند:
# /etc/myshop/myshop.env (chmod 640, owner root:myshop)
DJANGO_SETTINGS_MODULE=config.settings_prod
DJANGO_SECRET_KEY=change-me-to-a-long-random-value
DJANGO_ALLOWED_HOSTS=example.com,www.example.com
EMAIL_HOST=smtp.example.com
EMAIL_USER=noreply@example.com
EMAIL_PASSWORD=change-me
حالا مهاجرت پایگاه داده، جمعآوری فایلهای استاتیک و ساخت کاربر مدیر را با همین متغیرها اجرا کنید. در آزمایش ما collectstatic برای یک پروژه خام ۱۳۰ فایل را به staticfiles کپی کرد؛ همین فایلها هستند که بعداً Nginx مستقیم سرو میکند.
cd /srv/myshop/app
sudo -u myshop bash -c 'set -a; source /etc/myshop/myshop.env; set +a;
/srv/myshop/venv/bin/python manage.py migrate &&
/srv/myshop/venv/bin/python manage.py collectstatic --noinput'
Gunicorn یک سرور WSGI است. یعنی درخواست HTTP را میگیرد، آن را به شکلی که جنگو میفهمد تحویل برنامه میدهد و پاسخ را برمیگرداند. هر پروژه جنگو یک فایل wsgi.py دارد که شیء application در آن تعریف شده است و Gunicorn همین را اجرا میکند. اول یک آزمایش سریع بزنید تا مطمئن شوید برنامه بالا میآید:
cd /srv/myshop/app
set -a; source /etc/myshop/myshop.env; set +a
/srv/myshop/venv/bin/gunicorn --bind 127.0.0.1:8000 config.wsgi:application
اگر خروجی گفت Listening at و Booting worker، کار میکند. با Ctrl+C آن را ببندید. مستندات خود Gunicorn تأکید میکند که آن را پشت یک پروکسی مثل Nginx اجرا کنید. دلیلش این است که ورکرهای همگام پیشفرض Gunicorn در برابر کلاینتهای کند آسیبپذیرند و Nginx با بافر کردن درخواستها این مشکل را حل میکند.
بهجای نوشتن همه گزینهها در خط فرمان، یک فایل gunicorn.conf.py در ریشه پروژه بسازید. ما همین فایل را با سوکت یونیکس اجرا کردیم و سه ورکر بالا آمد:
# /srv/myshop/app/gunicorn.conf.py
bind = "unix:/run/gunicorn-myshop.sock"
workers = 3
timeout = 30
accesslog = "-"
errorlog = "-"
چند ورکر بگذاریم؟ صفحه طراحی Gunicorn فرمول (2 × تعداد هستههای پردازنده) + 1 را نقطه شروع پیشنهاد میکند و هشدار میدهد که ورکر بیشتر لزوماً بهتر نیست؛ به گفته همان صفحه، معمولاً ۴ تا ۱۲ ورکر برای ترافیک سنگین هم کافی است. برای سرور دوهستهای، پنج ورکر شروع خوبی است. بعد مصرف حافظه را زیر بار واقعی ببینید، چون هر ورکر یک نسخه کامل از برنامه شما را در حافظه نگه میدارد. timeout یعنی اگر یک ورکر بیش از این تعداد ثانیه جواب ندهد، Gunicorn آن را دوباره راهاندازی میکند. اگر کارهای طولانی مثل ساخت گزارش بزرگ یا ارسال ایمیل گروهی دارید، بهجای بالا بردن timeout آن کارها را به صف پسزمینه بسپارید.
Gunicorn 27.0.0 روز ۱۰ نوامبر ۲۰۲۶ منتشر شد. طبق تغییرات رسمی، تغییر ناسازگار اصلی این است که درخواستی که هدر Transfer-Encoding آن به chunked ختم نشود، با خطای 400 رد میشود. همچنین چند مورد سختگیری بیشتر بر اساس استانداردهای HTTP اضافه شده، مثل رد کردن کاراکترهای کنترلی در مسیر درخواست. برای یک سایت جنگو معمولی پشت Nginx احتمالاً تغییری حس نمیکنید، اما اگر کلاینت یا سرویس خاصی دارید که هدرهای غیرعادی میفرستد، بعد از ارتقا آن را آزمایش کنید.
اگر Gunicorn را دستی اجرا کنید، با بستن ترمینال یا ریاستارت سرور خاموش میشود. systemd مدیر سرویسهای لینوکس است و کارش همین است که برنامه را بعد از روشن شدن سرور بالا بیاورد و اگر از کار افتاد، دوباره اجرا کند. مستندات Gunicorn الگوی دو فایل را پیشنهاد میکند: یک فایل سوکت که systemd با آن سوکت را میسازد و یک فایل سرویس که خود Gunicorn را اجرا میکند.
# /etc/systemd/system/gunicorn-myshop.socket
[Unit]
Description=gunicorn socket for myshop
[Socket]
ListenStream=/run/gunicorn-myshop.sock
SocketUser=www-data
SocketGroup=www-data
SocketMode=0660
[Install]
WantedBy=sockets.target
# /etc/systemd/system/gunicorn-myshop.service
[Unit]
Description=gunicorn daemon for myshop
Requires=gunicorn-myshop.socket
After=network.target
[Service]
Type=notify
NotifyAccess=main
User=myshop
Group=myshop
WorkingDirectory=/srv/myshop/app
EnvironmentFile=/etc/myshop/myshop.env
ExecStart=/srv/myshop/venv/bin/gunicorn -c gunicorn.conf.py config.wsgi:application
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true
[Install]
WantedBy=multi-user.target
وقتی systemd سوکت را میسازد و به Gunicorn میدهد، Gunicorn روی همان سوکت گوش میدهد. مالک سوکت www-data است، یعنی کاربری که Nginx روی Debian و Ubuntu با آن اجرا میشود؛ پس Nginx اجازه نوشتن روی سوکت را دارد. Type=notify هم باعث میشود Gunicorn آماده بودنش را به systemd خبر بدهد. حالا سرویس را فعال کنید و از طرف کاربر Nginx آزمایش کنید:
sudo systemctl daemon-reload
sudo systemctl enable --now gunicorn-myshop.socket
sudo -u www-data curl --unix-socket /run/gunicorn-myshop.sock -H "Host: example.com" http://localhost/
sudo systemctl status gunicorn-myshop.service
journalctl -u gunicorn-myshop.service -n 50
در این آزمایش اولیه، اگر HTTPS را در تنظیمات اجباری کرده باشید، پاسخ یک ریدایرکت 301 به نشانی https است. در آزمایش ما هم دقیقاً همین شد و وقتی هدر X-Forwarded-Proto با مقدار https را اضافه کردیم، صفحه ورود مدیریت با کد 200 آمد. پس 301 اینجا نشانه خرابی نیست. لاگهای Gunicorn با journalctl خوانده میشوند؛ این همان رویکردی است که در فاکتور لاگها توضیح دادهایم: برنامه لاگ را در خروجی استاندارد مینویسد و محیط اجرا آن را جمع میکند.

Nginx درخواست کاربران را روی پورتهای 80 و 443 میگیرد. اگر درخواست برای فایل استاتیک یا رسانه باشد، خودش فایل را از دیسک میفرستد؛ که خیلی سریعتر از جنگو است. بقیه درخواستها را از طریق سوکت به Gunicorn میدهد. این فایل را بسازید:
# /etc/nginx/sites-available/myshop
server {
listen 80 default_server;
return 444;
}
server {
listen 80;
server_name example.com www.example.com;
client_max_body_size 10M;
location /static/ {
alias /srv/myshop/app/staticfiles/;
}
location /media/ {
alias /srv/myshop/app/media/;
}
location / {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
proxy_pass http://unix:/run/gunicorn-myshop.sock;
}
}
بلوک اول همان پیشنهاد چکلیست جنگو است: هر درخواستی که دامنهاش با سایت شما نخواند، با کد 444 بدون پاسخ بسته میشود و اصلاً به جنگو نمیرسد. این کار لاگها را از خطاهای بیمورد Host پاک نگه میدارد. سه هدر proxy_set_header هم دامنه اصلی، نشانی واقعی کاربر و پروتکل اصلی را به جنگو میرسانند. client_max_body_size سقف اندازه فایل ارسالی کاربر است؛ اگر کاربران عکس بزرگ آپلود میکنند، آن را متناسب تنظیم کنید. در آدرسهای alias به اسلش پایانی دقت کنید؛ وقتی location با اسلش تمام میشود، alias هم باید با اسلش تمام شود. سایت را فعال کنید، پیکربندی را آزمایش کنید و Nginx را دوباره بارگذاری کنید:
sudo ln -s /etc/nginx/sites-available/myshop /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
مستندات جنگو درباره MEDIA_ROOT هشدار جدی دارد: فایلهایی که کاربران آپلود میکنند قابل اعتماد نیستند و وبسرور هرگز نباید آنها را تفسیر یا اجرا کند. در پیکربندی بالا Nginx فایلهای media را فقط بهعنوان فایل معمولی میفرستد و هیچ پردازشگری برای آن مسیر تعریف نشده است. همین را حفظ کنید و برای این پوشه هم مثل پایگاه داده پشتیبان بگیرید.
سایتی که کاربر در آن وارد حساب میشود باید کامل روی HTTPS باشد. چکلیست جنگو توضیح میدهد که محافظت فقط از بخش ورود کافی نیست، چون کوکی جلسه در HTTP و HTTPS یکی است. Let's Encrypt گواهی رایگان میدهد و Certbot ابزار رسمی EFF برای گرفتن و تمدید آن است. روش نصب Certbot را از صفحه دستورالعمل رسمی آن برای Nginx و سیستمعامل خودتان بردارید. بعد از نصب، معمولاً یک دستور کافی است:
sudo certbot --nginx -d example.com -d www.example.com
sudo certbot renew --dry-run
Certbot بلوکهای listen 443 و مسیر گواهیها را به همان فایل Nginx اضافه میکند و میتواند HTTP را به HTTPS هدایت کند. دستور دوم تمدید را بدون گرفتن گواهی واقعی شبیهسازی میکند تا مطمئن شوید تمدید خودکار کار میکند. پیش از این مرحله، رکورد DNS دامنه باید به IP سرور اشاره کند؛ وگرنه اعتبارسنجی Let's Encrypt شکست میخورد. حالا که HTTPS فعال است، تنظیمات CSRF_COOKIE_SECURE و SESSION_COOKIE_SECURE که در settings_prod گذاشتیم معنا پیدا میکنند: کوکیها فقط روی اتصال امن فرستاده میشوند.
| مورد | وضعیت درست | چطور بررسی کنیم؟ |
|---|---|---|
| DEBUG | False در تولید | manage.py check --deploy |
| SECRET_KEY | طولانی، تصادفی و بیرون از کد | فایل محیطی با دسترسی محدود |
| ALLOWED_HOSTS | فقط دامنههای واقعی | درخواست با Host ناشناخته باید رد شود |
| HTTPS | گواهی معتبر و هدایت HTTP به HTTPS | certbot renew --dry-run |
| کوکیها | SESSION_COOKIE_SECURE و CSRF_COOKIE_SECURE روشن | check --deploy |
| HSTS | شروع با مقدار کوچک و افزایش تدریجی | هشدارهای W004، W005 و W021 در check --deploy |
| دیوار آتش | فقط پورتهای SSH، 80 و 443 باز | sudo ufw status |
| پشتیبان | نسخه پشتیبان منظم از پایگاه داده و media | بازیابی آزمایشی روی سرور دیگر |
| بهروزرسانی | جنگو و سیستمعامل با وصلههای امنیتی | pip list --outdated و اعلانهای امنیتی جنگو |
HSTS به مرورگر میگوید این دامنه را تا مدت مشخصی فقط با HTTPS باز کند. ما در تنظیمات مقدار ۳۶۰۰ ثانیه، یعنی یک ساعت، گذاشتیم. مستندات امنیت جنگو هشدار میدهد که تنظیم نادرست HSTS میتواند سایت را برای مدت طولانی از دسترس خارج کند؛ چون مرورگرها تا پایان آن مدت دیگر نسخه HTTP را باز نمیکنند. پس اول مقدار کوچک بگذارید، چند روز سایت را بررسی کنید و بعد آن را بالا ببرید. INCLUDE_SUBDOMAINS و PRELOAD را فقط وقتی روشن کنید که مطمئن هستید همه زیردامنهها، حتی آنهایی که امروز استفاده نمیکنید، روی HTTPS هستند. هشدارهای W005 و W021 در check --deploy دقیقاً درباره همین دو تنظیماند.
جنگو هر ماه چند نسخه اصلاحی منتشر میکند؛ مثلاً نسخههای 6.1.3، 6.0.10 و 5.2.19 همگی سوم نوامبر ۲۰۲۶ آمدند. صفحه دانلود جنگو زمان پایان پشتیبانی هر شاخه را نشان میدهد. برای ارتقای امن، نسخه را در requirements.txt عوض کنید، روی سیستم خودتان آزمایش کنید و بعد روی سرور ببرید. از پایگاه داده و پوشه media هم نسخه پشتیبان بگیرید و دستکم یک بار بازگرداندن آن را واقعاً امتحان کنید؛ پشتیبانی که بازگردانیاش آزموده نشده، فقط یک امید است.
بعد از راهاندازی، هر بار که کد تازه دارید، همان چند قدم را به همان ترتیب تکرار کنید. میتوانید آنها را در یک اسکریپت کوتاه بنویسید:
#!/usr/bin/env bash
# /srv/myshop/deploy.sh (run as root)
set -euo pipefail
cd /srv/myshop/app
sudo -u myshop git pull --ff-only
sudo -u myshop /srv/myshop/venv/bin/pip install -r requirements.txt
sudo -u myshop bash -c 'set -a; source /etc/myshop/myshop.env; set +a;
/srv/myshop/venv/bin/python manage.py migrate --noinput &&
/srv/myshop/venv/bin/python manage.py collectstatic --noinput &&
/srv/myshop/venv/bin/python manage.py check --deploy'
systemctl reload gunicorn-myshop.service
reload سیگنال HUP را به Gunicorn میفرستد؛ Gunicorn ورکرهای تازه با کد جدید بالا میآورد و ورکرهای قدیمی را با آرامش خاموش میکند. برای بیشتر بهروزرسانیهای معمولی همین کافی است. اگر مهاجرت سنگینی دارید که جدول بزرگی را تغییر میدهد، آن را جداگانه و در زمان کمترافیک اجرا کنید. جدا کردن مرحله ساخت، انتشار و اجرا همان چیزی است که در فاکتور ساخت، انتشار، اجرا توضیح دادهایم. وقتی تیم بزرگتر شد، همین اسکریپت پایه یک پایپلاین CI/CD میشود.

| نشانه | علت رایج | راه حل |
|---|---|---|
| صفحه بدون CSS | collectstatic اجرا نشده یا alias در Nginx اشتباه است | collectstatic با تنظیمات تولید و بررسی مسیر STATIC_ROOT |
| 502 Bad Gateway | Gunicorn اجرا نشده یا مسیر سوکت متفاوت است | systemctl status و journalctl -u سرویس |
| 400 Bad Request | دامنه در ALLOWED_HOSTS نیست | افزودن دامنه به فایل محیطی و reload |
| خطای CSRF روی HTTPS | CSRF_TRUSTED_ORIGINS یا هدر X-Forwarded-Proto تنظیم نشده | افزودن نشانی کامل با https و هدر در Nginx |
| 403 روی فایلهای استاتیک | www-data اجازه خواندن پوشه را ندارد | اصلاح دسترسی پوشهها |
| خطای 500 بدون جزئیات | خطای برنامه با DEBUG خاموش | بررسی لاگها و تنظیم LOGGING یا ایمیل خطا |
این رایجترین مشکل اولین دیپلوی است. در محیط توسعه، runserver فایلهای استاتیک را خودش سرو میکرد؛ با DEBUG خاموش این کار انجام نمیشود. سه چیز را بررسی کنید: collectstatic با تنظیمات تولید اجرا شده باشد، مسیر alias در Nginx دقیقاً همان STATIC_ROOT باشد و کاربر www-data اجازه خواندن پوشه را داشته باشد. خطای 403 در لاگ Nginx معمولاً یعنی مشکل دسترسی پوشه است، نه جنگو.
یعنی Nginx نتوانسته به Gunicorn وصل شود. اول systemctl status gunicorn-myshop.service را ببینید. اگر سرویس اجرا نشده، لاگ journalctl معمولاً خطای import، متغیر محیطی جاافتاده یا مسیر اشتباه wsgi را نشان میدهد. اگر سرویس اجرا شده، مسیر سوکت در Nginx و فایل socket را مقایسه کنید و مطمئن شوید مالک سوکت www-data است.
اگر فرم ورود یا مدیریت روی HTTPS با خطای CSRF رد شد، دو تنظیم را ببینید: CSRF_TRUSTED_ORIGINS باید نشانی کامل با https داشته باشد و Nginx باید هدر X-Forwarded-Proto را بفرستد. در تنظیمات نمونه ما، فهرست مبداهای مورد اعتماد از همان ALLOWED_HOSTS ساخته میشود تا دو جا را جداگانه بهروز نکنید.
این آموزش WSGI را پوشش داد، چون بیشتر سایتهای جنگو همچنان با آن اجرا میشوند. اگر از ویوهای async، وبسوکت یا Django Channels استفاده میکنید، باید یک سرور ASGI داشته باشید؛ مثلاً Gunicorn همراه ورکر ASGI یا سرورهایی مثل Uvicorn و Daphne. منطق Nginx، systemd و HTTPS تقریباً همان است و فقط فرمان اجرای سرور عوض میشود. برای مقایسه رویکرد همگام و ناهمگام، جنگو یا FastAPI را بخوانید. داکر هم گزینه رایجی است، مخصوصاً وقتی چند سرویس مثل پایگاه داده، Redis و صف کار دارید. اما داکر جای فهم این قطعهها را نمیگیرد؛ داخل کانتینر هم همان Gunicorn و همان تنظیمات تولید را دارید. پلتفرمهای ابری آماده هم بخش سرور را برایتان مدیریت میکنند، ولی چکلیست امنیتی جنگو همچنان بر عهده شماست. اگر رابط برنامهنویسی دارید، آموزش Django REST Framework را ببینید؛ دیپلوی آن دقیقاً با همین روش انجام میشود. برای سازماندهی ویوها و مسیرها هم ویو و URL در جنگو را بخوانید. اگر میخواهید بدانید دکوریتورهایی مثل login_required که روی ویوها میگذارید چطور کار میکنند، آموزش دکوریتور در پایتون را ببینید. اگر ترجیح میدهید این کارها را به تیم متخصص بسپارید، خدمات طراحی و توسعه وبسایت رادینو را ببینید.
بعضی هاستها ابزار اجرای برنامه پایتون دارند، اما معمولاً کنترل کمی روی نسخه پایتون، فرایندها و تنظیمات وبسرور میدهند. برای پروژه جدی، سرور مجازی با دسترسی کامل یا پلتفرم مخصوص برنامههای پایتونی انتخاب قابلپیشبینیتری است.
مستندات Gunicorn اجرای آن پشت پروکسی را قویاً توصیه میکند. ورکرهای همگام در برابر کلاینتهای کند آسیبپذیرند، فایلهای استاتیک را Nginx سریعتر سرو میکند و HTTPS و محدود کردن دامنهها هم در Nginx سادهتر است.
هر دو سرور WSGI قابل استفادهاند و مستندات جنگو برای هر دو راهنما دارد. Gunicorn تنظیمات سادهتری دارد و برای شروع مناسبتر است. انتخاب مهمتر از نام سرور، رعایت چکلیست امنیتی و پایش سرور است.
لازم نیست کل سرور را ریاستارت کنید. بعد از گرفتن کد تازه و اجرای migrate و collectstatic، دستور systemctl reload gunicorn-myshop.service کافی است تا ورکرها با کد جدید بالا بیایند.
دستور python manage.py check --deploy را با تنظیمات تولید اجرا کنید و همه خطاها را برطرف کنید. هشدارها را هم بخوانید و برای هر کدام آگاهانه تصمیم بگیرید. بعد چکلیست رسمی دیپلوی جنگو را یک بار کامل مرور کنید.
نظرات 0
برای ارسال نظر وارد حساب کاربری شوید.
هنوز نظری ثبت نشده؛ اولین نفر باشید.