매일공부

[Django] 화면에서 로고 이미지가 깨지는데 PDF에는 정상 — DEBUG=False의 함정 본문

IT/작업기록

[Django] 화면에서 로고 이미지가 깨지는데 PDF에는 정상 — DEBUG=False의 함정

julian 2026. 8. 27. 14:23

증상

같은 이미지(회사 로고, 직인)인데 PDF에는 정상적으로 나오고, 웹 화면에서는 깨져서 보였다.

원인 — 미디어 파일 서빙이 DEBUG=True일 때만 등록되어 있었다

if settings.DEBUG:
    urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

미디어 파일(/media/...) 서빙이 DEBUG=True일 때만 URL에 등록되고 있었다. 운영서버는 DEBUG=False라서 Django가 /media/... 요청을 아예 처리하지 않고 404를 반환한다. 화면의 <img src="{{ company.logo.url }}">가 이 경로를 가리키니 깨질 수밖에 없었다.

반면 PDF는 WeasyPrint가 브라우저처럼 HTTP로 요청하는 게 아니라, 파일시스템 경로(MEDIA_ROOT)를 직접 읽어서 렌더링하기 때문에 이 문제와 무관했다 — 그래서 PDF는 멀쩡했던 것.

정적파일과 미디어파일은 서빙 주체가 다르다

정적파일(css/js)은 WhiteNoiseMiddleware가 운영에서도 서빙해주고 있었다. 하지만 WhiteNoise는 STATIC_ROOT만 서빙 대상으로 삼고, 업로드된 파일(MEDIA_ROOT)은 다루지 않는다.

서빙 주체 운영(DEBUG=False)에서

정적파일 (css/js) WhiteNoise 정상 서빙 ✅
미디어파일 (업로드 이미지) (아무도 없음) 404 ❌

DEBUG=True일 때는 Django가 둘 다 대신 서빙해주기 때문에 개발 중엔 이 차이가 전혀 드러나지 않았다. 딱 운영 배포 시점에야 터지는 전형적인 케이스.

nginx란 무엇이고 왜 필요한가

Django(gunicorn)를 운영 배포할 때 흔한 구조는 이렇다.

사용자 브라우저 → nginx → gunicorn → Django

nginx가 앞단에서 요청을 받아서, 정적파일/이미지처럼 단순한 파일 요청은 nginx가 디스크에서 직접 읽어 바로 응답하고, 그 외 동적인 요청(로그인, DB 조회 등)만 gunicorn에 넘긴다. gunicorn(파이썬 프로세스)이 파일 서빙까지 직접 하면 비효율적이라, 파일 서빙에 최적화된 nginx가 그 역할을 떼어가는 구조다.

선택지 두 가지

당장의 간단한 해결책(코드 한 줄): 미디어 서빙을 DEBUG 조건 밖으로 빼서 Django가 직접 서빙하게 하는 방법도 있었다. 지금 같은 내부용 소규모 서비스라면 실용적으로 문제없는 선택이다.

정석적인 해결책: nginx를 앞단에 추가하는 것. PDF/첨부파일이 앞으로 늘어날 걸 감안해서, 이번엔 nginx를 도입하는 쪽으로 결정했다 — 실제로 어떻게 붙였는지는 다음 글에서 이어서 다룬다.

정리

  • DEBUG=True/False에 따라 동작이 달라지는 코드는, 로컬 개발 환경에서는 절대 문제가 드러나지 않는다는 걸 명심해야 한다
  • "정적파일"과 "미디어파일"은 이름은 비슷해 보여도 Django/WhiteNoise 입장에서는 완전히 다른 취급을 받는다 — 하나가 되면 다른 하나도 될 거라고 넘겨짚으면 안 된다
Comments