بازگشت به وبلاگ
اوت 19, 2026راهنماها

نحوه اجرای برنامه‌های Python با Gunicorn و Nginx روی VPS

برنامهٔ Flask یا FastAPI را روی Ubuntu با virtualenv، سرویس systemd مربوط به Gunicorn، پروکسی معکوس Nginx، TLS، فایل محیط، لاگ و چک‌لیست خطای 502 و اندازهٔ worker مستقر کنید.

نحوه اجرای برنامه‌های Python با Gunicorn و Nginx روی VPS

سرور داخلی Flask و uvicorn --reload برای توسعه است. روی VPS عمومی مدیر فرایندی می‌خواهید که worker را ری‌استارت کند، به localhost وصل شود و پشت پروکسی معکوسی بنشیند که TLS و کلاینت کند را مدیریت کند. Gunicorn انتخاب معمول WSGI برای Flask و Django است. FastAPI می‌تواند زیر Gunicorn با کلاس worker از نوع Uvicorn اجرا شود. Nginx (یا Caddy) HTTPS را تمام می‌کند و به 127.0.0.1:8000 می‌فرستد.

این راهنما چیدمانی را طی می‌کند که از ری‌بوت جان سالم به‌در می‌برد: پروژه در /srv/app، virtualenv، .env با راز، واحد gunicorn.service، بلوک سرور Nginx، Let's Encrypt و فایل لاگی که واقعاً بتوانید grep کنید. همچنین دربارهٔ تعداد worker، مهلت و این‌که چرا 502 Bad Gateway تقریباً هرگز «Nginx خراب است» نیست حرف می‌زنیم — تقریباً همیشه Gunicorn اجرا نمی‌شود، به سوکت اشتباه بسته شده یا هنگام import می‌ترکد. مثال از Flask است با یادداشت FastAPI جایی که فرمان فرق دارد.

چرا این پشته

Gunicorn فرایندهای worker را از پیش fork می‌کند. مگر کلاس worker دیگری استفاده کنید هر worker در یک زمان یک درخواست را می‌گیرد. Nginx کلاینت کند را بافر می‌کند تا worker گیر ارسال بایت به شبکهٔ موبایل نماند. systemd اگر برنامه بمیرد ری‌استارت می‌کند. با هم این کسل‌کننده است؛ همان چیزی که ساعت ۳ صبح می‌خواهید.

  • Gunicorn: مدل فرایند پایدار WSGI/ASGI
  • systemd: شروع هنگام بوت، ری‌استارت هنگام سقوط، لاگ journald
  • Nginx: TLS، فایل ایستا، محدودیت اندازهٔ درخواست، gzip
  • venv: پایتون سیستم تمیز می‌ماند
  • اتصال localhost: برنامه جز از طریق Nginx در دسترس نیست

پیش‌نیازها

Python 3.10+ روی Ubuntu 22.04/24.04 کافی است. pip را به‌عنوان root داخل site-packages سیستم اجرا نکنید. برای TLS به دامنه نیاز دارید. اگر Caddy را به‌عنوان پروکسی ترجیح می‌دهید واحد Gunicorn این مقاله همان می‌ماند — فقط کانفیگ جلو عوض می‌شود (مقالهٔ Caddy را ببینید).

  • VPS با Ubuntu 22.04 یا 24.04
  • برنامه‌تان با requirements.txt یا معادل
  • نقطهٔ ورود WSGI (برای Flask: app:app) یا ASGI (برای FastAPI: app:app با workerهای uvicorn)
  • رکورد A دامنه برای HTTPS

گام ۱: بسته‌های سیستم، کاربر و پوشهٔ پروژه

کاربر سیستمی بسازید که نتواند تعاملی وارد شود، مالک کد باشد و Gunicorn را اجرا کند. نصب python3-venv و ابزار ساخت از شکست pip روی بسته‌هایی که هنوز افزونهٔ C کامپایل می‌کنند جلوگیری می‌کند.

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/

گام ۲: Virtualenv و وابستگی‌ها

venv را به‌عنوان appuser بسازید تا مالکیت فایل درست باشد. در تولید نسخه را پین کنید. بعد از نصب تأیید کنید می‌توانید برنامه را در gunicorn --check-config یک‌باره یا import پایتون وارد کنید. خطاهای import اینجا همان خطاهایی هستند که بعداً 502 می‌شوند.

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

گام ۳: فایل محیط

SECRET_KEY یا URL پایگاه را در واحد systemd طوری سخت‌کد نکنید که به git قابل خواندن برای همه برسد. از EnvironmentFile استفاده کنید. chmod 640، مالک root، گروه appuser (یا اگر ترجیح می‌دهید مالک appuser).

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

گام ۴: سرویس systemd مربوط به Gunicorn

به 127.0.0.1:8000 وصل شوید نه 0.0.0.0، مگر دلیلی برای رد کردن Nginx دارید. تعداد worker برای worker همگام اغلب (2 × CPU) + 1 است؛ روی VPS با 2 vCPU این ۵ است که اگر هر worker مدل سنگین یادگیری ماشین بار کند زیاد است — آن‌وقت 2–3 استفاده کنید. برای FastAPI مقدار --worker-class uvicorn.workers.UvicornWorker را بگذارید و uvicorn نصب کنید. مهلت باید از کندترین درخواست صادق شما بیشتر باشد، نه ۳۰ ثانیه اگر خروجی دو دقیقه‌ای دارید.

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

گام ۵: پروکسی معکوس Nginx و TLS

Nginx روی 80/443 گوش می‌دهد و به Gunicorn پروکسی می‌کند. برای آپلود client_max_body_size مهم است. proxy_read_timeout باید با مهلت Gunicorn جور باشد یا از آن بیشتر. بعد از کار کردن بلوک سرور روی HTTP با Certbot گواهی بگیرید (یا جلو را به Caddy عوض کنید). قطعهٔ زیر فقط HTTP است تا آزمایش کنید؛ بعد Certbot را اجرا کنید که می‌تواند فایل را ویرایش کند.

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

گام ۶: فایل ایستا، مجوز و جزئیات Flask/FastAPI

اگر می‌توانید Nginx باید فایل ایستا را سرو کند؛ از Gunicorn سریع‌تر است. Django collectstatic، Flask send_from_directory برای برنامه‌های کوچک، یا بعداً CDN. appuser باید درخت را بخواند. اگر FastAPI استفاده می‌کنید uvicorn[standard] را در venv نصب کنید و ExecStart را به UvicornWorker عوض کنید. اگر به‌جای TCP از سوکت Unix استفاده می‌کنید proxy_pass را به سوکت بزنید و مجوز را جور کنید تا www-data بتواند بنویسد.

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

Workerها، حافظه و بارگذاری بدون قطعی

Worker همگام Gunicorn ساده است و برای API درخواست/پاسخ سبک روی CPU کافی است. اگر انتظار ورودی/خروجی کند همزمان زیاد لازم دارید gevent یا worker از نوع ASGI را در نظر بگیرید — اندازه بگیرید، حدس نزنید. هر worker برنامه را بار می‌کند؛ RAM تقریباً برابر worker ضربدر RSS برنامه. VPS دو گیگابایتی با ۸ worker برای برنامهٔ ۳۰۰ مگابایتی سواپ می‌کند و «تصادفی کند» حس می‌شود. systemctl reload gunicorn (HUP) اگر فایل را درجا مستقر کردید worker را با کد جدید ری‌استارت می‌کند؛ وقتی وابستگی عوض شود ری‌استارت کامل روشن‌تر است.

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

عیب‌یابی 502 و سقوط بی‌صدا

502 یعنی Nginx نتوانست پاسخ معتبر از بالادست بگیرد. اولین فرمان journalctl -u gunicorn -e است. علت‌های رایج: نام غلط module:app، کلید گم‌شده در .env، Postgres اجرا نیست، به 127.0.0.1 بسته شده اما Nginx روی میزبان دیگر است، SELinux (روی Ubuntu نادر)، یا برنامه فقط روی IPv6 گوش می‌دهد. 504 مهلت است. حلقهٔ 301 وقتی رخ می‌دهد که برنامه به HTTP هدایت کند در حالی که X-Forwarded-Proto نادیده گرفته شده.

  • systemctl status gunicorn — فعال است؟
  • journalctl -u gunicorn -n 100 — ImportError، محیط گم، پایگاه داده
  • از VPS دستور curl -v http://127.0.0.1:8000/ — اگر این شکست Nginx بی‌گناه است
  • nginx -t و error.log — upstream prematurely closed connection
  • ss -tulpn | grep 8000 — چیزی گوش نمی‌دهد
  • دیسک پر — worker به شکل مرموز می‌ترکد

امنیت

برنامه هرگز عمومی bind نمی‌شود. رازها در .env می‌مانند. venv و سیستم‌عامل را وصله‌شده نگه دارید. Gunicorn را به‌عنوان root اجرا نکنید. اگر ورود را مدیریت می‌کنید کوکی نشست را Secure و SameSite بگذارید و چارچوب را طوری تنظیم کنید که فقط از Nginx به X-Forwarded-Proto اعتماد کند. مسیر ورود را در Nginx یا در برنامه محدود به نرخ کنید.

  • به 127.0.0.1 یا سوکت unix وصل شوید
  • chmod 600 .env
  • User= غیر root در systemd
  • TLS از طریق Certbot یا Caddy
  • حالت debug و بارگذاری خودکار را در تولید خاموش کنید

نکات

  • مسیر /health اضافه کنید که اتصال پایگاه را برای Compose و متعادل‌کنندهٔ بار چک کند
  • دارایی ایستا را با هدر cache-control در Nginx بفرستید
  • برای ایمیل و کار سنگین از worker یا صف جدا استفاده کنید (Redis + systemd)
  • نسخهٔ gunicorn و uvicorn را در requirements.txt پین کنید
  • قبل از اولین انتقال تولید اسنپ‌شات بگیرید

برنامهٔ Python روی VPS وقتی آمادهٔ تولید است که زیر Gunicorn به‌عنوان سرویس systemd اجرا شود، فقط به localhost وصل شود و از طریق Nginx یا Caddy با TLS برسد. رازها را در EnvironmentFile بگذارید، worker را بر اساس RAM اندازه بگیرید نه فرمول پست وبلاگ، و 502 را اول از ژورنال Gunicorn اشکال‌زدایی کنید. وقتی این مسیر برای مخزن شما مستند شد هر استقرار بعدی rsync یا git pull، pip install و systemctl restart gunicorn است.