איך להריץ אפליקציות פייתון עם Gunicorn ו-Nginx על VPS
פרסו אפליקציית Flask או FastAPI על Ubuntu עם virtualenv, שירות systemd של Gunicorn, פרוקסי הפוך של Nginx, TLS, קובצי סביבה, רישום ורשימת בדיקה לשגיאות 502 וגודל עובדים.

שרת Flask המובנה ו-uvicorn --reload מיועדים לפיתוח. על VPS ציבורי רוצים מנהל תהליכים שמפעיל מחדש עובדים, נקשר ל-localhost ויושב מאחורי פרוקסי הפוך שמטפל ב-TLS ובלקוחות איטיים. Gunicorn הוא בחירת WSGI הרגילה ל-Flask ול-Django. FastAPI יכול לרוץ תחת Gunicorn עם מחלקת עובד Uvicorn. Nginx (או Caddy) מסיים HTTPS ומעביר אל 127.0.0.1:8000.
המדריך עובר על פריסה ששורדת אתחולים מחדש: פרויקט ב-/srv/app, virtualenv, .env עם סודות, יחידת gunicorn.service, בלוק שרת Nginx, Let's Encrypt וקובצי יומן שאפשר באמת לעשות עליהם grep. נדבר גם על מספר עובדים, זמני קצוב, ולמה 502 Bad Gateway כמעט אף פעם לא «Nginx שבור» — כמעט תמיד Gunicorn לא רץ, נקשר לשקע הלא נכון, או קורס בייבוא. הדוגמה משתמשת ב-Flask עם הערות ל-FastAPI במקום שהפקודה שונה.
למה המחסנית הזו
Gunicorn עושה fork מראש לתהליכי עובד. כל עובד מטפל בבקשה אחת בכל פעם אלא אם משתמשים במחלקת עובד אחרת. Nginx מאחסן לקוחות איטיים כדי שעובדים לא ייתקעו בשליחת בתים לרשת סלולרית. 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 עם עובדי uvicorn)
- רשומת A של דומיין ל-HTTPS
שלב 1: חבילות מערכת, משתמש ותיקיית פרויקט
צרו משתמש מערכת שלא יכול להיכנס באופן אינטראקטיבי, שיחזיק בקוד ויריץ Gunicorn. התקנת python3-venv וכלי בנייה מונעת כשלי pip בחבילות שעדיין מהדרות הרחבות C.
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/שלב 2: Virtualenv ותלויות
צרו את ה-venv כ-appuser כדי שבעלות הקבצים תהיה נכונה. נעצו גרסאות בייצור. אחרי התקנה אשרו שאפשר לייבא את האפליקציה ב-gunicorn --check-config חד-פעמי או בייבוא פייתון. שגיאות ייבוא כאן הן אותן שגיאות שהופכות ל-502 אחר כך.
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')"שלב 3: קובץ סביבה
אל תקודדו SECRET_KEY או כתובות מסד ביחידת systemd באופן שיגיע ל-git קריא לכל העולם. השתמשו ב-EnvironmentFile. chmod 640, בעלים root, קבוצה appuser (או בעלים appuser אם מעדיפים).
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שלב 4: שירות systemd של Gunicorn
קשרו ל-127.0.0.1:8000, לא 0.0.0.0, אלא אם יש סיבה לדלג על Nginx. מספר עובדים לעיתים קרובות (2 × CPU) + 1 לעובדים סינכרוניים; על VPS עם 2 vCPU זה 5, וזה יכול להיות יותר מדי אם כל עובד טוען מודל למידת מכונה כבד — אז השתמשו ב-2–3. ל-FastAPI הגדירו --worker-class uvicorn.workers.UvicornWorker והתקינו uvicorn. זמני קצוב צריכים לחרוג מהבקשה הכנה האיטית ביותר שלכם, לא 30 שניות אם יש ייצואים של שתי דקות.
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שלב 5: פרוקסי הפוך של Nginx ו-TLS
Nginx מאזין על 80/443 ועושה פרוקסי ל-Gunicorn. client_max_body_size חשוב להעלאות. proxy_read_timeout צריך להתאים לקצוב של Gunicorn או לחרוג ממנו. אחרי שבלוק השרת עובד ב-HTTP הנפיקו תעודה עם Certbot (או החליפו את החזית ל-Caddy). הקטע למטה הוא HTTP בלבד כדי שתוכלו לבדוק; אחר כך הריצו Certbot שיכול לערוך את הקובץ.
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שלב 6: קבצים סטטיים, הרשאות ופרטי Flask/FastAPI
Nginx צריך להגיש קבצים סטטיים אם אפשר; זה מהיר מ-Gunicorn. Django collectstatic, Flask send_from_directory לאפליקציות זעירות, או CDN אחר כך. appuser חייב להיות מסוגל לקרוא את העץ. אם משתמשים ב-FastAPI התקינו uvicorn[standard] ב-venv ושנו את ExecStart להשתמש ב-UvicornWorker. אם משתמשים בשקעי Unix במקום TCP, הפנו proxy_pass לשקע והתאימו הרשאות כדי ש-www-data יוכל לכתוב אליו.
# 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עובדים, זיכרון והפעלות מחדש בלי השבתה
עובדי Gunicorn סינכרוניים פשוטים ומספיקים לממשקי בקשה/תגובה קלים במעבד. אם צריך הרבה המתנות קלט/פלט איטיות במקביל, שקלו gevent או עובד ASGI — מדדו, אל תנחשו. כל עובד טוען את האפליקציה; RAM ~= עובדים כפול RSS של האפליקציה. VPS של 2 GB עם 8 עובדים של אפליקציה ב-300 MB יחליף וירגיש «לאט באקראי». systemctl reload gunicorn (HUP) יכול להפעיל מחדש עובדים עם הקוד החדש אם פרסתם קבצים במקום; הפעלה מחדש מלאה ברורה יותר כשתלויות משתנות.
# 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, סביבה חסרה, מסד
- curl -v http://127.0.0.1:8000/ מה-VPS — אם זה נכשל Nginx חף מפשע
- nginx -t ו-error.log — upstream prematurely closed connection
- ss -tulpn | grep 8000 — שום דבר לא מאזין
- דיסק מלא — עובדים קורסים בדרכים מסתוריות
אבטחה
האפליקציה לעולם לא נקשרת בציבור. סודות נשארים ב-.env. שמרו על ה-venv ומערכת ההפעלה מתוקנים. אל תריצו Gunicorn כ-root. אם מטפלים בכניסות הגדירו עוגיות הפעלה Secure ו-SameSite, והגדירו את המסגרת לסמוך על X-Forwarded-Proto מ-Nginx בלבד. הגבילו קצב בנתיבי כניסה ב-Nginx או באפליקציה.
- קשרו 127.0.0.1 או שקע unix
- chmod 600 .env
- User= שאינו root ב-systemd
- TLS דרך Certbot או Caddy
- כבו מצב ניפוי באגים והפעלה מחדש אוטומטית בייצור
טיפים
- הוסיפו נתיב /health שבודק קישוריות מסד ל-Compose ולמאזני עומס
- שלחו נכסים סטטיים עם כותרת cache-control ב-Nginx
- השתמשו בעובד או תור נפרד (Redis + systemd) למיילים ולעבודות כבדות
- נעצו גרסאות gunicorn ו-uvicorn ב-requirements.txt
- צלמו תמונת מצב לפני המעבר הראשון לייצור
אפליקציית פייתון על VPS מוכנה לייצור כשהיא רצה תחת Gunicorn כשירות systemd, נקשרת רק ל-localhost, ומגיעים אליה דרך Nginx או Caddy עם TLS. שימו סודות ב-EnvironmentFile, התאימו עובדים ל-RAM ולא לנוסחת פוסט בבלוג, ונפו 502 קודם מיומן Gunicorn. ברגע שהנתיב הזה מתועד למאגר שלכם, כל פריסה מאוחרת היא rsync או git pull, pip install ו-systemctl restart gunicorn.