이 글은 Hugo + PaperMod 블로그 세팅 시리즈의 두 번째 글이다.

  1. Hugo + PaperMod로 기술 블로그 만들기
  2. NCP 서버에 Hugo 블로그 배포하기 ← 현재 글
  3. 커스텀 도메인 연결과 Let’s Encrypt SSL 설정

배포 구조

[로컬 macOS]                    [NCP Rocky Linux]
content/*.md                    
    ↓ hugo --minify             
public/  ──── rsync ────→  /var/www/blog/  ←── Nginx 서빙

Hugo가 생성한 정적 파일을 rsync로 서버에 전송하고, Nginx가 서빙하는 단순한 구조다. DB도 Node.js도 필요 없다.

1. SSH 키 인증 설정

매번 비밀번호를 입력하는 건 번거롭고, 스크립트 자동화도 불가능하다. SSH 키 인증을 먼저 세팅한다.

키 생성

ssh-keygen -t ed25519 -C "blog-deploy"
  • ed25519: RSA보다 짧고 안전한 알고리즘
  • passphrase: 비워도 되고, 입력하면 macOS Keychain이 기억해준다

서버에 공개키 등록

ssh-copy-id -i ~/.ssh/id_ed25519.pub root@서버IP

이때 마지막으로 서버 비밀번호를 입력한다. 이후로는 비밀번호 없이 접속 가능.

SSH config 설정 (선택)

~/.ssh/config에 아래를 추가하면 ssh ncp-blog만으로 접속할 수 있다:

Host ncp-blog
    HostName 서버IP
    User root
    IdentityFile ~/.ssh/id_ed25519

2. Nginx 설정 파일

Hugo 정적 파일을 서빙하기 위한 Nginx 설정이다.

server {
    listen 80;
    server_name 서버IP;

    root /var/www/blog;
    index index.html;

    # gzip 압축
    gzip on;
    gzip_types text/plain text/css application/json application/javascript
               text/xml application/xml application/xml+rss text/javascript;
    gzip_min_length 1000;

    # 정적 파일 캐싱 (30일)
    location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    # Hugo 정적 사이트 서빙
    location / {
        try_files $uri $uri/ =404;
    }

    # 404 페이지
    error_page 404 /404.html;
    location = /404.html {
        internal;
    }

    # 보안 헤더
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
}

핵심 설정:

  • try_files $uri $uri/ =404: 파일 → 디렉토리(index.html) → 404 순서로 탐색
  • gzip: 텍스트 기반 파일을 압축해서 전송 (대역폭 절약)
  • expires 30d: CSS/JS/이미지를 30일 캐싱 (재방문 시 빠른 로딩)

3. 서버 세팅

SSH로 서버에 접속해서 실행한다.

# Nginx 설치
dnf install -y nginx

# 블로그 디렉토리 생성
mkdir -p /var/www/blog

# Nginx 설정 파일 복사 (로컬에서)
scp deploy/nginx/blog.conf ncp-blog:/etc/nginx/conf.d/blog.conf

# 설정 검사 + 시작
ssh ncp-blog "nginx -t && systemctl enable nginx && systemctl start nginx"

systemctl enable은 서버가 재부팅되어도 Nginx가 자동 시작되게 한다.

4. 배포 스크립트

매번 명령어를 치는 건 번거로우니 스크립트로 만든다.

deploy.sh:

#!/bin/bash
set -e

SERVER_USER="root"
SERVER_HOST="서버IP"
SERVER_PORT="22"
REMOTE_DIR="/var/www/blog"

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$SCRIPT_DIR"

build() {
    echo "=== Hugo 빌드 시작 ==="
    hugo --minify
    echo "=== 빌드 완료 ==="
}

deploy() {
    echo "=== 서버 배포 시작 ==="
    rsync -avz --delete \
        -e "ssh -p ${SERVER_PORT}" \
        public/ \
        "${SERVER_USER}@${SERVER_HOST}:${REMOTE_DIR}/"
    echo "=== 배포 완료 ==="
}

case "${1:-all}" in
    build)   build ;;
    deploy)  deploy ;;
    all)     build && deploy ;;
    *)       echo "사용법: $0 {build|deploy|all}" ; exit 1 ;;
esac

사용법:

chmod +x deploy.sh

./deploy.sh build    # 빌드만
./deploy.sh deploy   # 배포만
./deploy.sh          # 빌드 + 배포

rsync의 --delete 옵션은 서버에서 로컬에 없는 파일을 삭제한다. 글을 지웠을 때 서버에도 반영되게 하려면 필요하다.

5. NCP ACG (방화벽) 설정

서버에서 curl localhost는 되는데 외부에서 접속이 안 된다면, NCP ACG 설정을 확인해야 한다.

NCP 콘솔 → Server → ACG → 인바운드 규칙:

프로토콜포트허용 소스
TCP800.0.0.0/0
TCP4430.0.0.0/0

NCP는 OS 레벨 방화벽(firewalld)과 별개로 ACG라는 네트워크 방화벽을 사용한다. 둘 다 확인해야 한다.

배포 확인

curl -s -o /dev/null -w "HTTP %{http_code}, %{time_total}s" http://서버IP/
# HTTP 200, 0.021s

다음 글

IP 주소로 접속하는 블로그는 아무래도 불편하다. 다음 글에서는 커스텀 도메인을 연결하고 HTTPS를 설정한다.

커스텀 도메인 연결과 Let’s Encrypt SSL 설정