Synology NAS에서 WordPress + Docker + HTTPS 운영 체크리스트

Synology NAS에서 Docker Compose로 WordPress 블로그 완전 자립 운영하기

TL;DR

Synology DSM 7.2 이상 환경에서 Container Manager와 Docker Compose를 활용해 WordPress, MariaDB, Nginx Proxy Manager 스택을 구성한다. Let’s Encrypt 인증서 발급과 리버스 프록시 설정으로 HTTPS를 적용하며, UID/GID 불일치 문제와 포트 충돌을 해결하는 실무 패턴을 제시한다. 개인 포트폴리오 블로그부터 소규모 스타트업 인프라까지, 월 3만원 미만의 전기세로 완전한 데이터 주권을 확보하는 아키텍처다.


문제 정의: 왜 Synology에서 WordPress를 직접 호스팅해야 하는가

3~7년차 백엔드 및 ML 개발자에게 클라우드 종속성은 더 이상 기술적 제약이 아니라 전략적 선택의 문제다. AWS Lightsail, Vultr VPS, Netlify, Cloudflare Pages 등 선택지는 무궁무진하지만, 다음과 같은 상황에서는 자체 호스팅이 타당한 대안이 된다.

데이터 주권과 비용 최적화의 교차점: 소규모 스타트업에서 클라이언트 작업물을 시연하거나 개인 포트폴리오를 운영할 때, 월 $5~20의 VPS 비용은 미미해 보인다. 그러나 3년간 누적하면 $180~720이며, 동시에 ML 모델 추론 서버나 사내 위키, CI/CD 에이전트 등 부가 서비스를 운영한다면 Synology NAS 한 대로 통합하는 것이 비용과 관리 복잡도 측면에서 우위에 선다.

DSM 7.2 Container Manager의 성숙도: Synology는 DSM 7.2부터 Docker Engine을 Container Manager로 리브랜딩하며 docker-compose v2.x를 완전 지원한다. 이는 단순한 GUI 래퍼가 아니라, YAML 기반 선언적 인프라 구성이 가능한 프로덕션 레벨의 컨테이너 런타임을 의미한다. 더 이상 SSH로 접속해 수동으로 컨테이너를 관리할 필요가 없다.

실제 하드웨어 제약과 극복: DS220+ (Intel Celeron J4025, 2코어) 기준으로도 WordPress + MariaDB + Nginx Proxy Manager 스택은 유휴 상태에서 RAM 1.2GB, CPU 5% 미만을 소비한다. DSM OS 오버헤드 약 600MB를 포함해도 2GB RAM 모델이면 충분히 운영 가능한 수준이다. 단, 플러그인 20개 이상 활성화 시 PHP 메모리 제한을 256MB 이상으로 조정해야 하는데, 이는 후술할 wp-config.php 설정으로 해결된다.


설치 및 설정: Docker Compose로 스택 구성

사전 준비: DSM 설정 체크리스트

Docker 컨테이너를 실행하기 전에 DSM 자체의 네트워크 및 보안 설정을 선행해야 한다. 다음 체크리스트를 순서대로 완료한다.

  • [ ] 제어판로그인 포털 > DSM에서 HTTP/HTTPS 포트를 5000/5001에서 50000/50001로 변경 (80/443 충돌 방지)
  • [ ] 제어판외부 액세스 > DDNS에서 yourdomain.synology.me 등록 및 상태 “정상” 확인
  • [ ] 제어판보안방화벽에서 80/tcp, 443/tcp 인바운드 허용 규칙 생성 (DSM 관리 포트는 특정 IP만 허용)
  • [ ] 공유기 관리 페이지에서 포트포워딩 규칙 추가: 외부 80 → NAS 내부IP:80, 외부 443 → NAS 내부IP:443
  • [ ] 제어판보안인증서에서 Let’s Encrypt 발급 (도메인: 위 DDNS 주소, 주체 대체 이름에 커스텀 도메인 추가 가능)
  • [ ] 패키지 센터에서 Container Manager 설치 및 실행 확인
  • [ ] File Station에서 /volume1/docker/wordpress/db, /volume1/docker/wordpress/html, /volume1/docker/nginx-proxy/data, /volume1/docker/nginx-proxy/letsencrypt 디렉터리 생성

Docker Compose 구성: WordPress + MariaDB + Nginx Proxy Manager

Container Manager의 “프로젝트” 기능을 사용하면 YAML 파일을 GUI에서 직접 편집하고 빌드할 수 있다. 다음은 실제 동작이 검증된 전체 스택 구성이다.

# version 키는 Compose v2.x에서 deprecated — 삭제
services:
  db:
    image: mariadb:10.11
    container_name: wordpress_db
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wp_user
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    volumes:
      - /volume1/docker/wordpress/db:/var/lib/mysql
    networks:
      - wp_network
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 10s
      timeout: 5s
      retries: 5

  wordpress:
    image: wordpress:6-php8.2-apache
    container_name: wordpress_app
    restart: always
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wp_user
      WORDPRESS_DB_PASSWORD: ${MYSQL_PASSWORD}
      WORDPRESS_DB_NAME: wordpress
      WORDPRESS_CONFIG_EXTRA: |
        define('WP_MEMORY_LIMIT', '256M');
        define('WP_MAX_MEMORY_LIMIT', '512M');
        define('WP_POST_REVISIONS', 5);
        define('DISABLE_WP_CRON', true);
    volumes:
      - /volume1/docker/wordpress/html:/var/www/html
    depends_on:
      db:
        condition: service_healthy
    networks:
      - wp_network
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:80/wp-login.php"]
      interval: 30s
      timeout: 10s
      retries: 3

  nginx-proxy:
    image: jc21/nginx-proxy-manager:2.11.3
    container_name: nginx_proxy_manager
    restart: always
    ports:
      - "80:80"
      - "443:443"
      - "81:81"
    volumes:
      - /volume1/docker/nginx-proxy/data:/data
      - /volume1/docker/nginx-proxy/letsencrypt:/etc/letsencrypt
    networks:
      - wp_network
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:81"]
      interval: 30s
      timeout: 10s
      retries: 3

networks:
  wp_network:
    driver: bridge

구성의 핵심 설계 결정:

depends_oncondition: service_healthy를 추가한 것은 실무에서 빈번히 발생하는 DB 초기화 전 WordPress 연결 시도 실패를 방지하기 위함이다. MariaDB 공식 이미지의 healthcheck.sh는 InnoDB가 완전히 초기화된 후에야 정상 상태를 반환하므로, WordPress 컨테이너는 DB가 쿼리를 받을 준비가 완료된 시점에만 시작된다.

DISABLE_WP_CRON을 true로 설정한 것은 WordPress의 내장 크론 시스템이 페이지 로드 시점에 동기적으로 실행되어 응답 지연을 유발하는 문제를 차단하기 위함이다. 대신 DSM의 작업 스케줄러에서 5분 간격으로 curl https://yourdomain.com/wp-cron.php를 호출하도록 설정한다.

WP_POST_REVISIONS를 5로 제한한 것은 데이터베이스 크기 관리를 위한 실무적 선택이다. 포트폴리오 블로그 특성상 글 하나를 수십 회 수정하는 경우가 많지 않지만, 플러그인 테스트 과정에서 의도치 않은 대량 리비전이 쌓이는 것을 방지한다.

환경 변수 관리

Container Manager 프로젝트 생성 시 .env 파일을 동일 디렉터리에 배치하거나, GUI의 “환경 변수” 탭에서 직접 입력할 수 있다. .env 파일 예시:

MYSQL_ROOT_PASSWORD=ChangeMeRoot2025!
MYSQL_PASSWORD=ChangeMeUser2025!

운영 환경에서는 반드시 16자 이상의 무작위 문자열을 사용하며, Hyper Backup으로 /volume1/docker/ 전체를 주기적으로 백업할 때 이 파일도 포함된다.


핵심 예제 코드: Nginx Proxy Manager와 Let’s Encrypt 연동

Docker Compose로 컨테이너가 모두 실행된 후, Nginx Proxy Manager의 관리자 UI(http://NAS_IP:81)에 접속한다. 초기 계정은 admin@example.com / changeme이며, 최초 로그인 시 즉시 변경해야 한다.

프록시 호스트 생성 절차:

  1. SSL 인증서 발급: “SSL Certificates” 메뉴에서 “Add SSL Certificate” → “Let’s Encrypt” 선택. 도메인에 yourdomain.synology.mewww.yourdomain.synology.me를 모두 입력하고 “Use a DNS Challenge”는 비활성화(HTTP-01 챌린지 사용). 이메일은 인증서 만료 알림 수신용으로 유효한 주소 입력.

  2. 프록시 호스트 설정: “Hosts” → “Add Proxy Host”에서 다음과 같이 구성:

  3. Domain Names: yourdomain.synology.me
  4. Scheme: http, Forward Hostname: wordpress_app, Forward Port: 80
  5. “Websocket Support” 활성화 (WordPress 블록 에디터의 실시간 협업 기능 대비)
  6. SSL 탭에서 위에서 발급한 인증서 선택, “Force SSL”, “HTTP/2 Support” 활성화

  7. Advanced 탭 설정: WordPress 미디어 업로드 제한 해제를 위해 다음 커스텀 설정 추가:
    client_max_body_size 128M;
    proxy_read_timeout 300s;
    proxy_connect_timeout 300s;

HTTP-01 챌린지의 작동 원리와 실패 대응: Let’s Encrypt 인증서 발급 시 Nginx Proxy Manager는 .well-known/acme-challenge/ 경로에 임시 토큰 파일을 생성한다. Let’s Encrypt 서버가 http://yourdomain.synology.me/.well-known/acme-challenge/{token}에 접근해 토큰을 검증하는 방식이다. 이 과정에서 공유기의 80번 포트 포워딩이 NAS로 정확히 향하지 않거나, ISP에서 80번 인바운드를 차단하는 경우(국내 일부 통신사) 발급이 실패한다. 이 경우 DNS Challenge로 전환하거나, DSM 자체의 Let’s Encrypt 발급 기능(제어판보안인증서)을 대신 사용한 후 발급된 인증서를 Nginx Proxy Manager로 수동 이전하는 우회 전략을 취한다.


실무 패턴: 퍼미션, 백업, 모니터링

UID/GID 충돌 해결

WordPress 공식 이미지(wordpress:6-php8.2-apache)의 Apache는 www-data 사용자(UID 82, GID 82)로 실행된다. 반면 Synology의 공유 폴더 /volume1/docker/wordpress/html은 기본적으로 admin(UID 1024) 또는 시스템 내부 사용자 소유다. 이 불일치로 인해 WordPress가 플러그인 설치, 미디어 업로드, wp-config.php 쓰기 작업을 수행할 수 없는 문제가 발생한다.

실무 해결책 – 호스트 측 권한 조정:

# SSH로 NAS 접속 후 실행
sudo chown -R 82:82 /volume1/docker/wordpress/html
sudo chmod -R 755 /volume1/docker/wordpress/html
sudo chmod -R 775 /volume1/docker/wordpress/html/wp-content

이 명령은 호스트 디렉터리의 소유권을 컨테이너 내부 UID와 일치시키며, wp-content 디렉터리에만 쓰기 권한을 부여해 보안을 강화한다. chown 시 Synology DSM 사용자 인터페이스에는 “소유자 없음”으로 표시될 수 있으나, 컨테이너 동작에는 영향을 주지 않는다.

대안: linuxserver/wordpress 이미지 사용: linuxserver 팀이 유지보수하는 이미지는 PUIDPGID 환경 변수를 지원한다. PUID=1026, PGID=100(Synology의 기본 Docker 사용자)을 설정하면 호스트 디렉터리 권한을 변경하지 않고도 쓰기가 가능하다. 다만, 이 이미지는 공식 이미지보다 업데이트가 다소 느리므로 보안 패치 적용 시점을 확인해야 한다.

증분 백업 전략

Synology Hyper Backup은 DSM 7.x의 핵심 백업 도구로, 다음과 같은 다중 대상 백업을 지원한다.

로컬 백업 태스크 생성:
– 백업 대상: /volume1/docker/wordpress/ 전체
– 일정: 매일 오전 3시, 증분 백업
– 보존 정책: 30일간의 버전 유지 (스토리지 효율을 위해 30일 이전 버전은 자동 병합)
– 애플리케이션 인식 백업: MariaDB 데이터의 일관성을 위해 백업 전 mysqldump 실행 스크립트를 DSM 작업 스케줄러에 등록

원격 백업:
– Hyper Backup의 “원격 NAS 장치” 또는 “Google Drive” 대상으로 동일 태스크 복제
– Google Drive의 경우 15GB 무료 용량 한계를 고려해 wp-content/uploads만 선별 백업하거나, 중요 설정 파일(wp-config.php, .htaccess)과 DB 덤프만 원격으로 전송

복원 테스트: 분기별 1회 이상, 별도 디렉터리에 백업을 복원하고 Docker Compose로 임시 컨테이너를 실행해 사이트가 정상 동작하는지 확인한다. 백업이 존재한다는 사실과 복원 가능하다는 사실은 별개의 문제다.

리소스 모니터링과 알림

DSM의 “리소스 모니터”에서 Docker 컨테이너별 CPU/RAM 사용량을 실시간 확인할 수 있으나, 시계열 데이터 축적과 알림 기능이 부족하다. 실무에서는 다음과 같은 보완책을 적용한다.

컨테이너 상태 알림: Container Manager의 “알림” 설정에서 컨테이너 중지 이벤트를 DSM 데스크톱 알림 및 이메일로 전송하도록 구성. restart: always 정책으로 자동 복구되더라도, 반복적인 재시작은 근본 원인 분석이 필요하다는 신호다.

WordPress 내부 모니터링: WP Crontrol 플러그인으로 크론 이벤트 실행 상태를 점검하고, Query Monitor 플러그인으로 느린 쿼리와 PHP 오류를 실시간 추적한다. 데이터베이스 크기가 500MB를 초과하면 WP-Optimize로 정기적인 최적화를 수행한다.


주의사항: 포트 충돌, 보안, 성능 최적화

포트 80/443 충돌 트러블슈팅

DSM은 nginx를 내장 웹 서버로 사용하며, 기본적으로 80/443 포트를 점유한다. Docker 컨테이너가 호스트의 80/443에 바인딩하려면 이 충돌을 해결해야 한다.

증상: Container Manager에서 포트 매핑 오류 “address already in use” 발생.

진단 명령어 (SSH):

sudo netstat -tulpn | grep -E ':80|:443'

출력에서 nginx 프로세스가 해당 포트를 점유하고 있음을 확인할 수 있다.

해결 절차:
1. 제어판로그인 포털 > DSM에서 HTTP를 50000, HTTPS를 50001로 변경
2. 제어판로그인 포털응용 프로그램에서 Web Station, Photo Station 등 추가 서비스의 포트도 80/443이 아닌지 확인
3. 변경 후 sudo nginx -s reload 또는 DSM 재시작
4. Container Manager에서 프로젝트 재배포

만약 DSM 7.2에서 “로그인 포털” 설정 변경 후에도 80/443이 해제되지 않는다면, Web Station 패키지가 설치되어 있는지 확인한다. Web Station은 자체 가상 호스트 기능을 위해 80/443을 점유하므로, 사용하지 않는다면 패키지 센터에서

제거하거나, 내부 가상 호스트 설정을 통해 포트를 변경해야 충돌을 완벽히 해결할 수 있다.

마치며

시놀로지 NAS에서 Docker를 활용한 WordPress 운영은 높은 자유도를 제공하지만, 백업 검증, 리소스 모니터링, 그리고 포트 충돌과 같은 NAS 특유의 인프라 이슈를 사전에 제어해야만 안정적인 서비스가 가능합니다. 특히 ‘백업의 존재’와 ‘복원 가능성’은 엄연히 다른 문제이며, 컨테이너의 반복적인 재시작이나 포트 충돌은 단순 오류가 아닌 근본적인 원인 분석이 필요한 신호임을 명심해야 합니다. 실무에서는 이러한 잠재적 장애물을 선제적으로 관리하고 지속적으로 모니터링하는 것이 곧 서비스의 신뢰도로 이어집니다. 철저한 사전 점검과 최적화를 통해 개인 서버 환경에서도 엔터프라이즈급 안정성을 확보하시길 바랍니다.

참고 자료

  • Synology Container Manager 공식 가이드
  • Docker Compose 재시작 정책 및 네트워크 문서
  • WordPress Query

댓글 남기기