Python 애플리케이션: gunicorn과 systemd
VDS에서 Django, Flask, FastAPI를 실행하는 방법을 알아봐요. 가상 환경, 소켓을 사용하는 gunicorn, systemd 서비스, 등급에 맞는 워커 수 선택을 다뤄요.
업데이트 2026년 8월 23일
내장 개발 서버(manage.py runserver, flask run)는 프로덕션 부하를 견디도록 설계되지 않았어요. VPS에서는 gunicorn이나 uvicorn을 설치하고, 웹 서버가 이들을 외부에 서비스하도록 구성해요.
환경
프로젝트 패키지를 시스템 Python에 절대 설치하지 마세요 — 시스템 업데이트가 애플리케이션을 망가뜨려요.
apt install -y python3 python3-venv python3-pip
useradd -r -m -d /srv/app -s /usr/sbin/nologin appuser
sudo -u appuser python3 -m venv /srv/app/venv
sudo -u appuser /srv/app/venv/bin/pip install -r /srv/app/requirements.txt
sudo -u appuser /srv/app/venv/bin/pip install gunicorn
FastAPI의 경우 uvicorn[standard]를 추가로 설치하세요 — gunicorn이 이를 워커 클래스로 실행해요.
서비스
/etc/systemd/system/myapp.service:
[Unit]
Description=Python app
After=network-online.target postgresql.service
[Service]
User=appuser
WorkingDirectory=/srv/app
EnvironmentFile=/srv/app/.env
ExecStart=/srv/app/venv/bin/gunicorn \
--workers 3 \
--bind unix:/run/myapp/gunicorn.sock \
--access-logfile - \
myproject.wsgi:application
RuntimeDirectory=myapp
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
FastAPI의 경우 실행 줄을 --worker-class uvicorn.workers.UvicornWorker app.main:app로 바꾸세요.
systemctl daemon-reload
systemctl enable --now myapp
journalctl -u myapp -f
RuntimeDirectory=myapp는 시작할 때마다 필요한 권한과 함께 /run/myapp을 생성해요 — 그렇지 않으면 소켓을 둘 곳이 없어요.
워커 수
일반적인 공식은 2 × cores + 1이지만, 메모리 한계에 부딪혀요. 각 Django 워커는 80–200 MB를 사용해요.
| 티어 | 코어 | 워커 |
|---|---|---|
| VDS-1 | 1 | 2 |
| VDS-2 | 2 | 3 |
| VDS-3 | 4 | 5 |
| VDS-4 | 6 | 9 |
애플리케이션이 네트워크와 데이터베이스 대기에 많은 시간을 쓴다면, 프로세스 수를 늘리는 대신 --threads 2를 추가하세요.
웹 서버
location / {
proxy_pass http://unix:/run/myapp/gunicorn.sock;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /static/ { alias /srv/app/static/; }
Django 정적 파일은 collectstatic으로 수집한 뒤 Nginx로 직접 서비스하세요. Python을 거쳐 전달하면 불필요한 부하가 생겨요.
자주 발생하는 오류
| 증상 | 원인 |
|---|---|
502 및 connect() to unix:... failed (13) | Nginx에 소켓 권한이 없음 — --umask 007과 공유 그룹을 설정하세요 |
ModuleNotFoundError | venv가 아닌 시스템 Python으로 실행함 |
Django의 DisallowedHost | 도메인이 ALLOWED_HOSTS에 추가되지 않음 |
| 워커가 타임아웃으로 종료됨 | 긴 요청 — --timeout을 늘리고 무거운 작업을 큐로 옮기세요 |
| 릴리스 후 모든 것이 멈춤 | systemctl restart myapp을 잊음 — 메모리 속 코드는 스스로 갱신되지 않아요 |
시크릿은
settings.py가 아니라600권한의EnvironmentFile에 보관하세요. 설정 파일은 결국 저장소에 들어가게 돼요.