워드프레스 블로그 구축 2편, 워드프레스 도커로 설치하고 Nginx Proxy로 SSL 적용하기

워드프레스 블로그 구축 과정중 핵심인 오라클 클라우드 프리티어 VM에 워드프레스와 MariaDB를 도커 컨테이너로 설치하고, Nginx Proxy와 ACME Companion을 이용해 무료 SSL 인증서까지 자동으로 적용하는 과정을 정리했습니다. 보안 규칙에서 80·443 포트를 열고, docker-compose.yml로 워드프레스·DB·프록시 컨테이너를 구성한 뒤, 도메인 연결 전에 공인 IP로 접속했을 때 발생하는 문제와 해결 방법까지 함께 다룹니다. 전체 흐름만 빠르게 확인하고 싶다면 아래 목차를 참고하시면 됩니다.

이 글은 워드프레스 블로그를 구축 과정 시리즈 글의 두번째 워드프레스 설치에 관한 글입니다.

  1. 오라클 클라우드 VM 생성
  2. 워드프레스 설치
  3. 가비아에서 도메인 구매하고 도메인 연결하기
  4. 워드프레스 테마와 필수 플러그인

워드프레스 블로그 구축 쉽게하는 HTTPS 플로우 이해

브라우저에서 워드프레스까지 요청이 흘러가는 경로는 다음과 같습니다.

순서 구간 포트 역할
1 브라우저 → 서버 443 HTTPS 요청 수신
2 Nginx Proxy 요청을 워드프레스 컨테이너로 라우팅
3 SSL/TLS 처리 ACME Companion이 발급한 인증서로 암호화
4 Nginx Proxy → 워드프레스 80 내부적으로 워드프레스 컨테이너에 전달

이 글에서는 워드프레스를 먼저 설치하고, 이어서 Nginx Proxy를 설치해 인증서를 세팅하는 순서로 진행합니다.

오라클 콘솔에서 보안 포트 열기

오라클 클라우드 콘솔의 가상 클라우드 네트워크(VCN) 메뉴에서 보안 규칙으로 들어가 인그레스 규칙을 추가하면 됩니다. 소스 CIDR을 0.0.0.0/0으로 두고, 포트 80(HTTP)과 443(HTTPS)을 각각 추가하면 끝납니다.

워드프레스 블로그 구축, 보안 규칙에서 포트 오픈 작성 방법

워드프레스 블로그 구축, 보안 규칙에서 포트 오픈한 목록

포트가 실제로 열렸는지는 로컬 맥북에서 nc 명령어로 확인할 수 있습니다.

nc -zv <서버_공인_IP> 80
nc -zv <서버_공인_IP> 443

이때 Connection refused 메시지가 뜨더라도 걱정하지 않으셔도 됩니다. 포트 자체는 열려 있지만 아직 그 포트에서 응답할 웹서버(워드프레스, nginx)가 실행되지 않았기 때문에 나오는 정상적인 메시지입니다.

워드프레스 블로그 구축 핵심인 워드프레스와 MariaDB 도커로 설치

VM 접속, 도커

로컬 환경이 맥북이라는 가정하에 로컬 맥북에서 전편에서 다운로드한 프라이빗 키가 있는 디렉토리로 이동한 다음, 퍼블릭 IP로 SSH 접속합니다.

cd ~/keys
chmod 400 oracle-vm-key.pem
ssh -i oracle-vm-key.pem ubuntu@<서버_공인_IP>

패키지 관리자를 업데이트하고 도커가 없으면 install 하세요.

sudo apt update
sudo apt upgrade -y
docker --version

Docker 프로세스는 기본적으로 루트(root) 권한으로 실행되기 때문에, 일반 사용자 계정으로 docker 명령어를 실행할 때 접근이 거부됩니다. sudo usermod -aG docker ubuntu 로 일반 사용자 계정을 도커 그룹에 추가 합니다. 터미널을 종료하고 재접속해야 반영됩니다.

unable to get image 'wordpress:latest': permission denied while trying to connect to the docker API at unix:///var/run/docker.sock

워드프레스 디렉토리 및 환경 변수 설정

mkdir ~/wordpress
cd ~/wordpress
vi .env

.env 파일에는 MySQL 접속 정보를 변수로 분리해 비밀번호 등 민감한 값이 docker-compose.yml에 직접 노출되지 않도록 합니다.

MYSQL_ROOT_PASSWORD=[비밀번호 입력]
MYSQL_DATABASE=wordpress
MYSQL_USER=wordpress
MYSQL_PASSWORD=[비밀번호 입력]

docker-compose.yml 작성

vi docker-compose.yml
services:

  db:
    image: mariadb:11
    restart: always
    env_file:
      - .env
    volumes:
      - db_data:/var/lib/mysql
    networks:
      - proxy-network


  wordpress:
    image: wordpress:latest
    restart: always
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: ${MYSQL_USER}
      WORDPRESS_DB_PASSWORD: ${MYSQL_PASSWORD}
      WORDPRESS_DB_NAME: ${MYSQL_DATABASE}
      VIRTUAL_HOST: elphygia.com,www.elphygia.com
      LETSENCRYPT_HOST: elphygia.com,www.elphygia.com
      LETSENCRYPT_EMAIL: elphygia@gmail.com
      WORDPRESS_CONFIG_EXTRA: |
        define('CONCATENATE_SCRIPTS', true);
        define('FORCE_SSL_ADMIN', true);
        if (isset($$_SERVER['HTTP_X_FORWARDED_PROTO']) && $$_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
            $$_SERVER['HTTPS'] = 'on';
        }
    volumes:
      - wp_data:/var/www/html

    networks:
      - proxy-network

networks:
  proxy-network:
    external: true

volumes:
  db_data:
  wp_data:

여기서 눈여겨봐야 할 부분은 networks.proxy-network.external: true입니다. 워드프레스 컴포즈 파일은 proxy-network가 이미 존재한다고 가정하고 그 네트워크에 연결만 하려고 합니다. 즉 이 네트워크를 실제로 만들어주는 쪽은 다음에 설치할 Nginx Proxy 컴포즈 파일이어야 합니다.

Nginx Proxy와 ACME Companion으로 SSL 적용하기

Nginx Proxy 설정

mkdir ~/nginx-proxy
cd ~/nginx-proxy
vi docker-compose.yml
services:

  nginx-proxy:
    image: nginxproxy/nginx-proxy
    container_name: nginx-proxy
    restart: always

    ports:
      - "80:80"
      - "443:443"

    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro
      - ./certs:/etc/nginx/certs
      - ./vhost.d:/etc/nginx/vhost.d
      - ./html:/usr/share/nginx/html

    networks:
      proxy-network:
        aliases:
          - elphygia.com
          - www.elphygia.com


  acme-companion:
    image: nginxproxy/acme-companion:latest
    container_name: nginx-acme
    restart: always

    depends_on:
      - nginx-proxy

    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./certs:/etc/nginx/certs
      - ./vhost.d:/etc/nginx/vhost.d
      - ./html:/usr/share/nginx/html
      - ./acme:/etc/acme.sh

    environment:
      DEFAULT_EMAIL: elphygia@gmail.com
      NGINX_PROXY_CONTAINER: nginx-proxy

    networks:
      - proxy-network


networks:
  proxy-network:
    name: proxy-network

이 컴포즈 파일에는 external: true가 없기 때문에, proxy-network라는 이름의 네트워크가 없으면 도커가 자동으로 만들어줍니다. 반대로 워드프레스 쪽 컴포즈 파일은 이 네트워크가 이미 있어야 정상 동작합니다.

컨테이너 실행 순서

두 스택을 올리는 순서가 중요합니다. Nginx Proxy를 먼저 실행해 proxy-network를 생성한 뒤, 워드프레스를 실행해야 합니다.

cd ~/nginx-proxy
docker compose up -d

cd ~/wordpress
docker compose up -d

만약 순서를 반대로 실행해서 network proxy-network declared as external, but could not be found 같은 에러가 발생했다면, 아래 명령어로 네트워크를 직접 만들어준 뒤 다시 docker compose up -d를 실행하면 됩니다.

docker network create proxy-network

도메인 연결 전 공인 IP로 접속했다면

주의할 점이 하나 있습니다. 도메인 연결이 완료되지 않은 상태라도 워드프레스가 설치되면 공인 IP로 바로 접속이 가능합니다. 하지만 이 상태에서 접속해 초기 설정을 진행하면 사이트 URL이 도메인이 아닌 IP 값으로 저장되어, 나중에 MySQL에 직접 들어가 값을 수정해야 하는 번거로움이 생깁니다.

이미 공인 IP로 접속해서 초기 설정을 마쳤다면 아래 SQL로 IP 값을 도메인으로 일괄 치환하시면 됩니다.

docker compose exec db mariadb -u root -p
USE wordpress;

UPDATE wp_options SET option_value = REPLACE(option_value, 'http://<서버_공인_IP>', 'https://elphygia.com') WHERE option_name IN ('siteurl', 'home');
UPDATE wp_options SET option_value = REPLACE(option_value, 'https://<서버_공인_IP>', 'https://elphygia.com') WHERE option_name IN ('siteurl', 'home');

UPDATE wp_posts SET post_content = REPLACE(post_content, 'http://<서버_공인_IP>', 'https://elphygia.com');
UPDATE wp_posts SET post_content = REPLACE(post_content, 'https://<서버_공인_IP>', 'https://elphygia.com');

UPDATE wp_postmeta SET meta_value = REPLACE(meta_value, 'http://<서버_공인_IP>', 'https://elphygia.com');
UPDATE wp_postmeta SET meta_value = REPLACE(meta_value, 'https://<서버_공인_IP>', 'https://elphygia.com');

DNS를 클라우드플레어로 쓰려다 보류한 이유

참고로 DNS 서버는 원래 클라우드플레어를 사용하려고 했습니다. 클라우드플레어를 쓰면 무료 CDN과 자체 SSL, 트래픽 및 공격 방어 기능을 함께 사용할 수 있어 매력적인 선택지입니다. 하지만 이번 환경에서는 클라우드플레어 프록시를 켠 상태에서 워드프레스 글 편집기가 열리지 않는 문제가 있어, 이 글에서는 nginx-proxy와 acme-companion으로 직접 SSL을 처리하는 방식으로 진행했습니다. 원인을 해결하면 별도 글로 다시 다룰 예정입니다.

4. 자주 묻는 질문 (FAQ)

Q. proxy-network를 먼저 만들지 않고 워드프레스 컴포즈 파일을 먼저 실행하면 어떻게 되나요?
A. external: true로 지정된 네트워크가 존재하지 않기 때문에 에러가 발생하며 컨테이너가 올라가지 않습니다. Nginx Proxy를 먼저 실행하거나, docker network create proxy-network 명령어로 네트워크를 미리 만들어두시면 됩니다.

Q. 오라클 클라우드 보안 규칙에서 포트를 열었는데도 접속이 안 됩니다.
A. 보안 규칙(VCN 방화벽)뿐 아니라 VM 내부의 iptables나 ufw 같은 OS 방화벽도 함께 확인하셔야 합니다. 두 곳 모두 80, 443 포트가 열려 있어야 합니다.

Q. 도메인 연결 전에 공인 IP로 워드프레스에 접속해버렸는데 어떻게 하나요?
A. 이미 초기 설정을 마치셨다면 본문의 SQL 스크립트로 wp_options, wp_posts, wp_postmeta 테이블의 IP 값을 도메인으로 치환하시면 정상화됩니다.

Q. 클라우드플레어 대신 nginx-proxy와 acme-companion을 써도 괜찮나요?
A. 가능합니다. Let’s Encrypt 인증서를 자동으로 발급·갱신해주기 때문에 별도의 CDN 없이 SSL만 필요하다면 충분히 실용적인 선택입니다.