휴고 블로그 서버 구축 가이드 (초보자용)
목차
Hugo 블로그 서버 구축 가이드 (초보자용) #
이 문서는 blog.snsoz.com 서버를 세팅하면서 진행한 작업을 순서대로 정리한 것입니다.
명령어 하나하나에 “이게 왜 필요한지” 주석을 달아뒀으니, 나중에 다시 볼 때 처음부터 헤매지 않도록 만들었습니다.
휴고서버 설치 및 테마 설치 #
Hugo 설치 확인 #
이 서버는 Hugo를 snap으로 설치해서 쓰고 있습니다. 이미 설치되어 있는지 확인하려면:
hugo version
설치가 안 되어 있다면 (extended 버전을 써야 Sass/SCSS 컴파일이 되므로 꼭 extended로):
sudo snap install hugo --channel=extended
빌드 스크립트 만들기 #
매번 손으로 명령어 여러 개 치지 않도록, 빌드 과정을 스크립트 하나로 묶어뒀습니다.
sudo nano /usr/local/bin/hugo.sh
#!/bin/bash
# 빌드 결과를 화면에도 보여주고, 로그 파일에도 같이 남긴다 (나중에 로그 대시보드에서 확인하기 위함)
LOGFILE=/home/master/hugo-build.log
{
echo "===== Build started: $(date '+%Y-%m-%d %H:%M:%S') ====="
# public/resources를 매번 지우고 새로 빌드해야, 옛날 CSS/이미지 캐시 때문에
# "분명 고쳤는데 안 바뀐다" 같은 문제가 안 생긴다
rm -rf ~/blog.snsoz.com/public ~/blog.snsoz.com/resources
cd ~/blog.snsoz.com
hugo
# nginx(웹서버)가 파일을 읽을 수 있도록 소유권/권한 정리
sudo chown -R master:www-data ~/blog.snsoz.com/public
chmod -R 755 ~/blog.snsoz.com/public
echo "✅ Hugo build complete. $(date '+%Y-%m-%d %H:%M:%S')"
} 2>&1 | tee -a "$LOGFILE"
이후로는 파일 고치고 나서 이 한 줄만 실행하면 됩니다:
/usr/local/bin/hugo.sh
테마: FixIt → Congo로 교체 #
원래 FixIt 테마를 쓰고 있었는데, 라이트/다크 모드 전환 시 코드블록 색상이 깨지는 버그를 겪은 뒤
Congo 테마로 교체했습니다. Congo는 설정 파일이 하나(hugo.toml)가 아니라
config/_default/ 폴더 안에 여러 개로 나뉘어 있는 게 특징입니다.
cd ~/blog.snsoz.com
# 기존 테마 삭제
rm -rf themes/FixIt
# Congo 설치 (git submodule 방식 — 테마 자체를 별도 git 저장소로 관리)
git submodule add -b stable https://github.com/jpanther/congo.git themes/congo
git submodule update --init --recursive
테마 설정 파일은 Congo가 제공하는 예제(exampleSite)를 복사해서 시작하는 게 제일 안전합니다.
직접 손으로 하나하나 만들면 실수하기 쉽기 때문입니다.
mkdir -p config/_default
cp -r themes/congo/exampleSite/config/_default/* config/_default/
그 다음, 그 안의 파일들(hugo.toml, markup.toml, languages.ko.toml, menus.ko.toml, params.toml)을
한글 사이트에 맞게 수정했습니다. 핵심 변경 포인트:
languages.en.toml→languages.ko.toml로 이름 변경 (다른 언어 파일들은 삭제)markup.toml에서 코드 하이라이트 스타일을dracula로,noClasses = true로 설정 (noClasses = true로 해야 색상이 CSS 클래스가 아니라 코드에 직접 박혀서, 테마 CSS와 충돌해 글자가 안 보이는 사고를 막을 수 있음)params.toml에서defaultAppearance = "dark",autoSwitchAppearance = false로 고정 (라이트 모드일 때 코드블록 필터가 충돌하는 문제를 원천 차단하기 위함)
FixIt 전용 기능을 Congo에서도 쓰게 만들기 #
FixIt에서 쓰던 {{< admonition >}}, {{< image >}} 같은 shortcode는 Congo엔 없어서,
글 내용을 안 고치고도 그대로 쓸 수 있게 shortcode 파일을 새로 만들어줬습니다.
mkdir -p ~/blog.snsoz.com/layouts/shortcodes
nano ~/blog.snsoz.com/layouts/shortcodes/admonition.html
이런 식으로, “테마 원본 파일은 안 건드리고 내 사이트 폴더 안에 같은 이름의 파일을 만들면 그게 우선 적용된다"는 Hugo의 오버라이드(override) 원리를 계속 활용했습니다. 이 방식의 장점은, 나중에 Congo 테마를 업데이트해도 내가 만든 파일은 그대로 남아있다는 것입니다.
대표이미지(썸네일) 규칙 #
Congo는 글 폴더 안에 파일명에 feature, cover, thumb가 들어간 이미지가 있으면
자동으로 그 글의 대표이미지로 인식합니다. 따로 설정할 필요가 없습니다.
content/posts/글제목/
├─ index.md ← 본문에는 feature.png를 다시 언급하지 않음 (중복 표시 방지)
├─ feature.png ← 자동으로 상단 대표이미지로 사용됨
└─ mail-tester.png ← 본문 중간에 {{< figure src="mail-tester.png" >}}로 직접 넣는 이미지
이미지 클릭하면 원본 크게 보기 (라이트박스) #
외부 라이브러리 없이 순수 CSS/HTML만으로 만들었습니다. 원리는 간단합니다 —
체크박스(<input type="checkbox">)를 숨겨두고, 이미지를 <label>로 감싸서
클릭하면 체크박스가 켜지고, 그 상태에 따라 CSS로 확대 화면을 보여줬다 숨겼다 하는 방식입니다.
nano ~/blog.snsoz.com/layouts/shortcodes/figure.html
대표이미지(자동으로 뜨는 이미지)는 shortcode를 거치지 않는 별도 영역이라, 자바스크립트를 조금 추가해서 똑같이 클릭 확대가 되도록 만들었습니다.
nano ~/blog.snsoz.com/layouts/partials/extend-footer.html
참고: Congo 테마 자체는
layouts/_partials/(언더스코어 있음)라는 최신 방식을 쓰는데, 처음에 실수로layouts/partials/(언더스코어 없음)에 파일을 만들었다가 나중에 통일했습니다. 둘 다 작동은 하지만, 섞어 쓰면 헷갈리니 한쪽으로 통일하는 게 좋습니다.
files.snsoz.com — File Browser 세팅 #
SSH로 nano 열어서 파일 고치는 대신, 브라우저에서 파일을 보고 수정할 수 있게
File Browser라는 오픈소스 프로그램을 설치했습니다.
설치 #
# 공식 설치 스크립트 실행 (단일 실행파일이 /usr/local/bin에 설치됨)
curl -fsSL https://raw.githubusercontent.com/filebrowser/get/master/get.sh | bash
서비스로 등록 (서버 재부팅해도 자동 실행되도록) #
sudo nano /etc/systemd/system/filebrowser.service
[Unit]
Description=File Browser
After=network.target
[Service]
User=master
# -r / : 탐색 시작 폴더 (전체 서버를 다 보이게 하려면 /, 특정 폴더만 보이게 하려면 그 경로)
# -a 127.0.0.1 : 외부에서 직접 접근 못 하게, 이 서버 내부에서만 열리게 함 (안전장치)
# -p 8080 : 내부적으로 쓰는 포트 번호
ExecStart=/usr/local/bin/filebrowser -r / -a 127.0.0.1 -p 8080 -d /home/master/filebrowser.db
Restart=always
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now filebrowser
외부에서 접속되게 nginx 서브도메인 연결 #
-a 127.0.0.1로 막아뒀기 때문에, 외부에서 접속하려면 nginx가 중간에서
https://files.snsoz.com 요청을 받아 내부 8080번 포트로 전달(proxy)해줘야 합니다.
sudo nano /etc/nginx/sites-available/files.snsoz.com
server {
listen 80;
server_name files.snsoz.com;
location / {
# 이 서버로 들어온 요청을 그대로 내부 8080 포트(File Browser)로 전달
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# 심볼릭 링크로 활성화
sudo ln -s /etc/nginx/sites-available/files.snsoz.com /etc/nginx/sites-enabled/
# 문법 오류 있는지 먼저 검사 (틀린 설정으로 nginx 껐다 켜면 사이트 전체가 죽을 수 있어서 꼭 먼저 확인)
sudo nginx -t
sudo systemctl reload nginx
HTTPS(SSL) 인증서 발급 — certbot이 자동으로 다 해줌 #
# DNS(files.snsoz.com → 이 서버 IP)가 먼저 연결되어 있어야 성공함
sudo certbot --nginx -d files.snsoz.com
이 명령 하나로 인증서 발급 + nginx 설정에 HTTPS 블록 자동 추가 + 90일마다 자동 갱신 예약까지 다 됩니다.
⚠️ 주의: 이 도구는 휴지통이 없음 #
File Browser는 파일을 지우면 복구 불가능하게 즉시 영구삭제됩니다.
실수 방지를 위해, 필요하면 탐색 범위를 / 대신 /home/master처럼 좁혀서
시스템 핵심 폴더(/etc, /root)를 실수로 못 건드리게 막아두는 것도 방법입니다.
status.snsoz.com — 로그 대시보드 (다크/라이트 토글까지) #
서버에 문제가 생겼을 때 SSH로 로그 파일을 하나하나 찾아 들어가는 대신, 브라우저에서 탭 클릭만으로 주요 로그를 바로 볼 수 있는 페이지를 PHP로 만들었습니다.
PHP 내장 서버로 실행 #
sudo mkdir -p /opt/hugodash
sudo chown master:master /opt/hugodash
nano /opt/hugodash/index.php
핵심 구조는 이렇습니다 (전체 코드는 실제 서버의 /opt/hugodash/index.php 참고):
<?php
// 어떤 로그를 보여줄지 목록으로 미리 정의해둠
$logs = [
'nginx_access' => ['label' => 'Nginx 접속 로그', 'type' => 'file', 'path' => '/var/log/nginx/access.log'],
'nginx_error' => ['label' => 'Nginx 에러 로그', 'type' => 'file', 'path' => '/var/log/nginx/error.log'],
'hugo_build' => ['label' => 'Hugo 빌드 로그', 'type' => 'file', 'path' => '/home/master/hugo-build.log'],
'letsencrypt' => ['label' => 'SSL 인증서(Certbot) 로그', 'type' => 'file', 'path' => '/var/log/letsencrypt/letsencrypt.log'],
'nginx_status' => ['label' => 'Nginx 서비스 상태', 'type' => 'journal', 'unit' => 'nginx'],
'filebrowser_status' => ['label' => 'File Browser 서비스 상태', 'type' => 'journal', 'unit' => 'filebrowser'],
];
// 파일 로그는 tail 명령으로 마지막 500줄만, journal 로그는 journalctl로 최근 200줄만 읽음
// (전체를 다 읽으면 느리고, 어차피 최근 기록이 제일 중요하므로)
에러/경고 줄만 눈에 띄게 배경색 반전 #
로그가 몇백 줄씩 쏟아지면 진짜 문제가 되는 줄을 놓치기 쉽습니다.
그래서 error, denied, failed, 5xx 같은 단어/상태코드가 있는 줄은 빨간 배경으로,
warn, 4xx는 주황 배경으로 자동으로 강조되게 정규식으로 처리했습니다.
function highlightLog($text) {
// 에러 흔적이 전혀 없으면 굳이 빈 화면 대신 "이상 없음" 메시지를 보여줌
if (trim($text) === '') {
return '<span class="log-ok">✅ 최근 에러/기록 없음</span>';
}
$text = htmlspecialchars($text); // HTML 특수문자 깨짐 방지
$lines = explode("\n", $text);
foreach ($lines as &$line) {
// error, denied, failed, 5xx 상태코드 → 빨간 배경
if (preg_match('/\b(error|denied|failed|stopped|terminated|crit|fatal)\b/i', $line)
|| preg_match('/" (5\d\d) /', $line)) {
$line = '<span class="log-err">' . $line . '</span>';
// warn, 4xx 상태코드 → 주황 배경
} elseif (preg_match('/\b(warn|warning)\b/i', $line)
|| preg_match('/" (4\d\d) /', $line)) {
$line = '<span class="log-warn">' . $line . '</span>';
}
}
return implode("\n", $lines);
}
다크/라이트 모드 토글 #
CSS 변수(--bg, --text 등)를 :root에 정의해두고, data-theme 속성값에 따라
그 변수 값만 바꿔치기하는 방식으로 구현했습니다. 선택한 테마는 localStorage에 저장해서
새로고침해도 유지됩니다.
<script>
// 페이지 열릴 때, 저장된 테마가 있으면 그걸로, 없으면 dark를 기본값으로 적용
(function() {
var saved = localStorage.getItem('dashTheme') || 'dark';
document.documentElement.setAttribute('data-theme', saved);
})();
function toggleTheme() {
var cur = document.documentElement.getAttribute('data-theme');
var next = cur === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', next);
localStorage.setItem('dashTheme', next); // 다음 방문 때도 기억하도록 저장
}
</script>
systemd 서비스 등록 #
sudo nano /etc/systemd/system/hugodash.service
[Unit]
Description=Hugo Server Log Dashboard
After=network.target
[Service]
User=master
WorkingDirectory=/opt/hugodash
# PHP 내장 웹서버를 8082 포트로 띄움 (내부 전용, 외부는 nginx를 거쳐야 접근 가능)
ExecStart=/usr/bin/php -S 127.0.0.1:8082 -t /opt/hugodash
Restart=always
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now hugodash
nginx 서브도메인 + 비밀번호 보호 #
이 페이지는 서버 IP, 에러 내용 같은 민감한 정보를 보여주기 때문에, File Browser와 달리 로그인 기능이 없어서 nginx 단에서 비밀번호를 걸어야 합니다.
# apache2-utils는 이름만 apache일 뿐, htpasswd라는 비밀번호 생성 도구만 들어있는 패키지.
# 아파치 웹서버 자체가 설치되는 게 아니라서 nginx와 충돌하지 않음
sudo apt install apache2-utils -y
sudo htpasswd -c /etc/nginx/.htpasswd master
server {
listen 80;
server_name status.snsoz.com;
location / {
auth_basic "Restricted"; # 이 두 줄이 로그인 팝업을 띄워줌
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://127.0.0.1:8082;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
sudo ln -s /etc/nginx/sites-available/status.snsoz.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d status.snsoz.com
SSL 로그를 읽으려면 권한을 별도로 열어줘야 함 #
/var/log/letsencrypt/ 폴더는 기본적으로 root만 들어갈 수 있게 잠겨 있어서,
일반 계정(master)으로 도는 PHP가 그 안의 로그를 못 읽습니다.
setfacl로 딱 필요한 만큼만 권한을 열어줬습니다 (폴더 전체를 열지 않고, 파일 하나 + 통과 권한만).
sudo apt install acl -y
# 파일 자체를 읽을 수 있게
sudo setfacl -m u:master:r /var/log/letsencrypt/letsencrypt.log
# 폴더 자체가 잠겨있으면 파일 권한을 열어도 소용없으므로, 폴더를 "통과"할 권한도 열어줌
sudo setfacl -m u:master:x /var/log/letsencrypt/
죽은 SSL 인증서 정리 #
certbot renew --dry-run(실제 갱신은 안 하고 “갱신이 될지"만 미리 확인하는 명령)을 돌려봤더니,
쓰지도 않는 snsox.com 인증서 하나가 계속 갱신 실패로 걸리는 걸 발견했습니다.
원인 파악 순서 #
# 1. 일단 시뮬레이션으로 뭐가 실패하는지 확인
sudo certbot renew --dry-run
# 2. 문제의 인증서가 어떤 도메인들을 포함하고, 언제 만료됐는지 확인
sudo certbot certificates | grep -A3 snsox.com
→ mail.snsox.com이 포함되어 있어서 자칫 메일서버에 영향 있을까 봐 조심스럽게 확인했지만,
**메일서버는 완전히 다른 서버(VM)**이고, 그 서버는 자기만의 인증서(oscrc.com 통합 인증서)로
mail.snsox.com을 이미 정상적으로 관리하고 있다는 걸 메일서버에서 직접 확인했습니다.
# 메일서버 쪽에서 실행 (이 블로그 서버가 아니라 메일서버 VM에서!)
sudo certbot certificates
즉, 이 블로그 서버에 남아있던 snsox.com 인증서 설정은 실제로 아무 서비스도 안 쓰는
옛날 찌꺼기 설정이었다는 게 확인되어, 안전하게 지웠습니다.
정리 명령 #
sudo certbot delete --cert-name snsox.com
최종 확인 #
sudo certbot renew --dry-run
→ blog.snsox.com, blog.snsoz.com, files.snsoz.com, status.snsoz.com 4개 모두
success로 뜨고, 실패 항목이 하나도 없으면 완전히 정리된 것입니다.
오늘 한 일 요약 #
| 항목 | 내용 |
|---|---|
| 테마 | FixIt → Congo로 교체 (다크모드 고정, dracula 코드하이라이트, shortcode 오버라이드) |
| 이미지 | 대표이미지 자동 인식, 클릭 시 원본 확대(라이트박스) |
| files.snsoz.com | File Browser 설치, nginx 연결, SSL 발급 |
| status.snsoz.com | PHP 로그 대시보드, 에러/경고 하이라이트, 다크/라이트 토글, 비밀번호 보호 |
| SSL 정리 | 안 쓰는 snsox.com 인증서 삭제, 전체 인증서 갱신 정상화 확인 |
핵심 원칙 하나만 기억하기: 테마나 프로그램의 원본 파일은 되도록 안 건드리고, 내 사이트 폴더 안에 같은 이름/경로로 파일을 만들어서 “오버라이드"하는 방식을 쓰면, 나중에 테마를 업데이트해도 내가 한 작업이 사라지지 않습니다.