TürkiyeVPS

اجرای برنامه پایتون روی VPS: venv، systemd و پراکسی معکوس Nginx

راهنمای گام به گام برای اجرای دائمی برنامه وب یا ربات پایتونی روی VPS: محیط مجازی (venv)، Gunicorn، سرویس systemd، پراکسی معکوس Nginx و SSL رایگان.

8 دقیقه مطالعه

وقتی برنامه‌ای را که روی رایانه‌تان با python app.py بی‌دردسر اجرا می‌شود به سرور منتقل می‌کنید، پرسش‌های تازه‌ای پیش می‌آید: چرا با بستن ترمینال برنامه متوقف می‌شود؟ اگر سرور راه‌اندازی مجدد شود چه اتفاقی می‌افتد؟ چگونه می‌توان با نام دامنه و از طریق HTTPS به برنامه دسترسی داشت؟ در این راهنما یک برنامه وب نوشته‌شده با Python 3.12 را روی یک VPS با Ubuntu 24.04 به شکلی مناسب محیط تولید (production) منتشر می‌کنیم.

ساختاری که راه‌اندازی می‌کنیم این است:

  1. برنامه در محیط مجازی (venv) مخصوص به خود اجرا می‌شود.
  2. Gunicorn برنامه را روی پورتی اجرا می‌کند که فقط از داخل سرور قابل دسترسی است.
  3. systemd برنامه Gunicorn را به عنوان یک سرویس مدیریت می‌کند: هنگام بوت آن را اجرا می‌کند و اگر از کار بیفتد دوباره راه‌اندازی‌اش می‌کند.
  4. Nginx درخواست‌های بیرونی را دریافت می‌کند و به برنامه می‌رساند (پراکسی معکوس، reverse proxy).

در این مثال از Flask استفاده می‌کنیم. مراحل برای Django یا FastAPI هم تقریباً یکسان است؛ تفاوت‌ها را در جای خود ذکر می‌کنیم.

۱. سرور را آماده کنید

با SSH متصل شوید، بسته‌ها را به‌روز کنید و ابزارهای لازم را نصب کنید:

sudo apt update && sudo apt upgrade -y
sudo apt install -y python3 python3-venv python3-pip nginx
python3 --version

در Ubuntu 24.04 خروجی python3 --version باید Python 3.12.x باشد. از نظر امنیتی مهم است که برنامه را به جای root با کاربری مخصوص خودش و با دسترسی محدود اجرا کنید:

sudo adduser --system --group --home /srv/myapp myapp

۲. برنامه را مستقر کنید و محیط مجازی بسازید

کد خود را در پوشه /srv/myapp/app قرار دهید. اگر از Git استفاده می‌کنید:

sudo -u myapp git clone https://github.com/kullanici/proje.git /srv/myapp/app

برای آزمایش می‌توانید یک برنامه کوچک Flask هم بنویسید. /srv/myapp/app/app.py:

from flask import Flask

app = Flask(__name__)

@app.get("/")
def index():
    return "Merhaba, uygulama VPS'te çalışıyor!"

حالا محیط مجازی را بسازید و وابستگی‌ها را نصب کنید:

cd /srv/myapp
sudo -u myapp python3 -m venv venv
sudo -u myapp venv/bin/pip install --upgrade pip
sudo -u myapp venv/bin/pip install flask gunicorn

اگر پروژه شما فایل requirements.txt دارد، به جای خط آخر از sudo -u myapp venv/bin/pip install -r app/requirements.txt gunicorn استفاده کنید.

چرا venv؟ Ubuntu 24.04 اجازه نمی‌دهد با pip روی پایتون سیستم بسته نصب کنید (خطای externally-managed-environment). این یک محافظت آگاهانه است: مانع می‌شود بسته‌هایی را که ابزارهای سیستم به آن‌ها وابسته‌اند خراب کنید. یک venv جداگانه برای هر پروژه، تداخل وابستگی‌ها را هم از بین می‌برد.

۳. با Gunicorn آزمایش کنید

سرور توسعه Flask برای محیط تولید مناسب نیست. به جای آن از Gunicorn استفاده می‌کنیم:

cd /srv/myapp/app
sudo -u myapp ../venv/bin/gunicorn --bind 127.0.0.1:8000 app:app

وقتی از یک ترمینال دیگر curl http://127.0.0.1:8000 را اجرا کنید، باید متن خوشامد را ببینید. app:app یعنی «شیء app در فایل app.py». در Django این مقدار معمولاً proje.wsgi:application است. در برنامه‌های ASGI مانند FastAPI، پارامتر -k uvicorn.workers.UvicornWorker به Gunicorn اضافه می‌شود (برای این کار باید بسته uvicorn را هم نصب کنید).

پس از آزمایش، با Ctrl+C آن را متوقف کنید. توجه کنید که برنامه به 127.0.0.1 متصل شده است: از بیرون مستقیماً قابل دسترسی نیست و ترافیک را از طریق Nginx دریافت خواهد کرد.

۴. سرویس systemd بسازید

برای اینکه برنامه با بستن ترمینال متوقف نشود و با راه‌اندازی مجدد سرور خودبه‌خود اجرا شود، یک سرویس systemd می‌نویسیم:

sudo nano /etc/systemd/system/myapp.service

محتوای آن:

[Unit]
Description=myapp Gunicorn servisi
After=network.target

[Service]
User=myapp
Group=myapp
WorkingDirectory=/srv/myapp/app
Environment="PATH=/srv/myapp/venv/bin"
EnvironmentFile=-/srv/myapp/.env
ExecStart=/srv/myapp/venv/bin/gunicorn --workers 3 --bind 127.0.0.1:8000 app:app
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

چند توضیح:

  • EnvironmentFile به شما امکان می‌دهد اطلاعات محرمانه‌ای مانند رمز پایگاه داده یا کلید API را جدا از کد نگه دارید. علامت - در ابتدای آن برای این است که اگر فایل وجود نداشت، سرویس باز هم اجرا شود.
  • --workers 3 برای یک سرور کوچک شروع معقولی است. توصیه رایج (۲ × تعداد هسته‌ها) + ۱ است، اما RAM خود را هم در نظر بگیرید.
  • Restart=on-failure اگر برنامه با خطا بسته شود، آن را پس از پنج ثانیه دوباره اجرا می‌کند.

سرویس را فعال و اجرا کنید:

sudo systemctl daemon-reload
sudo systemctl enable --now myapp
systemctl status myapp

در سطر وضعیت باید active (running) را ببینید. اگر خطایی وجود دارد، لاگ‌ها را بررسی کنید:

journalctl -u myapp -n 50 --no-pager

۵. Nginx را به عنوان پراکسی معکوس تنظیم کنید

ابتدا رکورد A در DNS دامنه خود را به آدرس IP سرور VPS اشاره دهید. سپس یک فایل سایت برای Nginx بسازید:

sudo nano /etc/nginx/sites-available/myapp
server {
    listen 80;
    server_name ornek.com www.ornek.com;

    client_max_body_size 20M;

    location /static/ {
        alias /srv/myapp/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;
    }
}

بلوک /static/ باعث می‌شود فایل‌های CSS، JS و تصاویر بدون درگیر کردن پایتون، مستقیماً توسط Nginx ارائه شوند. اگر برنامه شما چنین پوشه‌ای ندارد، می‌توانید این بلوک را حذف کنید. سایت را فعال کنید:

sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx

دستور nginx -t بررسی می‌کند که آیا فایل تنظیمات خطا دارد یا نه؛ عادت کنید پس از هر تغییر آن را اجرا کنید.

۶. فایروال و HTTPS

اگر از UFW استفاده می‌کنید، به ترافیک SSH و وب اجازه دهید:

sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable

برای دریافت گواهی SSL رایگان از Let's Encrypt:

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

Certbot بلوک HTTPS را خودش به تنظیمات Nginx اضافه می‌کند و تمدید خودکار گواهی را هم تنظیم می‌کند. می‌توانید با sudo certbot renew --dry-run بررسی کنید که تمدید کار می‌کند.

به‌روزرسانی برنامه

وقتی کد را تغییر می‌دهید، این کارها را انجام دهید:

cd /srv/myapp/app
sudo -u myapp git pull
sudo -u myapp ../venv/bin/pip install -r requirements.txt
sudo systemctl restart myapp

اطلاعات محرمانه را از کد جدا کنید

نوشتن مقادیری مانند رمز پایگاه داده، کلید API یا SECRET_KEY در داخل کد، ممکن است هنگام ارسال کد به یک مخزن Git باعث نشت آن‌ها شود. برای این کار از فایل /srv/myapp/.env که در سرویس systemd تعریف کردیم استفاده کنید:

sudo nano /srv/myapp/.env
DATABASE_URL=postgresql://myapp:[email protected]:5432/myapp
SECRET_KEY=uzun-ve-rastgele-bir-deger

مجوزهای فایل را طوری محدود کنید که فقط کاربر برنامه بتواند آن را بخواند و سرویس را دوباره راه‌اندازی کنید:

sudo chown myapp:myapp /srv/myapp/.env
sudo chmod 600 /srv/myapp/.env
sudo systemctl restart myapp

در سمت پایتون این مقادیر را با os.environ["DATABASE_URL"] می‌خوانید. برای تولید یک کلید تصادفی و قوی می‌توانید از دستور python3 -c "import secrets; print(secrets.token_hex(32))" استفاده کنید.

اشتباهات رایج

  • اجرای برنامه با root: اگر حفره‌ای در برنامه پیدا شود، مهاجم کل سرور را در اختیار می‌گیرد. همیشه از یک کاربر جداگانه با دسترسی محدود استفاده کنید.
  • باز گذاشتن سرور توسعه به روی بیرون: دستور flask run --host 0.0.0.0 یا runserver در Django برای محیط تولید طراحی نشده‌اند. از Gunicorn و Nginx استفاده کنید.
  • روشن گذاشتن حالت اشکال‌زدایی: debug=True در Flask و DEBUG = True در Django ممکن است کد و تنظیمات شما را در صفحات خطا نمایش دهند.
  • فراموش کردن تنظیم ALLOWED_HOSTS در Django: اگر دامنه خود را به این فهرست اضافه نکنید، درخواست‌هایی که از طریق Nginx می‌رسند خطای «Bad Request (400)» می‌گیرند. اجرای دستور collectstatic برای فایل‌های استاتیک را هم فراموش نکنید.
  • مجوزهای پوشه: برای اینکه Nginx بتواند فایل‌های استاتیک را بخواند، پوشه /srv/myapp/app/static و پوشه‌های بالاتر باید برای کاربر www-data قابل خواندن باشند. اگر خطای 403 می‌گیرید، اولین جایی که باید بررسی کنید همین‌جاست.

اگر ربات یا کار پس‌زمینه اجرا می‌کنید

برای ربات تلگرام/دیسکورد یا اسکریپتی که رابط وب ندارد، به Nginx نیازی نیست. کافی است در همان فایل systemd سطر ExecStart را به /srv/myapp/venv/bin/python bot.py تغییر دهید. systemd ربات را روشن نگه می‌دارد و اگر از کار بیفتد دوباره راه‌اندازی‌اش می‌کند.

چه سروری کافی است؟

برای یک برنامه کوچک Flask/FastAPI یا یک ربات، معمولاً یک VPS با ۱ تا ۲ vCPU و ۱ تا ۲ گیگابایت RAM کافی است. اگر مدل یادگیری ماشین، کارهای پس‌زمینه سنگین یا مصرف دائمی و بالای پردازنده دارید، سروری که منابعش با دیگران به اشتراک گذاشته نمی‌شود نتیجه پایدارتری می‌دهد. پکیج‌های VPS و VDS در TürkiyeVPS با دسترسی root ارائه می‌شوند؛ می‌توانید تک‌تک مراحل این راهنما را روی سرور خودتان اجرا کنید.

سوالات متداول

آیا اجرا با screen یا tmux کافی نیست؟

برای آزمایش کافی است، اما وقتی سرور راه‌اندازی مجدد شود یا برنامه از کار بیفتد، خودبه‌خود دوباره اجرا نمی‌شود. چون systemd هم اجرای خودکار و هم ثبت لاگ را فراهم می‌کند، برای برنامه‌های دائمی باید آن را ترجیح داد.

خطای «externally-managed-environment» می‌گیرم؛ چه کنم؟

این خطا زمانی دیده می‌شود که بخواهید مستقیماً روی پایتون سیستم pip install اجرا کنید. برای پروژه خود با python3 -m venv venv یک محیط مجازی بسازید و بسته‌ها را با venv/bin/pip نصب کنید.

برنامه خطای 502 Bad Gateway می‌دهد؛ چرا؟

یعنی Nginx نمی‌تواند به برنامه پشت خود برسد. با systemctl status myapp بررسی کنید که سرویس در حال اجراست، با journalctl -u myapp ببینید خطایی وجود دارد یا نه و مطمئن شوید پورت proxy_pass در Nginx با پورت Gunicorn یکسان است.

آیا می‌توانم چند برنامه پایتون را روی یک VPS اجرا کنم؟

بله. کافی است برای هر برنامه یک venv جداگانه، یک پورت متفاوت (مانند 8000 و 8001)، یک سرویس systemd جداگانه و یک بلوک server جداگانه در Nginx بسازید.