방치된 구형 노트북 한 대가 24/7 운영 가능한 개인 서버로 탈바꿈할 수 있다. Ubuntu Server 24.04 LTS를 기반으로 unattended-upgrades 자동 보안 패치, fail2ban 브루트포스 차단, UFW 계층적 방화벽, Docker Compose 멀티 서비스 오케스트레이션, Portainer 웹 UI, Watchtower 컨테이너 자동 업데이트, 그리고 주간·월간 자동화 크론탭까지 — 실제 운영 환경에서 즉시 적용 가능한 심화 가이드를 제공한다.

하드웨어 선택 기준 — 전력 효율이 핵심

홈 서버용 노트북의 최소 권장 사양은 Intel Core i5 4세대 이상, RAM 8GB(16GB 권장), SSD 128GB 이상이다. 가장 중요한 선택 기준은 TDP(열 설계 전력)다. 24/7 운영 시 TDP 15W 노트북은 연간 약 131kWh를 소비하고, TDP 65W 데스크톱은 약 569kWh를 소비한다. 전기료만으로도 연간 약 8만 원의 차이가 발생한다. 노트북은 배터리 회로가 UPS 역할도 겸하여, 순간 정전 시 자동 보호가 되는 추가 이점이 있다.

하드웨어TDP연간 소비전력연간 전기료권장 용도
노트북 (저전력)15W131 kWh약 26,000원파일서버·NAS
노트북 (일반)28W245 kWh약 49,000원미디어서버
미니 PC35W307 kWh약 61,000원범용서버
데스크톱 PC65W+569 kWh+약 114,000원+GPU 작업

Ubuntu Server 24.04 LTS 설치 및 초기 보안 강화

Ubuntu Server 24.04 LTS를 USB로 설치한 후 가장 먼저 해야 할 것은 덮개 절전 모드 차단, 시스템 업데이트, 그리고 자동 보안 패치 활성화다. unattended-upgrades는 CVE 패치를 사람의 개입 없이 자동으로 적용해 제로데이 노출 기간을 최소화한다.

bash
# ── 1. 덮개 닫기 시 절전 모드 완전 차단 ──────────────────────────────────
sudo sed -i 's/#HandleLidSwitch=suspend/HandleLidSwitch=ignore/' \
  /etc/systemd/logind.conf
sudo sed -i 's/#HandleLidSwitchDocked=ignore/HandleLidSwitchDocked=ignore/' \
  /etc/systemd/logind.conf
sudo systemctl restart systemd-logind

# 결과 확인
loginctl show-session $(loginctl | awk 'NR==2{print $1}') | grep Lid

# ── 2. 시스템 전체 업데이트 및 필수 패키지 설치 ──────────────────────────
sudo apt update && sudo apt full-upgrade -y
sudo apt install -y \
  curl wget git htop iotop nethogs \
  ufw fail2ban unattended-upgrades \
  logwatch mailutils \
  apt-listchanges bsd-mailx

unattended-upgrades 심화 설정

unattended-upgrades의 기본 설정은 보안 패치만 자동 적용한다. 패키지 블랙리스트 지정, 재부팅 일정, 이메일 알림까지 세밀하게 제어할 수 있다.

bash
# 자동 업데이트 활성화 (대화형 설정)
sudo dpkg-reconfigure -plow unattended-upgrades

# /etc/apt/apt.conf.d/50unattended-upgrades 핵심 설정값 확인 및 수정
sudo nano /etc/apt/apt.conf.d/50unattended-upgrades
conf
// /etc/apt/apt.conf.d/50unattended-upgrades

Unattended-Upgrade::Allowed-Origins {
    "${distro_id}:${distro_codename}-security";
    "${distro_id}ESMApps:${distro_codename}-apps-security";
    "${distro_id}ESM:${distro_codename}-infra-security";
};

// 커널 업그레이드 포함 (재부팅 필요)
Unattended-Upgrade::Package-Blacklist {
    // 특정 패키지 자동 업데이트 제외 예시
    // "linux-image-*";
    // "postgresql-*";
};

// 패치 후 필요 시 자동 재부팅 (심야 02:00)
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "02:00";

// 이메일 알림 (sendmail/postfix 필요)
Unattended-Upgrade::Mail "admin@yourdomain.com";
Unattended-Upgrade::MailReport "on-change";

// 오래된 의존성 자동 제거
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Remove-New-Unused-Dependencies "true";

// 로그 상세도 증가
Unattended-Upgrade::Verbose "false";
Unattended-Upgrade::Debug "false";
bash
# /etc/apt/apt.conf.d/20auto-upgrades — 주기 설정
sudo tee /etc/apt/apt.conf.d/20auto-upgrades > /dev/null << 'EOF'
APT::Periodic::Update-Package-Lists "1";        // 매일 패키지 목록 갱신
APT::Periodic::Download-Upgradeable-Packages "1"; // 매일 패키지 다운로드
APT::Periodic::AutocleanInterval "7";           // 7일마다 캐시 정리
APT::Periodic::Unattended-Upgrade "1";          // 매일 자동 업그레이드 실행
EOF

# 즉시 테스트 실행 (dry-run)
sudo unattended-upgrade --dry-run --debug

# 실제 적용 로그 확인
sudo cat /var/log/unattended-upgrades/unattended-upgrades.log

SSH 보안 강화 — 공개키 전용 인증

기본 SSH 설정은 비밀번호 인증을 허용하여 브루트포스 공격에 취약하다. Ed25519 키 쌍을 사용한 공개키 전용 인증으로 전환하고, 포트 변경 및 루트 로그인을 완전히 차단해야 한다. Ed25519는 RSA-4096보다 서명 속도가 빠르고 키 크기가 작으면서 동등한 보안 강도를 제공한다.

bash
# ── [로컬 머신] Ed25519 키 쌍 생성 ─────────────────────────────────────────
# -C 코멘트에 날짜와 용도를 기록해두면 나중에 관리가 용이함
ssh-keygen -t ed25519 -C "homeserver-$(date +%Y%m%d)" -f ~/.ssh/homeserver_ed25519

# [로컬 머신] 공개키를 서버에 복사
ssh-copy-id -i ~/.ssh/homeserver_ed25519.pub -p 22 user@192.168.1.xxx

# [로컬 머신] ~/.ssh/config에 별칭 추가
cat >> ~/.ssh/config << 'EOF'
Host homeserver
    HostName 192.168.1.xxx
    User user
    Port 2222
    IdentityFile ~/.ssh/homeserver_ed25519
    ServerAliveInterval 60
    ServerAliveCountMax 3
EOF

# 이제 ssh homeserver 만으로 접속 가능
bash
# ── [서버] /etc/ssh/sshd_config 강화 설정 ──────────────────────────────────
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak  # 백업 필수

sudo tee /etc/ssh/sshd_config.d/99-hardening.conf > /dev/null << 'EOF'
# 비표준 포트로 변경 (자동화 스캐너 차단 효과)
Port 2222

# 인증 제한
PasswordAuthentication no
PermitRootLogin no
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
PermitEmptyPasswords no

# 연결 제한
MaxAuthTries 3
MaxSessions 5
LoginGraceTime 30

# 프로토콜 보안
Protocol 2
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding no   # 포트 포워딩 비허용 (Tailscale 사용 시)

# 알고리즘 명시 (약한 알고리즘 차단)
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com

# 로그 상세도 높임
LogLevel VERBOSE
EOF

# 설정 문법 검사 (반드시 확인 후 재시작)
sudo sshd -t && echo "설정 OK" || echo "설정 오류 있음"

# SSH 데몬 재시작 (기존 연결 유지)
sudo systemctl reload ssh

# 새 포트로 연결 확인 후 기존 세션 유지
ssh -p 2222 user@192.168.1.xxx "echo 접속성공"

fail2ban — SSH 무차별 대입 공격 방어

fail2ban은 로그 파일을 실시간으로 모니터링하여 지정된 횟수 이상 인증에 실패한 IP를 iptables/nftables로 자동 차단한다. 기본 jail.conf는 직접 수정하지 않고, jail.local에 오버라이드 설정을 작성해야 패키지 업데이트 시 설정이 보존된다.

bash
# /etc/fail2ban/jail.local 생성 (jail.conf는 절대 직접 수정 금지)
sudo tee /etc/fail2ban/jail.local > /dev/null << 'EOF'
[DEFAULT]
# 차단 시간: 1시간 (음수 = 영구 차단)
bantime  = 3600
# 감시 윈도우: 10분 내
findtime  = 600
# 허용 실패 횟수
maxretry = 5
# 화이트리스트 (로컬 네트워크는 차단 제외)
ignoreip = 127.0.0.1/8 ::1 192.168.1.0/24
# 백엔드: systemd 저널 사용
backend = systemd
# 차단 시 이메일 알림 (sendmail 설정 필요)
destemail = admin@yourdomain.com
sender    = fail2ban@yourdomain.com
mta       = sendmail
action    = %(action_mwl)s   # 차단 + 이메일 + 로그 포함

[sshd]
enabled  = true
port     = 2222              # 변경한 SSH 포트에 맞게 조정
filter   = sshd
logpath  = %(sshd_log)s
maxretry = 3                 # SSH는 더 엄격하게 3회
bantime  = 86400             # 24시간 차단

[sshd-ddos]
enabled  = true
port     = 2222
filter   = sshd-ddos
logpath  = %(sshd_log)s
maxretry = 10
findtime = 60
bantime  = 3600

# Docker 관련 포트 보호 (Portainer 등)
[portainer]
enabled  = true
port     = 9000
filter   = apache-auth        # 로그인 실패 필터 재활용
logpath  = /var/log/syslog
maxretry = 5
bantime  = 1800
EOF

# fail2ban 서비스 활성화 및 시작
sudo systemctl enable --now fail2ban

# 상태 확인
sudo fail2ban-client status
sudo fail2ban-client status sshd

# 특정 IP 수동 차단 테스트 및 해제
sudo fail2ban-client set sshd banip 1.2.3.4
sudo fail2ban-client set sshd unbanip 1.2.3.4

# 차단된 IP 목록 확인
sudo fail2ban-client status sshd | grep "Banned IP"
bash
# fail2ban 커스텀 필터 예시 — 반복 포트 스캔 차단
sudo tee /etc/fail2ban/filter.d/portscan.conf > /dev/null << 'EOF'
[Definition]
failregex = \[UFW BLOCK\] .* SRC=
ignoreregex =
EOF

# 필터 테스트
sudo fail2ban-regex /var/log/ufw.log /etc/fail2ban/filter.d/portscan.conf

# jail.local에 portscan jail 추가
sudo tee -a /etc/fail2ban/jail.local > /dev/null << 'EOF'

[portscan]
enabled  = true
filter   = portscan
logpath  = /var/log/ufw.log
maxretry = 10
findtime = 60
bantime  = 3600
EOF

sudo systemctl restart fail2ban

UFW 방화벽 — 계층적 규칙과 로깅 활성화

UFW(Uncomplicated Firewall)는 iptables/nftables의 프론트엔드로, 규칙 순서가 중요하다. allow before deny 원칙에 따라 화이트리스트 규칙을 먼저 추가하고, 기본 정책을 deny로 설정한다. 로깅을 활성화하면 fail2ban과 logwatch가 차단 이벤트를 수집해 분석할 수 있다.

bash
# ── 기본 정책 설정 ───────────────────────────────────────────────────────────
sudo ufw default deny incoming    # 모든 인바운드 차단
sudo ufw default allow outgoing   # 모든 아웃바운드 허용
sudo ufw default deny forward     # 포워딩 차단 (Subnet Router 미사용 시)

# ── 서비스별 포트 개방 (주석으로 용도 명확히 기록) ─────────────────────────
# SSH (변경한 포트)
sudo ufw allow 2222/tcp comment 'SSH 원격 접속'

# Docker 웹 UI — Portainer
sudo ufw allow 9000/tcp comment 'Portainer 웹 UI'

# 미디어 서버 — Jellyfin
sudo ufw allow 8096/tcp comment 'Jellyfin HTTP'
sudo ufw allow 8920/tcp comment 'Jellyfin HTTPS'

# 파일 공유 — Samba (로컬 네트워크만 허용)
sudo ufw allow from 192.168.1.0/24 to any port 445 proto tcp comment 'Samba 로컬 전용'
sudo ufw allow from 192.168.1.0/24 to any port 137,138 proto udp comment 'Samba NetBIOS 로컬'

# Wake-on-LAN (UDP 브로드캐스트)
sudo ufw allow 9/udp comment 'Wake-on-LAN'

# Tailscale WireGuard 포트
sudo ufw allow 41641/udp comment 'Tailscale WireGuard'

# ── 로깅 활성화 ─────────────────────────────────────────────────────────────
# low: 차단된 패킷만 / medium: 차단+허용 / full: 전체 (디스크 주의)
sudo ufw logging medium

# ── UFW 활성화 ──────────────────────────────────────────────────────────────
sudo ufw enable

# ── 규칙 검증 ───────────────────────────────────────────────────────────────
sudo ufw status verbose
sudo ufw status numbered   # 번호로 확인 (삭제 시 ufw delete [번호])

# 로그 실시간 모니터링
sudo tail -f /var/log/ufw.log | grep -E "BLOCK|ALLOW"
bash
# Docker가 UFW를 우회하는 문제 방지 (중요!)
# Docker는 기본적으로 iptables를 직접 조작해 UFW 규칙을 우회함
# /etc/docker/daemon.json 에 다음 설정 추가
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json > /dev/null << 'EOF'
{
  "iptables": false,
  "ip6tables": false
}
EOF
# 주의: 이 설정 적용 시 Docker 컨테이너 간 통신 라우팅을 수동 설정해야 함
# 대안: ufw-docker 유틸리티 설치
sudo apt install ufw-docker -y
sudo ufw-docker install

logwatch — 매일 이메일 보안 보고서

logwatch는 시스템 로그를 파싱하여 매일 요약 리포트를 이메일로 발송한다. SSH 로그인 시도, 패키지 업데이트 내역, 디스크 사용량 경고 등을 한눈에 파악할 수 있다.

bash
# logwatch 설치
sudo apt install logwatch -y

# /etc/logwatch/conf/logwatch.conf 커스텀 설정
sudo tee /etc/logwatch/conf/logwatch.conf > /dev/null << 'EOF'
# 출력 형식 (html | text | file)
Output = mail
# 수신 이메일
MailTo = admin@yourdomain.com
# 발신 이메일
MailFrom = logwatch@homeserver
# 상세도 (Low | Med | High)
Detail = Med
# 범위 (yesterday | today | all)
Range = yesterday
# 서비스 필터 (All = 전체)
Service = All
# 로그 디렉터리
LogDir = /var/log
EOF

# 즉시 테스트 실행 (stdout으로 출력)
sudo logwatch --output stdout --detail Med --range today

# cron에 등록 (매일 오전 7시 발송)
echo "0 7 * * * root /usr/sbin/logwatch --output mail" | \
  sudo tee /etc/cron.d/logwatch

Wake-on-LAN으로 원격 부팅 구현

WoL 설정을 통해 꺼진 서버를 네트워크로 원격 부팅할 수 있다. Tailscale과 연동하면 외부 네트워크에서도 홈서버를 원격으로 깨울 수 있다. BIOS/UEFI 설정에서 "Wake on LAN" 옵션을 먼저 활성화해야 한다.

bash
# 네트워크 인터페이스 이름 확인
ip link show

# WoL 지원 여부 확인 (g = magic packet 활성화)
sudo ethtool enp3s0 | grep -i wake
# Wake-on: g  ← 이렇게 나와야 정상

# WoL 영구 활성화 (systemd-networkd 방식)
sudo tee /etc/systemd/network/10-wol.link > /dev/null << 'EOF'
[Match]
MACAddress=aa:bb:cc:dd:ee:ff

[Link]
WakeOnLan=magic
EOF

sudo systemctl restart systemd-networkd

# MAC 주소 확인 방법
ip link show enp3s0 | grep "link/ether"

# [원격 머신에서] Magic Packet 전송
sudo apt install wakeonlan
wakeonlan aa:bb:cc:dd:ee:ff

# 브로드캐스트 IP 직접 지정 (서브넷이 다른 경우)
wakeonlan -i 192.168.1.255 aa:bb:cc:dd:ee:ff
"유휴 노트북 한 대가 연간 수백만 원의 클라우드 비용을 대체한다. 초기 투자 0원의 인프라 혁신."

Docker 설치 및 네트워크 브릿지 설정

Docker 공식 레포지토리에서 설치하고, 컨테이너 간 통신을 위한 커스텀 브릿지 네트워크를 생성한다. 브릿지 네트워크를 사용하면 컨테이너 이름으로 DNS 해석이 가능해 IP를 하드코딩할 필요가 없다.

bash
# ── Docker 공식 레포지토리로 설치 ──────────────────────────────────────────
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
  sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

echo "deb [arch=$(dpkg --print-architecture) \
  signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] \
  https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list

sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

# 현재 사용자를 docker 그룹에 추가 (재로그인 필요)
sudo usermod -aG docker $USER

# ── 커스텀 브릿지 네트워크 생성 ─────────────────────────────────────────────
# homelab 네트워크: 172.20.0.0/16 대역 사용
docker network create \
  --driver bridge \
  --subnet 172.20.0.0/16 \
  --gateway 172.20.0.1 \
  --opt "com.docker.network.bridge.name"="br-homelab" \
  homelab

# 네트워크 목록 확인
docker network ls
docker network inspect homelab

Docker Compose로 멀티 서비스 일괄 관리

Docker Compose는 여러 컨테이너 서비스를 단일 YAML 파일로 정의하고 docker compose up -d 한 줄로 전체 스택을 기동한다. restart: unless-stopped 정책은 서버 재부팅 후 컨테이너가 자동으로 기동되도록 보장한다.

bash
# 프로젝트 디렉터리 구조 생성
mkdir -p ~/homelab/{portainer,jellyfin,samba,watchtower}
mkdir -p ~/homelab/jellyfin/{config,cache,media}

# 홈랩 전체 스택 compose 파일 작성
cat > ~/homelab/docker-compose.yml << 'YAML'
version: "3.9"

# 공통 설정 재사용 (YAML 앵커)
x-restart-policy: &restart-policy
  restart: unless-stopped

x-logging: &default-logging
  logging:
    driver: "json-file"
    options:
      max-size: "10m"
      max-file: "3"

networks:
  homelab:
    external: true   # 앞서 생성한 homelab 네트워크 사용

services:

  # ── Portainer CE: Docker 웹 관리 UI ─────────────────────────────────────
  portainer:
    image: portainer/portainer-ce:latest
    container_name: portainer
    <<: *restart-policy
    <<: *default-logging
    security_opt:
      - no-new-privileges:true
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - portainer_data:/data
    ports:
      - "9000:9000"
      - "9443:9443"   # HTTPS
    networks:
      - homelab

  # ── Jellyfin: 오픈소스 미디어 서버 ──────────────────────────────────────
  jellyfin:
    image: jellyfin/jellyfin:latest
    container_name: jellyfin
    <<: *restart-policy
    <<: *default-logging
    user: "1000:1000"   # 호스트 사용자 UID:GID 일치
    environment:
      - JELLYFIN_PublishedServerUrl=http://homeserver.tail12345.ts.net:8096
    volumes:
      - ~/homelab/jellyfin/config:/config
      - ~/homelab/jellyfin/cache:/cache
      - ~/homelab/jellyfin/media:/media:ro
    ports:
      - "8096:8096"
      - "8920:8920"
    devices:
      - /dev/dri:/dev/dri   # Intel QSV 하드웨어 트랜스코딩
    networks:
      - homelab

  # ── Watchtower: 컨테이너 자동 업데이트 ─────────────────────────────────
  watchtower:
    image: containrrr/watchtower:latest
    container_name: watchtower
    <<: *restart-policy
    <<: *default-logging
    environment:
      - WATCHTOWER_CLEANUP=true          # 구버전 이미지 자동 삭제
      - WATCHTOWER_INCLUDE_STOPPED=false # 중지된 컨테이너 제외
      - WATCHTOWER_SCHEDULE=0 0 4 * * *  # 매일 오전 4시 업데이트 확인
      - WATCHTOWER_TIMEOUT=60            # 업데이트 타임아웃 60초
      - WATCHTOWER_NOTIFICATIONS=email
      - WATCHTOWER_NOTIFICATION_EMAIL_FROM=watchtower@homeserver
      - WATCHTOWER_NOTIFICATION_EMAIL_TO=admin@yourdomain.com
      - WATCHTOWER_NOTIFICATION_EMAIL_SERVER=smtp.gmail.com
      - WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT=587
      - WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER=you@gmail.com
      - WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD=app-specific-password
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    networks:
      - homelab

volumes:
  portainer_data:
YAML
bash
# ── 스택 실행 및 관리 명령어 ────────────────────────────────────────────────

# 전체 스택 백그라운드 기동
docker compose -f ~/homelab/docker-compose.yml up -d

# 특정 서비스만 재시작
docker compose -f ~/homelab/docker-compose.yml restart jellyfin

# 서비스 로그 실시간 확인
docker compose -f ~/homelab/docker-compose.yml logs -f jellyfin

# 전체 서비스 상태 확인
docker compose -f ~/homelab/docker-compose.yml ps

# 이미지만 업데이트 (수동)
docker compose -f ~/homelab/docker-compose.yml pull
docker compose -f ~/homelab/docker-compose.yml up -d

# 중지 및 삭제 (볼륨 보존)
docker compose -f ~/homelab/docker-compose.yml down

# 볼륨까지 완전 삭제 (주의: 데이터 손실)
docker compose -f ~/homelab/docker-compose.yml down -v

Portainer CE — 웹 UI 컨테이너 관리

Portainer CE는 Docker 컨테이너, 이미지, 볼륨, 네트워크를 웹 브라우저에서 시각적으로 관리할 수 있는 오픈소스 도구다. 컨테이너별 CPU/메모리 실시간 통계, 로그 스트리밍, 콘솔 접속 기능을 제공한다.

bash
# Portainer가 이미 compose로 실행 중이라면:
# http://192.168.1.xxx:9000 또는 http://homeserver:9000 접속
# 초기 관리자 비밀번호 설정 화면이 나타남 (5분 내 설정 필요)

# 컨테이너별 리소스 통계 CLI로도 확인 가능
docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}"

# Portainer 에이전트 모드로 원격 Docker 호스트도 관리 가능
docker run -d \
  -p 9001:9001 \
  --name portainer_agent \
  --restart=always \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v /var/lib/docker/volumes:/var/lib/docker/volumes \
  portainer/agent:latest

오류 발생 시 AI 도구 활용 — Claude Code CLI

홈서버 운영 중 예상치 못한 오류가 발생했을 때, Claude Code CLI (claude 명령)를 터미널에서 직접 사용하면 설정 파일 분석과 수정을 빠르게 처리할 수 있다. 오류 메시지를 파이프로 넘기거나, 설정 파일을 직접 수정하도록 지시할 수 있다.

bash
# ── 시나리오 1: SSH 접속 불가 ────────────────────────────────────────────────
# sshd 설정 문법 오류가 있을 때
sudo journalctl -u ssh -n 50 | claude "sshd 시작 실패 원인 분석해줘"

# 설정 파일을 직접 분석 요청
claude "이 sshd_config 설정에서 오류를 찾아줘" < /etc/ssh/sshd_config.d/99-hardening.conf

# ── 시나리오 2: Docker 컨테이너 시작 안됨 ──────────────────────────────────
# 컨테이너 로그를 AI에게 분석 요청
docker logs jellyfin 2>&1 | tail -100 | claude "jellyfin 컨테이너 시작 실패 원인 분석해줘"

# 권한 문제 자동 진단
docker inspect jellyfin | claude "컨테이너 설정에서 볼륨 마운트 권한 문제 찾아줘"

# ── 시나리오 3: UFW 규칙 문제 ──────────────────────────────────────────────
sudo ufw status verbose | claude "UFW 규칙에서 8096 포트가 차단되는 이유 분석해줘"

# ── 시나리오 4: 디스크 공간 부족 ──────────────────────────────────────────
du -sh /* 2>/dev/null | sort -hr | head -20 | claude "어느 디렉터리를 정리해야 할지 알려줘"

# ── Claude Code로 설정 파일 직접 수정 ─────────────────────────────────────
# claude가 파일을 직접 읽고 수정 (Claude Code에서만 가능)
claude "fail2ban jail.local에서 maxretry를 5에서 3으로 바꿔줘"
claude "/etc/docker/daemon.json에 log-driver json-file 설정 추가해줘"

시스템 정리 자동화 — 주간·월간 크론탭

크론탭으로 주간·월간 정리 작업을 자동화하면 디스크 공간 부족이나 로그 적체 문제를 사전에 방지할 수 있다. Docker 미사용 이미지와 컨테이너는 지속적으로 쌓이며, 방치 시 수십 GB의 공간을 차지한다.

bash
# ── 자동화 스크립트 작성 ────────────────────────────────────────────────────
sudo tee /usr/local/bin/weekly-cleanup.sh > /dev/null << 'EOF'
#!/bin/bash
LOG="/var/log/weekly-cleanup.log"
echo "=== 주간 정리 시작: $(date) ===" >> "$LOG"

# Docker 미사용 리소스 정리
docker system prune -f --volumes >> "$LOG" 2>&1
echo "Docker 정리 완료" >> "$LOG"

# systemd 저널 7일 이상 된 로그 삭제
journalctl --vacuum-time=7d >> "$LOG" 2>&1

# APT 캐시 및 불필요 패키지 제거
apt-get autoremove -y >> "$LOG" 2>&1
apt-get autoclean >> "$LOG" 2>&1

# 임시 파일 정리
find /tmp -type f -atime +7 -delete 2>/dev/null

echo "=== 주간 정리 완료: $(date) ===" >> "$LOG"
EOF

sudo chmod +x /usr/local/bin/weekly-cleanup.sh

# ── 크론탭 등록 ─────────────────────────────────────────────────────────────
# 형식: 분 시 일 월 요일 명령
sudo tee /etc/cron.d/homeserver-maintenance > /dev/null << 'EOF'
# 매주 일요일 오전 3시 — 주간 정리 (매일 서비스 운영 중 방해 없도록 심야 실행)
0 3 * * 0 root /usr/local/bin/weekly-cleanup.sh

# 매월 1일 오전 2시 — 전체 apt 업그레이드 (unattended-upgrades 보완)
0 2 1 * * root apt-get upgrade -y >> /var/log/monthly-upgrade.log 2>&1

# 매일 오전 6시 — 디스크 사용량 체크 및 알림
0 6 * * * root /usr/local/bin/disk-alert.sh
EOF
bash
# ── 디스크 사용량 90% 초과 시 이메일 알림 스크립트 ────────────────────────
sudo tee /usr/local/bin/disk-alert.sh > /dev/null << 'EOF'
#!/bin/bash
THRESHOLD=90
ADMIN_EMAIL="admin@yourdomain.com"
HOSTNAME=$(hostname)

while IFS= read -r line; do
  USAGE=$(echo "$line" | awk '{print $5}' | sed 's/%//')
  MOUNT=$(echo "$line" | awk '{print $6}')

  if [ "$USAGE" -ge "$THRESHOLD" ]; then
    SUBJECT="[홈서버 경고] ${HOSTNAME} 디스크 ${MOUNT} 사용량 ${USAGE}%"
    BODY="$(date)\n\n디스크 사용량이 임계치를 초과했습니다.\n\n$(df -h)\n\n대용량 디렉터리:\n$(du -sh /* 2>/dev/null | sort -hr | head -10)"
    echo -e "$BODY" | mail -s "$SUBJECT" "$ADMIN_EMAIL"
    logger -t disk-alert "경고: ${MOUNT} ${USAGE}% 사용 중"
  fi
done < <(df -h | awk 'NR>1 && /^\// {print}')
EOF

sudo chmod +x /usr/local/bin/disk-alert.sh

# 즉시 테스트
sudo /usr/local/bin/disk-alert.sh

logrotate 커스텀 설정

애플리케이션이 생성하는 커스텀 로그는 logrotate로 자동 순환 관리해야 한다. 압축 보관, 보존 기간, 권한 등을 세밀하게 제어할 수 있다.

conf
# /etc/logrotate.d/homeserver — 홈서버 커스텀 로그 순환 설정
/var/log/weekly-cleanup.log
/var/log/monthly-upgrade.log
/var/log/homeserver-*.log {
    weekly              # 매주 순환
    rotate 8            # 최근 8개 파일 보존 (약 2개월)
    compress            # gzip 압축
    delaycompress       # 직전 파일은 압축 유예 (현재 쓰기 중일 수 있음)
    missingok           # 파일 없어도 오류 무시
    notifempty          # 빈 파일은 순환 건너뜀
    create 0640 root adm  # 새 파일 권한: rw-r-----
    dateext             # 날짜를 파일명에 포함 (cleanup.log-20260519.gz)
    sharedscripts
    postrotate
        # 순환 후 훅 (필요 시 서비스 재시작)
        # systemctl reload rsyslog > /dev/null 2>&1 || true
    endscript
}

# Docker 관련 로그는 json-file 드라이버가 별도 관리
# (docker-compose.yml의 max-size, max-file 옵션 참조)
bash
# logrotate 설정 테스트 (강제 실행)
sudo logrotate -f /etc/logrotate.d/homeserver

# 모든 logrotate 규칙 디버그 실행
sudo logrotate -d /etc/logrotate.conf 2>&1 | head -50

# 현재 logrotate 상태 확인 (마지막 실행 시간)
cat /var/lib/logrotate/status | grep homeserver

Watchtower 자동 업데이트 — 세밀한 제어

Watchtower는 실행 중인 컨테이너의 베이스 이미지 업데이트를 자동으로 감지하고 교체한다. 모든 컨테이너를 무차별적으로 업데이트하면 의도치 않은 브레이킹 체인지가 발생할 수 있으므로, 레이블로 컨테이너별 업데이트 정책을 제어한다.

bash
# ── Watchtower 레이블로 컨테이너별 업데이트 정책 제어 ──────────────────────
# docker-compose.yml 서비스에 레이블 추가 예시

# 자동 업데이트 허용 (기본값)
# com.centurylinklabs.watchtower.enable: "true"

# 자동 업데이트 제외 (버전 고정 필요한 서비스)
# com.centurylinklabs.watchtower.enable: "false"

# 업데이트 전 사전 스크립트 실행
# com.centurylinklabs.watchtower.lifecycle.pre-update: /scripts/pre-update.sh

# ── Watchtower 수동 실행 (일회성) ───────────────────────────────────────────
docker run --rm \
  -v /var/run/docker.sock:/var/run/docker.sock \
  containrrr/watchtower \
  --run-once \
  --cleanup \
  jellyfin portainer   # 특정 컨테이너만 업데이트

# ── 업데이트 로그 확인 ──────────────────────────────────────────────────────
docker logs watchtower --tail 50