<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Ubuntu on Chanyeol Dev</title>
    <link>https://chanyeols.com/tags/ubuntu/</link>
    <description>Recent content in Ubuntu on Chanyeol Dev</description>
    <generator>Hugo</generator>
    <language>ko-kr</language>
    <lastBuildDate>Sat, 04 Apr 2026 09:00:00 +0900</lastBuildDate>
    <atom:link href="https://chanyeols.com/tags/ubuntu/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>노트북 한 대로 홈서버 구축하기 - 12편 완전 정복 총정리</title>
      <link>https://chanyeols.com/posts/part-00-summary/</link>
      <pubDate>Sat, 04 Apr 2026 09:00:00 +0900</pubDate>
      <guid>https://chanyeols.com/posts/part-00-summary/</guid>
      <description>ThinkPad 노트북 한 대로 NAS, 사진 관리, 비밀번호 관리, 모니터링, 보안, 자동 백업, 커스텀 대시보드까지 구축한 홈서버 구축기 12편을 한 페이지로 정리합니다.</description>
      <content:encoded><![CDATA[<h2 id="시작은-단순했다">시작은 단순했다</h2>
<p>클라우드 비용이 아깝고, NAS도 필요하고, 사이드 프로젝트 서버도 있으면 좋겠고. 마침 집에 안 쓰는 ThinkPad가 있었다.</p>
<p>그렇게 시작된 홈서버 구축기가 어느새 12편이 됐다.</p>
<hr>
<h2 id="최종-구성">최종 구성</h2>
<p><img alt="전체 아키텍처 구성도" loading="lazy" src="/images/homeserver-01-architecture.png"></p>
<ul>
<li><strong>기기</strong>: ThinkPad E15 Gen3 (Ryzen 5 5600U, RAM 16GB)</li>
<li><strong>OS</strong>: Ubuntu Server 24.04 LTS</li>
<li><strong>네트워크</strong>: Tailscale VPN + OCI Nginx 리버스 프록시</li>
<li><strong>도메인</strong>: yourdomain.com (Cloudflare)</li>
<li><strong>스토리지</strong>: 256GB SSD (OS/Docker) + 1TB SSD (/mnt/data, NTFS)</li>
</ul>
<hr>
<h2 id="운영-중인-서비스-전체-목록">운영 중인 서비스 전체 목록</h2>
<table>
  <thead>
      <tr>
          <th>포트</th>
          <th>서비스</th>
          <th>접근 방식</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>2283</td>
          <td>Immich (사진 관리)</td>
          <td>도메인 (OCI 프록시)</td>
      </tr>
      <tr>
          <td>9090</td>
          <td>Filebrowser (파일 관리)</td>
          <td>도메인 (OCI 프록시)</td>
      </tr>
      <tr>
          <td>11000</td>
          <td>Vaultwarden (비밀번호)</td>
          <td>도메인 (OCI 프록시)</td>
      </tr>
      <tr>
          <td>13000</td>
          <td>Grafana (모니터링)</td>
          <td>Tailscale VPN</td>
      </tr>
      <tr>
          <td>19000</td>
          <td>Portainer (Docker GUI)</td>
          <td>Tailscale VPN</td>
      </tr>
      <tr>
          <td>19090</td>
          <td>Prometheus</td>
          <td>Tailscale VPN</td>
      </tr>
      <tr>
          <td>19100</td>
          <td>Node Exporter</td>
          <td>내부 수집용</td>
      </tr>
      <tr>
          <td>23000</td>
          <td>컨테이너 대시보드 (React)</td>
          <td>Tailscale VPN</td>
      </tr>
      <tr>
          <td>28080</td>
          <td>컨테이너 대시보드 (Spring)</td>
          <td>Tailscale VPN</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="12편-한눈에-보기">12편 한눈에 보기</h2>
<h3 id="1편--왜-홈서버인가--전체-아키텍처">1편 — 왜 홈서버인가? + 전체 아키텍처</h3>
<p><a href="/posts/part-01-intro">→ 보러가기</a></p>
<p>클라우드 대신 홈서버를 선택한 이유와 Tailscale + OCI + Cloudflare로 구성한 전체 아키텍처를 소개한다.</p>
<hr>
<h3 id="2편--ubuntu-server-설치--tailscale-vpn">2편 — Ubuntu Server 설치 + Tailscale VPN</h3>
<p><a href="/posts/part-02-ubuntu-tailscale">→ 보러가기</a></p>
<p>Ventoy로 Ubuntu Server 24.04를 설치하고, Tailscale VPN으로 홈서버와 OCI 인스턴스를 하나의 사설 네트워크로 묶었다.</p>
<hr>
<h3 id="3편--외장-ssd-마운트--filebrowser-원격-파일-관리">3편 — 외장 SSD 마운트 + Filebrowser 원격 파일 관리</h3>
<p><a href="/posts/part-03-ssd-filebrowser">→ 보러가기</a></p>
<p>NTFS 외장 SSD를 마운트하고 ntfsfix로 읽기/쓰기 문제를 해결했다. Docker로 Filebrowser를 띄워서 브라우저에서 파일을 관리할 수 있게 됐다.</p>
<hr>
<h3 id="4편--immich로-구글-포토-대체하기">4편 — Immich로 구글 포토 대체하기</h3>
<p><a href="/posts/part-04-immich">→ 보러가기</a></p>
<p><img alt="Immich 접속 화면" loading="lazy" src="/images/homeserver-04-immich.png"></p>
<p>Docker Compose 설치 과정에서 구버전 docker, NTFS 권한 문제 등 4가지 트러블슈팅을 겪었다. DB는 반드시 ext4에 둬야 한다.</p>
<hr>
<h3 id="5편--vaultwarden으로-비밀번호-자체-호스팅">5편 — Vaultwarden으로 비밀번호 자체 호스팅</h3>
<p><a href="/posts/part-05-vaultwarden">→ 보러가기</a></p>
<p>구글 비밀번호를 CSV로 내보내서 Vaultwarden으로 이전했다. Bitwarden 브라우저 확장과 연동하면 기존과 사용감이 동일하다.</p>
<hr>
<h3 id="6편--portainer-ce로-docker-gui-관리">6편 — Portainer CE로 Docker GUI 관리</h3>
<p><a href="/posts/part-06-portainer">→ 보러가기</a></p>
<p><img alt="Portainer 대시보드" loading="lazy" src="/images/homeserver-06-portainer.png"></p>
<p>컨테이너가 8개를 넘어가면서 CLI 관리가 한계에 달했다. Portainer CE로 웹 UI에서 컨테이너를 관리한다. 보안상 VPN 안에서만 접근한다.</p>
<hr>
<h3 id="7편--grafana--prometheus로-홈서버-모니터링">7편 — Grafana + Prometheus로 홈서버 모니터링</h3>
<p><a href="/posts/part-07-monitoring">→ 보러가기</a></p>
<p><img alt="Grafana 모니터링 대시보드" loading="lazy" src="/images/homeserver-07-grafana.png"></p>
<p>Node Exporter를 홈서버 + OCI 두 곳에 설치하고, Prometheus가 Tailscale VPN을 통해 두 서버의 메트릭을 수집한다. ThinkPad 배터리 메트릭도 추가했다.</p>
<hr>
<h3 id="8편--fail2ban으로-ssh-브루트포스-차단">8편 — Fail2ban으로 SSH 브루트포스 차단</h3>
<p><a href="/posts/part-08-fail2ban">→ 보러가기</a></p>
<p>auth.log를 열어보니 수천 줄의 SSH 로그인 시도가 쌓여 있었다. Fail2ban으로 자동 차단하고, recidive jail로 반복 공격자는 7일 장기 차단한다.</p>
<hr>
<h3 id="9편--certbot-expand로-ssl-서브도메인-추가">9편 — certbot &ndash;expand로 SSL 서브도메인 추가</h3>
<p><a href="/posts/part-09-ssl">→ 보러가기</a></p>
<p>서비스가 늘어날 때마다 SSL 인증서에 서브도메인을 추가해야 한다. <code>--expand</code> 옵션과 셸 스크립트로 관리하면 <code>-d</code> 한 줄만 추가하면 된다.</p>
<p><img alt="브라우저 인증서 정보" loading="lazy" src="/images/homeserver-09-browser-cert.png"></p>
<hr>
<h3 id="10편--postgresql-자동-백업-pg_dump--cron">10편 — PostgreSQL 자동 백업 (pg_dump + cron)</h3>
<p><a href="/posts/part-10-postgresql-backup">→ 보러가기</a></p>
<p>Immich DB가 날아가면 앨범, 태그, 얼굴 인식 데이터가 전부 사라진다. <code>docker exec</code>으로 pg_dumpall을 실행하고 cron으로 매일 새벽 3시에 자동 백업한다.</p>
<hr>
<h3 id="11편--tlp--thinkfan--swap-튜닝으로-운영-최적화">11편 — TLP + thinkfan + Swap 튜닝으로 운영 최적화</h3>
<p><a href="/posts/part-11-tuning">→ 보러가기</a></p>
<p>CPU 온도 85°C → 55°C, Swap 사용률 26% → 7%. TLP 터보 부스트 비활성화와 swappiness 튜닝으로 24시간 운영에 최적화했다.</p>
<p><img alt="Grafana - Swap 튜닝 전후" loading="lazy" src="/images/homeserver-11-swap-after.png"></p>
<hr>
<h3 id="12편--직접-만든-컨테이너-대시보드-spring-boot--react--sse">12편 — 직접 만든 컨테이너 대시보드 (Spring Boot + React + SSE)</h3>
<p><a href="/posts/part-12-dashboard">→ 보러가기</a></p>
<p><img alt="커스텀 대시보드 UI" loading="lazy" src="/images/homeserver-12-dashboard-ui.png"></p>
<p>Portainer 대신 Spring Boot + React로 컨테이너 모니터링 대시보드를 직접 만들었다. SSE로 실시간 로그 스트리밍을 구현한 게 핵심이다.</p>
<hr>
<h2 id="돌아보며">돌아보며</h2>
<p>파일 서버 하나 만들려고 시작했는데 어쩌다 보니 이렇게 됐다.</p>
<p>홈서버의 가장 큰 장점은 공부할 게 계속 생긴다는 거다. 서비스 하나 올리면 모니터링이 필요하고, 모니터링 하다 보면 튜닝하고 싶어지고, 외부에 열면 보안이 신경 쓰이고. 그 과정에서 Linux, Docker, Nginx, Spring Boot, React를 실제로 써보게 된다.</p>
<p>클라우드가 편하긴 하지만, 직접 서버를 굴리는 재미는 또 다르다.</p>
]]></content:encoded>
    </item>
    <item>
      <title>노트북으로 홈서버 구축하기 - TLP &#43; thinkfan &#43; Swap 튜닝으로 운영 최적화 (11편)</title>
      <link>https://chanyeols.com/posts/part-11-tuning/</link>
      <pubDate>Thu, 02 Apr 2026 00:00:00 +0000</pubDate>
      <guid>https://chanyeols.com/posts/part-11-tuning/</guid>
      <description>24시간 돌아가는 홈서버의 CPU 온도를 85°C에서 55°C로 낮추고, swappiness 튜닝으로 불필요한 Swap 사용을 줄인 과정을 정리합니다.</description>
      <content:encoded><![CDATA[<h2 id="두-가지-문제">두 가지 문제</h2>
<p>홈서버를 며칠 돌려보니 두 가지가 눈에 띄었다.</p>
<ol>
<li>Immich 썸네일 생성 같은 작업이 걸리면 <strong>CPU 온도가 85°C까지 치솟는다</strong>. 24시간 켜두는 서버라 장기적으로 하드웨어에 좋지 않다.</li>
<li>Grafana 대시보드를 보니 <strong>RAM 사용률이 38%인데 Swap을 26%나 사용</strong>하고 있었다. RAM이 절반도 안 찼는데 Swap을 쓰는 건 비정상이다.</li>
</ol>
<p>두 문제를 각각 TLP + thinkfan, swappiness 튜닝으로 해결했다.</p>
<hr>
<h2 id="1부-tlp--thinkfan으로-온도-낮추기">1부: TLP + thinkfan으로 온도 낮추기</h2>
<h3 id="tlp-설치">TLP 설치</h3>
<p>TLP는 Linux용 전력 관리 도구다. 설치만 해도 기본값으로 어느 정도 효과가 있고, ThinkPad에 맞게 튜닝하면 훨씬 효과적이다.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo apt install tlp tlp-rdw -y
</span></span><span style="display:flex;"><span>sudo tlp start
</span></span></code></pre></div><p>설치 확인:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo tlp-stat -s
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>--- TLP 1.6.1 --------------------------------------------
</span></span><span style="display:flex;"><span>+++ TLP Status
</span></span><span style="display:flex;"><span>State          = enabled
</span></span><span style="display:flex;"><span>Mode           = AC
</span></span><span style="display:flex;"><span>Power source   = AC
</span></span></code></pre></div><h3 id="tlp-튜닝">TLP 튜닝</h3>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo vi /etc/tlp.conf
</span></span></code></pre></div><p>아래 항목을 찾아서 주석 해제 후 값을 수정한다.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>CPU_SCALING_GOVERNOR_ON_AC=powersave
</span></span><span style="display:flex;"><span>CPU_ENERGY_PERF_POLICY_ON_AC=balance_power
</span></span><span style="display:flex;"><span>CPU_BOOST_ON_AC=0
</span></span><span style="display:flex;"><span>PLATFORM_PROFILE_ON_AC=balanced
</span></span></code></pre></div><p><strong>핵심은 <code>CPU_BOOST_ON_AC=0</code>이다.</strong> 터보 부스트를 끄는 것만으로도 온도가 10~20°C 내려간다. 홈서버 용도에서는 최대 성능이 필요한 순간이 거의 없기 때문에 체감 성능 저하도 없다.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo tlp start
</span></span></code></pre></div><h3 id="thinkfan-설치">thinkfan 설치</h3>
<p>thinkfan은 온도 구간별로 팬 속도를 직접 제어하는 도구다. ThinkPad 기본 팬 제어보다 세밀하게 조절할 수 있다.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo apt install thinkfan -y
</span></span></code></pre></div><p>thinkpad_acpi 팬 제어 권한 활성화:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>echo <span style="color:#a5d6ff">&#34;options thinkpad_acpi fan_control=1&#34;</span> | sudo tee /etc/modprobe.d/thinkpad_acpi.conf
</span></span><span style="display:flex;"><span>sudo modprobe -r thinkpad_acpi
</span></span><span style="display:flex;"><span>sudo modprobe thinkpad_acpi <span style="color:#79c0ff">fan_control</span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">1</span>
</span></span></code></pre></div><p>센서 경로 확인:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>find /sys/devices -name <span style="color:#a5d6ff">&#34;temp*_input&#34;</span> 2&gt;/dev/null
</span></span></code></pre></div><p>ThinkPad CPU 온도 센서는 보통 아래 경로에 있다.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>/sys/devices/platform/thinkpad_hwmon/hwmon/hwmon4/temp1_input
</span></span></code></pre></div><p><code>hwmon</code> 뒤의 숫자는 환경마다 다를 수 있으니 직접 확인하자.</p>
<h3 id="thinkfan-설정">thinkfan 설정</h3>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo vi /etc/thinkfan.conf
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#7ee787">sensors</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span>- <span style="color:#7ee787">hwmon</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">/sys/devices/platform/thinkpad_hwmon/hwmon/hwmon4/temp1_input</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#7ee787">fans</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span>- <span style="color:#7ee787">tpacpi</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">/proc/acpi/ibm/fan</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#7ee787">levels</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span>- [<span style="color:#a5d6ff">0</span>,<span style="color:#6e7681"> </span><span style="color:#a5d6ff">0</span>,<span style="color:#6e7681"> </span><span style="color:#a5d6ff">55</span>]<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span>- [<span style="color:#a5d6ff">1</span>,<span style="color:#6e7681"> </span><span style="color:#a5d6ff">50</span>,<span style="color:#6e7681"> </span><span style="color:#a5d6ff">60</span>]<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span>- [<span style="color:#a5d6ff">2</span>,<span style="color:#6e7681"> </span><span style="color:#a5d6ff">55</span>,<span style="color:#6e7681"> </span><span style="color:#a5d6ff">65</span>]<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span>- [<span style="color:#a5d6ff">3</span>,<span style="color:#6e7681"> </span><span style="color:#a5d6ff">60</span>,<span style="color:#6e7681"> </span><span style="color:#a5d6ff">70</span>]<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span>- [<span style="color:#a5d6ff">4</span>,<span style="color:#6e7681"> </span><span style="color:#a5d6ff">65</span>,<span style="color:#6e7681"> </span><span style="color:#a5d6ff">75</span>]<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span>- [<span style="color:#a5d6ff">5</span>,<span style="color:#6e7681"> </span><span style="color:#a5d6ff">70</span>,<span style="color:#6e7681"> </span><span style="color:#a5d6ff">80</span>]<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span>- [<span style="color:#a5d6ff">7</span>,<span style="color:#6e7681"> </span><span style="color:#a5d6ff">75</span>,<span style="color:#6e7681"> </span><span style="color:#a5d6ff">255</span>]<span style="color:#6e7681">
</span></span></span></code></pre></div><table>
  <thead>
      <tr>
          <th>레벨</th>
          <th>온도 구간</th>
          <th>동작</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>0</td>
          <td>~55°C</td>
          <td>팬 정지</td>
      </tr>
      <tr>
          <td>1</td>
          <td>50~60°C</td>
          <td>최저속</td>
      </tr>
      <tr>
          <td>2</td>
          <td>55~65°C</td>
          <td>저속</td>
      </tr>
      <tr>
          <td>3</td>
          <td>60~70°C</td>
          <td>중저속</td>
      </tr>
      <tr>
          <td>4</td>
          <td>65~75°C</td>
          <td>중속</td>
      </tr>
      <tr>
          <td>5</td>
          <td>70~80°C</td>
          <td>고속</td>
      </tr>
      <tr>
          <td>7</td>
          <td>75°C~</td>
          <td>최대속</td>
      </tr>
  </tbody>
</table>
<p>서비스 시작:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo systemctl enable thinkfan
</span></span><span style="display:flex;"><span>sudo systemctl start thinkfan
</span></span><span style="display:flex;"><span>sudo systemctl status thinkfan
</span></span></code></pre></div><h3 id="온도-개선-결과">온도 개선 결과</h3>
<table>
  <thead>
      <tr>
          <th></th>
          <th>적용 전</th>
          <th>적용 후</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>CPU 온도 (풀로드)</td>
          <td>85°C</td>
          <td>55~66°C</td>
      </tr>
      <tr>
          <td>팬 RPM</td>
          <td>3600 RPM</td>
          <td>1800 RPM</td>
      </tr>
      <tr>
          <td>GPU 온도</td>
          <td>79°C</td>
          <td>48~59°C</td>
      </tr>
  </tbody>
</table>
<p>TLP 터보 부스트 비활성화만으로도 <strong>약 20~30°C</strong> 온도가 내려갔다. 24시간 상시 운영 환경에서는 성능보다 안정성이 중요하기 때문에 이 설정이 최적이다.</p>
<h3 id="부팅-시-자동-적용-확인">부팅 시 자동 적용 확인</h3>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo systemctl is-enabled tlp
</span></span><span style="display:flex;"><span>sudo systemctl is-enabled thinkfan
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 둘 다 &#34;enabled&#34; 출력되면 정상</span>
</span></span></code></pre></div><hr>
<h2 id="2부-swappiness-튜닝">2부: swappiness 튜닝</h2>
<h3 id="문제-발견">문제 발견</h3>
<p>Grafana 대시보드를 보다가 이상한 걸 발견했다.</p>
<p><img alt="Grafana - RAM 38% 사용 중인데 Swap 26% 사용" loading="lazy" src="/images/homeserver-11-swap-before.png"></p>
<p>RAM 사용률은 37.8%인데 Swap을 26%나 쓰고 있었다. Swap은 SSD를 메모리처럼 쓰는 거라 RAM보다 훨씬 느리다. 불필요하게 Swap을 쓰면 전체 성능이 저하된다.</p>
<h3 id="swappiness란">swappiness란?</h3>
<p><code>vm.swappiness</code>는 커널이 얼마나 적극적으로 Swap을 사용할지 결정하는 값이다.</p>
<table>
  <thead>
      <tr>
          <th>값</th>
          <th>설명</th>
          <th>적합한 환경</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>0</td>
          <td>Swap 거의 사용 안 함</td>
          <td>RAM이 매우 넉넉한 서버</td>
      </tr>
      <tr>
          <td>10</td>
          <td>RAM 거의 다 찰 때만 Swap 사용</td>
          <td>홈서버, 일반 서버</td>
      </tr>
      <tr>
          <td>60</td>
          <td>Ubuntu 기본값</td>
          <td>데스크탑</td>
      </tr>
      <tr>
          <td>100</td>
          <td>적극적으로 Swap 사용</td>
          <td>RAM이 매우 부족한 환경</td>
      </tr>
  </tbody>
</table>
<p>Ubuntu 기본값이 60인데, 홈서버처럼 RAM이 충분한 환경에서는 너무 높다.</p>
<h3 id="튜닝-적용">튜닝 적용</h3>
<p>즉시 적용:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo sysctl vm.swappiness<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">10</span>
</span></span></code></pre></div><p>재부팅 후에도 유지:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>echo <span style="color:#a5d6ff">&#34;vm.swappiness=10&#34;</span> | sudo tee -a /etc/sysctl.conf
</span></span></code></pre></div><p>적용 확인:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>cat /proc/sys/vm/swappiness
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 10</span>
</span></span></code></pre></div><p>기존 Swap에 올라간 데이터를 RAM으로 옮겨서 효과를 바로 확인한다.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo swapoff -a <span style="color:#ff7b72;font-weight:bold">&amp;&amp;</span> sudo swapon -a
</span></span></code></pre></div><h3 id="튜닝-결과">튜닝 결과</h3>
<p><img alt="Grafana - Swap 사용량이 7%대로 감소" loading="lazy" src="/images/homeserver-11-swap-after.png"></p>
<p>기존 26%가량 사용하던 Swap이 7%대로 떨어진 것을 확인할 수 있다.</p>
<hr>
<h2 id="정리">정리</h2>
<p><strong>온도 관리:</strong></p>
<ul>
<li>TLP <code>CPU_BOOST_ON_AC=0</code> 하나로 온도 20~30°C 감소</li>
<li>thinkfan으로 팬 커브를 직접 제어해서 불필요한 소음 줄임</li>
<li>홈서버는 성능보다 안정성이 중요하므로 터보 부스트 끄는 게 최선</li>
</ul>
<p><strong>Swap 튜닝:</strong></p>
<ul>
<li>Ubuntu 기본값 swappiness=60은 데스크탑 기준, 서버는 10이 적합</li>
<li><code>swapoff -a &amp;&amp; swapon -a</code>로 기존 Swap 데이터를 RAM으로 즉시 이동 가능</li>
<li>한 줄 설정으로 전반적인 시스템 응답성 개선</li>
</ul>
<p>다음 편에서는 Portainer 대신 Spring Boot + React로 직접 만든 컨테이너 대시보드를 다룬다. SSE를 이용한 실시간 로그 스트리밍이 핵심이다.</p>
]]></content:encoded>
    </item>
    <item>
      <title>노트북으로 홈서버 구축하기 - Fail2ban으로 SSH 브루트포스 공격 차단하기 (8편)</title>
      <link>https://chanyeols.com/posts/part-08-fail2ban/</link>
      <pubDate>Mon, 30 Mar 2026 00:00:00 +0000</pubDate>
      <guid>https://chanyeols.com/posts/part-08-fail2ban/</guid>
      <description>OCI 서버 auth.log를 열어보니 수천 줄의 SSH 로그인 시도가 쌓여 있었다. Fail2ban을 설치해서 브루트포스 공격을 자동으로 차단하는 과정을 정리합니다.</description>
      <content:encoded><![CDATA[<h2 id="얼마나-많이-들어오나">얼마나 많이 들어오나</h2>
<p>OCI 서버는 공인 IP가 직접 노출돼 있어서 설치 직후부터 SSH 로그인 시도가 들어온다. auth.log를 열어봤다가 깜짝 놀랐다.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo grep <span style="color:#a5d6ff">&#34;Failed password&#34;</span> /var/log/auth.log | tail -20
</span></span></code></pre></div><p><img alt="auth.log에 Invalid user 로그가 수천 줄 쌓인 화면" loading="lazy" src="/images/homeserver-08-authlog.png"></p>
<p><code>Invalid user admin</code>, <code>Invalid user guest</code>, <code>Invalid user root</code> 같은 로그가 수분 간격으로 끊임없이 들어오고 있었다. 전 세계 봇들이 24시간 SSH 로그인을 시도하는 것이다. 방치하면 언젠가 뚫릴 수 있고, 서버 리소스도 낭비된다.</p>
<hr>
<h2 id="fail2ban이란">Fail2ban이란?</h2>
<p>로그 파일을 모니터링하다가 일정 횟수 이상 로그인에 실패한 IP를 자동으로 방화벽에서 차단하는 도구다.</p>
<ul>
<li>설치만 해도 SSH 기본 보호 즉시 적용</li>
<li>iptables와 연동해서 IP 차단</li>
<li>일정 시간 후 자동으로 차단 해제</li>
<li>SSH 외에도 Nginx, Apache 등 다양한 서비스 보호 가능</li>
</ul>
<hr>
<h2 id="설치">설치</h2>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo apt install fail2ban -y
</span></span><span style="display:flex;"><span>sudo systemctl enable fail2ban
</span></span><span style="display:flex;"><span>sudo systemctl start fail2ban
</span></span><span style="display:flex;"><span>sudo systemctl status fail2ban
</span></span></code></pre></div><p><img alt="fail2ban active (running) 상태" loading="lazy" src="/images/homeserver-08-fail2ban-status.png"></p>
<p>설치 직후부터 기본 SSH 보호가 바로 적용된다.</p>
<hr>
<h2 id="설정">설정</h2>
<p>Fail2ban 기본 설정은 <code>/etc/fail2ban/jail.conf</code>에 있지만 이 파일은 직접 수정하지 않는다. 업데이트 시 덮어씌워질 수 있기 때문이다. 대신 <code>jail.local</code>을 만들어서 오버라이드한다.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo vi /etc/fail2ban/jail.local
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>[DEFAULT]
</span></span><span style="display:flex;"><span># 차단에서 제외할 IP (내 Tailscale IP 등)
</span></span><span style="display:flex;"><span>ignoreip = 127.0.0.1/8 ::1
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span># 차단 지속 시간: 기본 10분 → 1시간
</span></span><span style="display:flex;"><span>bantime = 3600
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span># 몇 초 동안 실패 횟수를 카운트할지
</span></span><span style="display:flex;"><span>findtime = 600
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span># 몇 번 실패하면 차단할지
</span></span><span style="display:flex;"><span>maxretry = 5
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>[sshd]
</span></span><span style="display:flex;"><span>enabled = true
</span></span><span style="display:flex;"><span>port = ssh
</span></span><span style="display:flex;"><span>logpath = %(sshd_log)s
</span></span><span style="display:flex;"><span>backend = %(sshd_backend)s
</span></span></code></pre></div><table>
  <thead>
      <tr>
          <th>항목</th>
          <th>기본값</th>
          <th>변경값</th>
          <th>설명</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>bantime</td>
          <td>600초</td>
          <td>3600초</td>
          <td>차단 지속 시간</td>
      </tr>
      <tr>
          <td>findtime</td>
          <td>600초</td>
          <td>600초</td>
          <td>실패 횟수 카운트 기간</td>
      </tr>
      <tr>
          <td>maxretry</td>
          <td>5회</td>
          <td>5회</td>
          <td>차단 기준 실패 횟수</td>
      </tr>
  </tbody>
</table>
<p>설정 적용:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo systemctl restart fail2ban
</span></span></code></pre></div><blockquote>
<p>⚠️ 설정 전에 반드시 자신의 IP를 <code>ignoreip</code>에 추가해둘 것. 실수로 자신의 IP가 차단되면 SSH 접속이 불가능해진다.</p>
</blockquote>
<hr>
<h2 id="반복-공격자-장기-차단-recidive">반복 공격자 장기 차단 (recidive)</h2>
<p>같은 IP가 여러 번 차단됐다 풀리면 또 시도하는 경우가 있다. <code>recidive</code> jail을 활성화하면 반복 공격자를 장기간 차단할 수 있다.</p>
<p><code>jail.local</code>에 추가:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>[recidive]
</span></span><span style="display:flex;"><span>enabled = true
</span></span><span style="display:flex;"><span>logpath = /var/log/fail2ban.log
</span></span><span style="display:flex;"><span>banaction = %(banaction_allports)s
</span></span><span style="display:flex;"><span>bantime = 604800   ; 7일
</span></span><span style="display:flex;"><span>findtime = 86400   ; 1일
</span></span><span style="display:flex;"><span>maxretry = 3       ; 하루에 3번 차단되면 7일 차단
</span></span></code></pre></div><hr>
<h2 id="nginx-보호">Nginx 보호</h2>
<p>OCI에서 Nginx 리버스 프록시를 운영하고 있으니 HTTP 레벨 공격도 막을 수 있다.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>[nginx-http-auth]
</span></span><span style="display:flex;"><span>enabled = true
</span></span><span style="display:flex;"><span>port = http,https
</span></span><span style="display:flex;"><span>logpath = /var/log/nginx/error.log
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>[nginx-botsearch]
</span></span><span style="display:flex;"><span>enabled = true
</span></span><span style="display:flex;"><span>port = http,https
</span></span><span style="display:flex;"><span>logpath = /var/log/nginx/access.log
</span></span><span style="display:flex;"><span>maxretry = 2
</span></span></code></pre></div><hr>
<h2 id="차단-현황-확인">차단 현황 확인</h2>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 전체 jail 상태</span>
</span></span><span style="display:flex;"><span>sudo fail2ban-client status
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># SSH jail 상세</span>
</span></span><span style="display:flex;"><span>sudo fail2ban-client status sshd
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>Status for the jail: sshd
</span></span><span style="display:flex;"><span>|- Filter
</span></span><span style="display:flex;"><span>|  |- Currently failed: 3
</span></span><span style="display:flex;"><span>|  |- Total failed:     1284
</span></span><span style="display:flex;"><span>|  `- File list:        /var/log/auth.log
</span></span><span style="display:flex;"><span>`- Actions
</span></span><span style="display:flex;"><span>   |- Currently banned: 12
</span></span><span style="display:flex;"><span>   |- Total banned:     87
</span></span><span style="display:flex;"><span>   `- Banned IP list:   185.156.73.233 139.19.117.130 ...
</span></span></code></pre></div><p><img alt="fail2ban-client status sshd 결과" loading="lazy" src="/images/homeserver-08-fail2ban-status-sshd.png"></p>
<p>실시간 로그 확인:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo tail -f /var/log/fail2ban.log
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>2026-03-29 22:48:33 INFO [sshd] Ban 193.46.255.86
</span></span><span style="display:flex;"><span>2026-03-29 22:52:33 INFO [sshd] Ban 185.156.73.233
</span></span><span style="display:flex;"><span>2026-03-29 23:01:15 INFO [sshd] Ban 139.19.117.130
</span></span></code></pre></div><hr>
<h2 id="ip-수동-차단해제">IP 수동 차단/해제</h2>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 수동 차단</span>
</span></span><span style="display:flex;"><span>sudo fail2ban-client set sshd banip 1.2.3.4
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 차단 해제 (내 IP 실수로 차단됐을 때)</span>
</span></span><span style="display:flex;"><span>sudo fail2ban-client set sshd unbanip 1.2.3.4
</span></span></code></pre></div><hr>
<h2 id="차단-목록-확인">차단 목록 확인</h2>
<p>이후 시간이 조금 지나고 확인해보니, 정상적으로 잘 차단하고 있는 걸 확인할 수 있다.
<img alt="fail2ban-client log 결과" loading="lazy" src="/images/homeserver-08-fail2ban-log.png"></p>
<h2 id="설치-전후-비교">설치 전후 비교</h2>
<table>
  <thead>
      <tr>
          <th></th>
          <th>설치 전</th>
          <th>설치 후</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>auth.log</td>
          <td>Failed password 수천 줄</td>
          <td>차단된 IP는 시도 자체가 안 됨</td>
      </tr>
      <tr>
          <td>서버 부하</td>
          <td>SSH 시도로 인한 불필요한 부하</td>
          <td>최소화</td>
      </tr>
      <tr>
          <td>보안</td>
          <td>브루트포스 공격에 노출</td>
          <td>자동 차단</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="정리">정리</h2>
<p>클라우드 서버를 운영한다면 Fail2ban은 선택이 아니라 필수다. 설치 자체는 5분이면 되고, 기본 설정만으로도 대부분의 브루트포스 공격을 막을 수 있다. <code>recidive</code> jail까지 설정해두면 반복 공격자를 장기간 차단해서 더욱 안전하게 운영할 수 있다.</p>
<p>다음 편에서는 서비스가 늘어나면서 기존 SSL 인증서에 서브도메인을 추가하는 방법을 다룬다.</p>
]]></content:encoded>
    </item>
    <item>
      <title>노트북으로 홈서버 구축하기 - 외장 SSD 마운트 &#43; Filebrowser 원격 파일 관리 (3편)</title>
      <link>https://chanyeols.com/posts/part-03-ssd-filebrowser/</link>
      <pubDate>Wed, 25 Mar 2026 00:00:00 +0000</pubDate>
      <guid>https://chanyeols.com/posts/part-03-ssd-filebrowser/</guid>
      <description>NTFS 외장 SSD를 Ubuntu에 마운트하고, Docker로 Filebrowser를 설치해서 브라우저에서 파일을 관리하는 환경을 구성합니다. OCI Nginx 리버스 프록시와 SSL 설정까지 다룹니다.</description>
      <content:encoded><![CDATA[<h2 id="외장-ssd-마운트">외장 SSD 마운트</h2>
<p>집에 1TB SSD가 남아있어서 홈서버 스토리지로 활용하기로 했다. 기존에 Windows에서 쓰던 드라이브라 NTFS 포맷이다.</p>
<h3 id="디스크-확인">디스크 확인</h3>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>lsblk
</span></span></code></pre></div><p>어떤 디바이스명으로 잡혔는지 확인한다.</p>
<p><img alt="lsblk 결과" loading="lazy" src="/images/homeserver-03-lsblk.png"></p>
<p>파티션 포맷 확인:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo blkid /dev/nvme1n1p2
</span></span></code></pre></div><p>NTFS로 확인됐으니 마운트를 진행한다.</p>
<h3 id="마운트">마운트</h3>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo apt install ntfs-3g -y
</span></span><span style="display:flex;"><span>sudo mkdir /mnt/data
</span></span><span style="display:flex;"><span>sudo mount /dev/nvme1n1p2 /mnt/data
</span></span></code></pre></div><p><img alt="마운트 결과" loading="lazy" src="/images/homeserver-03-mount.png"></p>
<p>마운트 후 확인해보니 <code>Could not mount read-write, trying read-only</code> 메시지가 떴다. 읽기 전용으로 마운트된 것이다. Windows에서 쓰던 드라이브라 더티 플래그가 남아있어서 발생하는 문제다.</p>
<h3 id="읽기쓰기-가능하도록-재마운트">읽기/쓰기 가능하도록 재마운트</h3>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo umount /mnt/data
</span></span><span style="display:flex;"><span>sudo ntfsfix /dev/nvme1n1p2
</span></span><span style="display:flex;"><span>sudo mount /dev/nvme1n1p2 /mnt/data
</span></span></code></pre></div><p><code>ntfsfix</code>로 더티 플래그를 제거하고 다시 마운트하면 읽기/쓰기가 모두 가능해진다.</p>
<h3 id="심볼릭-링크-생성">심볼릭 링크 생성</h3>
<p>홈 디렉토리에서 편하게 접근하기 위해 심볼릭 링크를 만들어둔다.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>ln -s /mnt/data ~/data
</span></span></code></pre></div><hr>
<h2 id="samba-설정">Samba 설정</h2>
<p>파일 탐색기에서 네트워크 드라이브처럼 접근하고 싶다면 Samba를 설치하면 된다.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo apt install samba -y
</span></span></code></pre></div><p>Samba 사용자 등록:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo smbpasswd -a your-username
</span></span></code></pre></div><p><img alt="Samba 사용자 등록" loading="lazy" src="/images/homeserver-03-samba-user.png"></p>
<p>설정 파일에 공유 디렉토리 추가:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo vi /etc/samba/smb.conf
</span></span></code></pre></div><p>파일 맨 아래에 아래 내용을 추가한다.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>[data]
</span></span><span style="display:flex;"><span>   path = /mnt/data
</span></span><span style="display:flex;"><span>   browseable = yes
</span></span><span style="display:flex;"><span>   read only = no
</span></span><span style="display:flex;"><span>   valid users = your-username
</span></span></code></pre></div><p>Samba 서비스 재시작:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo systemctl restart smbd
</span></span></code></pre></div><p>이제 Windows 파일 탐색기 주소창에 <code>\\100.109.108.36\data</code>를 입력하면 마운트된 SSD에 접근할 수 있다.</p>
<p><img alt="파일 탐색기에서 Samba 접근" loading="lazy" src="/images/homeserver-03-samba.png"></p>
<p>다만 이건 파일 탐색기 전용이라 브라우저에서 접근하려면 Filebrowser를 써야 한다.</p>
<hr>
<h2 id="docker--filebrowser-설치">Docker + Filebrowser 설치</h2>
<h3 id="docker-설치">Docker 설치</h3>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Docker 공식 레포 추가</span>
</span></span><span style="display:flex;"><span>sudo apt install ca-certificates curl gnupg -y
</span></span><span style="display:flex;"><span>sudo install -m <span style="color:#a5d6ff">0755</span> -d /etc/apt/keyrings
</span></span><span style="display:flex;"><span>curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
</span></span><span style="display:flex;"><span>echo <span style="color:#a5d6ff">&#34;deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu noble stable&#34;</span> | sudo tee /etc/apt/sources.list.d/docker.list
</span></span><span style="display:flex;"><span>sudo apt update
</span></span><span style="display:flex;"><span>sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin -y
</span></span></code></pre></div><p>root가 아닌 일반 계정으로 Docker를 사용하려면:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo usermod -aG docker <span style="color:#79c0ff">$USER</span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 재로그인 후 적용</span>
</span></span></code></pre></div><h3 id="filebrowser-실행">Filebrowser 실행</h3>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker run -d <span style="color:#79c0ff">\
</span></span></span><span style="display:flex;"><span>  --name filebrowser <span style="color:#79c0ff">\
</span></span></span><span style="display:flex;"><span>  --restart always <span style="color:#79c0ff">\
</span></span></span><span style="display:flex;"><span>  -v /mnt/data:/srv <span style="color:#79c0ff">\
</span></span></span><span style="display:flex;"><span>  -p 9090:80 <span style="color:#79c0ff">\
</span></span></span><span style="display:flex;"><span>  filebrowser/filebrowser
</span></span></code></pre></div><p><code>/mnt/data</code>를 컨테이너 안의 <code>/srv</code>로 마운트해서 Filebrowser가 SSD 전체를 서빙하도록 했다.</p>
<h3 id="초기-로그인">초기 로그인</h3>
<p>브라우저에서 <code>http://100.109.108.36:9090</code> 접속. 초기 계정은 <code>admin / admin</code>이라고 알려져 있는데 접속이 안 됐다.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker logs filebrowser
</span></span></code></pre></div><p>로그를 확인해보니 랜덤으로 생성된 비밀번호가 출력돼 있었다. 그 비밀번호로 접속하면 된다.</p>
<p><img alt="Filebrowser 로그인 화면" loading="lazy" src="/images/homeserver-03-filebrowser-login.png"></p>
<p><img alt="Filebrowser 접속 성공" loading="lazy" src="/images/homeserver-03-filebrowser.png"></p>
<hr>
<h2 id="oci-nginx-리버스-프록시--ssl">OCI Nginx 리버스 프록시 + SSL</h2>
<p>VPN 없이도 외부에서 브라우저로 접근하려면 OCI Nginx에 리버스 프록시를 설정해야 한다.</p>
<h3 id="cloudflare-dns-추가">Cloudflare DNS 추가</h3>
<p>Cloudflare에서 <code>files.yourdomain.com</code> 서브도메인을 OCI 공인 IP로 추가한다.</p>
<p><img alt="Cloudflare DNS 설정" loading="lazy" src="/images/homeserver-03-cloudflare.png"></p>
<h3 id="nginx-설정">Nginx 설정</h3>
<p>OCI 서버에서 <code>/etc/nginx/sites-available/files.yourdomain.com</code> 파일을 생성한다.</p>
<p>처음엔 HTTP만 설정했는데, <code>files.yourdomain.com</code>으로 접속하면 계속 메인 도메인으로 리다이렉트되는 문제가 있었다. SSL 인증서를 안 걸어줘서 생기는 문제였다.</p>
<p>최종 설정:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-nginx" data-lang="nginx"><span style="display:flex;"><span><span style="color:#ff7b72">server</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">listen</span> <span style="color:#a5d6ff">80</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">server_name</span> <span style="color:#a5d6ff">files.yourdomain.com</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">return</span> <span style="color:#a5d6ff">301</span> <span style="color:#a5d6ff">https://</span><span style="color:#79c0ff">$host$request_uri</span>;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">server</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">listen</span> <span style="color:#a5d6ff">443</span> <span style="color:#a5d6ff">ssl</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">server_name</span> <span style="color:#a5d6ff">files.yourdomain.com</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">ssl_certificate</span>     <span style="color:#a5d6ff">/ssl/live/yourdomain.com/fullchain.pem</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">ssl_certificate_key</span> <span style="color:#a5d6ff">/ssl/live/yourdomain.com/privkey.pem</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">ssl_protocols</span> <span style="color:#a5d6ff">TLSv1.2</span> <span style="color:#a5d6ff">TLSv1.3</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">ssl_prefer_server_ciphers</span> <span style="color:#79c0ff;font-weight:bold">on</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">location</span> <span style="color:#a5d6ff">/</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">proxy_pass</span> <span style="color:#a5d6ff">http://100.109.108.36:9090</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">proxy_set_header</span> <span style="color:#a5d6ff">Host</span> <span style="color:#79c0ff">$host</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">proxy_set_header</span> <span style="color:#a5d6ff">X-Real-IP</span> <span style="color:#79c0ff">$remote_addr</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">proxy_set_header</span> <span style="color:#a5d6ff">X-Forwarded-For</span> <span style="color:#79c0ff">$proxy_add_x_forwarded_for</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">proxy_set_header</span> <span style="color:#a5d6ff">X-Forwarded-Proto</span> <span style="color:#79c0ff">$scheme</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>심볼릭 링크 생성 후 적용:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo ln -s /etc/nginx/sites-available/files.yourdomain.com /etc/nginx/sites-enabled/
</span></span><span style="display:flex;"><span>sudo nginx -t <span style="color:#ff7b72;font-weight:bold">&amp;&amp;</span> sudo systemctl reload nginx
</span></span></code></pre></div><p><code>proxy_pass</code>에 Tailscale VPN IP를 쓰는 게 핵심이다. OCI와 홈서버가 같은 Tailscale 네트워크 안에 있기 때문에 가능한 구성이다.</p>
<p><img alt="Filebrowser 외부 접근 성공" loading="lazy" src="/images/homeserver-03-filebrowser-external.png"></p>
<p>이제 VPN 없이 <code>files.yourdomain.com</code>으로 접속해서 홈서버의 파일을 관리할 수 있다.</p>
<hr>
<h2 id="정리">정리</h2>
<ul>
<li>NTFS 드라이브는 <code>ntfsfix</code>로 더티 플래그 제거 후 마운트해야 읽기/쓰기가 된다</li>
<li>Samba는 파일 탐색기 접근용, Filebrowser는 브라우저 접근용</li>
<li>OCI Nginx 리버스 프록시 + Tailscale VPN 조합으로 홈서버를 외부에 안전하게 노출할 수 있다</li>
</ul>
<p>다음 편에서는 Immich를 설치해서 구글 포토를 대체하는 과정을 다룬다.</p>
]]></content:encoded>
    </item>
    <item>
      <title>노트북으로 홈서버 구축하기 - Ubuntu Server 설치 &#43; Tailscale VPN (2편)</title>
      <link>https://chanyeols.com/posts/part-02-ubuntu-tailscale/</link>
      <pubDate>Tue, 24 Mar 2026 16:00:00 +0900</pubDate>
      <guid>https://chanyeols.com/posts/part-02-ubuntu-tailscale/</guid>
      <description>Ventoy로 Ubuntu Server 24.04를 설치하고, Tailscale VPN으로 홈서버와 OCI 인스턴스를 하나의 사설 네트워크로 묶는 과정을 정리합니다.</description>
      <content:encoded><![CDATA[<h2 id="os-선택">OS 선택</h2>
<p>Windows를 그대로 쓸까, Linux native로 갈까 고민했다. Docker 운영이 메인이고 서버답게 쓰려면 Linux가 맞다. Ubuntu Server 24.04 LTS로 결정했다.</p>
<hr>
<h2 id="ubuntu-server-설치">Ubuntu Server 설치</h2>
<h3 id="준비물">준비물</h3>
<p><strong>Ventoy</strong> — USB를 부팅 드라이브로 만들어주는 도구다. 일반적인 방식은 ISO를 USB에 굽는 방식인데, Ventoy는 USB 하나에 여러 ISO를 넣고 부팅 시 선택할 수 있어서 훨씬 편하다.</p>
<ul>
<li>Ventoy 다운로드: <a href="https://github.com/ventoy/Ventoy/releases">https://github.com/ventoy/Ventoy/releases</a></li>
<li>Ubuntu Server 24.04 LTS ISO 다운로드: <a href="https://ubuntu.com/download/server">https://ubuntu.com/download/server</a></li>
</ul>
<h3 id="설치-순서">설치 순서</h3>
<p><strong>1. Ventoy2Disk.exe 실행</strong></p>
<p>USB를 꽂고 Ventoy2Disk.exe를 실행한다. 사용할 USB를 선택하고 Install을 누른다.</p>
<blockquote>
<p>⚠️ USB에 있는 데이터는 전부 지워지므로 먼저 백업할 것</p>
</blockquote>
<p><img alt="Ventoy2Disk 실행 화면" loading="lazy" src="/images/homeserver-02-ventoy.png"></p>
<p><strong>2. ISO 파일 복사</strong></p>
<p>Install 완료 후 다운로드한 Ubuntu ISO 파일을 USB에 그냥 복사하면 된다.</p>
<p><strong>3. 서버에 USB 꽂고 부팅</strong></p>
<p>Ubuntu를 설치할 노트북에 USB를 꽂고 전원을 켠다. F12를 연타해서 바이오스 부팅 메뉴 진입 후 Ventoy가 설치된 USB를 선택한다.</p>
<p>이후 Ubuntu Server 설치 과정은 안내에 따라 진행하면 된다. 특별히 복잡한 부분은 없다. 파티션은 기본값으로 잡고, SSH 서버 설치 옵션은 체크해두는 게 좋다.</p>
<hr>
<h2 id="tailscale-vpn-연결">Tailscale VPN 연결</h2>
<p>홈서버는 공인 IP가 없어서 외부에서 직접 접근이 안 된다. Tailscale을 사용하면 홈서버와 OCI 인스턴스를 같은 사설 네트워크로 묶을 수 있다.</p>
<h3 id="설치">설치</h3>
<p>Ubuntu 설치 후 터미널에서 아래 명령어를 실행한다.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>curl -fsSL https://tailscale.com/install.sh | sh
</span></span></code></pre></div><p>설치 후 활성화:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo tailscale up
</span></span></code></pre></div><p>실행하면 아래처럼 인증 URL이 출력된다.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>To authenticate, visit:
</span></span><span style="display:flex;"><span>    https://login.tailscale.com/a/xxxxxxxxxxxxxxx
</span></span></code></pre></div><p>URL을 브라우저에서 열어서 Tailscale 계정으로 로그인하면 홈서버가 네트워크에 등록된다.</p>
<h3 id="홈서버-ip-확인">홈서버 IP 확인</h3>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>tailscale ip -4
</span></span></code></pre></div><p>이 IP(예: <code>100.109.108.36</code>)로 이제 어디서든 홈서버에 SSH 접속이 가능하다.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>ssh username@100.109.108.36
</span></span></code></pre></div><h3 id="oci-서버에도-동일하게-설치">OCI 서버에도 동일하게 설치</h3>
<p>OCI 인스턴스에도 위 과정을 그대로 반복해서 Tailscale을 설치하면 두 서버가 같은 사설 네트워크로 묶인다.</p>
<p><img alt="Tailscale 관리 콘솔 - 두 서버가 연결된 화면" loading="lazy" src="/images/homeserver-02-tailscale.png"></p>
<p>설정 완료 후 Tailscale 관리 콘솔에서 홈서버와 OCI 서버가 모두 연결된 것을 확인할 수 있다. 이제 OCI에서 홈서버로, 홈서버에서 OCI로 자유롭게 통신이 가능하다.</p>
<hr>
<h2 id="여기까지의-구성">여기까지의 구성</h2>
<pre tabindex="0"><code>[내 PC / 스마트폰]
       │  Tailscale VPN
       ▼
[홈서버 ThinkPad] ─── Tailscale VPN ─── [OCI 인스턴스]
  100.109.108.36                           100.x.x.x
</code></pre><p>Tailscale이 연결된 것만으로도 이미 꽤 쓸만한 환경이 됐다. 이제 어디서든 홈서버에 SSH로 접속해서 작업할 수 있다.</p>
<p>다음 편에서는 외장 SSD를 마운트하고 Filebrowser로 브라우저에서 파일을 관리하는 환경을 구성한다.</p>
]]></content:encoded>
    </item>
    <item>
      <title>노트북으로 홈서버 구축하기 - 왜 홈서버인가? (1편)</title>
      <link>https://chanyeols.com/posts/part-01-intro/</link>
      <pubDate>Tue, 24 Mar 2026 09:00:00 +0900</pubDate>
      <guid>https://chanyeols.com/posts/part-01-intro/</guid>
      <description>클라우드 대신 집에 남는 노트북으로 홈서버를 구축한 이유와 Tailscale &#43; OCI &#43; Docker로 구성한 전체 아키텍처를 소개합니다.</description>
      <content:encoded><![CDATA[<h2 id="왜-홈서버를-만들게-됐나">왜 홈서버를 만들게 됐나</h2>
<p>개인 블로그를 운영하면서 OCI(Oracle Cloud) 무료 인스턴스 하나를 쭉 써왔다. 1 vCPU, 1GB RAM짜리라 Hugo 정적 블로그 서빙에는 충분했는데, 문제는 점점 하고 싶은 게 늘어난다는 것이다.</p>
<ul>
<li>사진 파일이 쌓이면서 개인 NAS가 필요해졌다</li>
<li>사이드 프로젝트 돌릴 서버가 필요했다</li>
<li>비밀번호 관리도 외부 서비스 말고 직접 하고 싶었다</li>
</ul>
<p>클라우드로 커버하면 되지 않냐 싶지만, 스토리지가 좀 붙으면 요금이 눈에 띄게 올라간다. 마침 집에 안 쓰는 노트북이 하나 있었고, 거기서부터 홈서버 구축기가 시작됐다.</p>
<hr>
<h2 id="하드웨어-선택-데스크탑-대신-노트북">하드웨어 선택: 데스크탑 대신 노트북</h2>
<p>처음엔 집에 있는 데스크탑을 홈서버로 쓸까 생각했다. 그런데 데스크탑을 24시간 켜두면 전기세가 만만치 않다.</p>
<p>마침 Ryzen 5 5600U, RAM 16GB짜리 <strong>ThinkPad E15 Gen3</strong>가 있었다. 노트북이라 전력 소비가 훨씬 낮고, 배터리가 있어서 정전 시 UPS 역할도 된다. 성능도 홈서버 용도로는 충분하다.</p>
<table>
  <thead>
      <tr>
          <th>항목</th>
          <th>사양</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>CPU</td>
          <td>Ryzen 5 5600U</td>
      </tr>
      <tr>
          <td>RAM</td>
          <td>16GB</td>
      </tr>
      <tr>
          <td>저장장치</td>
          <td>256GB SSD (OS/Docker) + 1TB SSD (/mnt/data)</td>
      </tr>
      <tr>
          <td>OS</td>
          <td>Ubuntu Server 24.04 LTS</td>
      </tr>
  </tbody>
</table>
<p>유선 랜 포트를 따로 뚫기가 애매해서 일단 와이파이로 연결해서 운영 중이다.</p>
<hr>
<h2 id="전체-아키텍처">전체 아키텍처</h2>
<p>핵심은 세 가지다.</p>
<p><strong>Tailscale VPN</strong> — 홈서버는 공인 IP가 없어서 외부에서 직접 접근이 안 된다. Tailscale로 홈서버와 OCI 인스턴스를 같은 사설 네트워크로 묶었다. 이렇게 하면 어디서든 VPN으로 홈서버에 접근할 수 있다.</p>
<p><strong>OCI Nginx 리버스 프록시</strong> — 기존에 Hugo 블로그를 서빙하던 OCI 인스턴스의 Nginx를 리버스 프록시로 활용한다. 외부에서 <code>photo.yourdomain.com</code>으로 접근하면 Nginx가 Tailscale VPN을 통해 홈서버의 Immich로 연결해주는 구조다.</p>
<p><strong>Cloudflare DNS</strong> — 도메인 관리는 Cloudflare에서 한다. 서브도메인 추가할 때마다 Cloudflare에서 레코드 하나 추가하고 Nginx 설정 추가하면 끝이다.</p>
<p><img alt="전체 아키텍처 구성도" loading="lazy" src="/images/homeserver-01-architecture.png"></p>
<hr>
<h2 id="운영-중인-서비스">운영 중인 서비스</h2>
<p>현재 홈서버에서 돌아가는 서비스 목록이다.</p>
<table>
  <thead>
      <tr>
          <th>포트</th>
          <th>서비스</th>
          <th>접근 방식</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>2283</td>
          <td>Immich (사진 관리)</td>
          <td>도메인 (OCI 프록시)</td>
      </tr>
      <tr>
          <td>9090</td>
          <td>Filebrowser (파일 관리)</td>
          <td>도메인 (OCI 프록시)</td>
      </tr>
      <tr>
          <td>11000</td>
          <td>Vaultwarden (비밀번호)</td>
          <td>도메인 (OCI 프록시)</td>
      </tr>
      <tr>
          <td>13000</td>
          <td>Grafana (모니터링)</td>
          <td>Tailscale VPN</td>
      </tr>
      <tr>
          <td>19000</td>
          <td>Portainer (Docker GUI)</td>
          <td>Tailscale VPN</td>
      </tr>
      <tr>
          <td>19090</td>
          <td>Prometheus</td>
          <td>Tailscale VPN</td>
      </tr>
      <tr>
          <td>19100</td>
          <td>Node Exporter</td>
          <td>내부 수집용</td>
      </tr>
      <tr>
          <td>23000</td>
          <td>컨테이너 대시보드 (React)</td>
          <td>Tailscale VPN</td>
      </tr>
      <tr>
          <td>28080</td>
          <td>컨테이너 대시보드 (Spring)</td>
          <td>Tailscale VPN</td>
      </tr>
  </tbody>
</table>
<p>Grafana, Portainer처럼 민감한 도구는 VPN 안에서만 접근하고 외부에는 열지 않았다. Docker 소켓에 직접 접근하는 툴을 외부에 노출하면 보안상 위험하기 때문이다.</p>
<hr>
<h2 id="시리즈-구성">시리즈 구성</h2>
<p>이 구축기는 총 12편으로 구성된다.</p>
<ol>
<li><strong>왜 홈서버인가? + 전체 아키텍처</strong> ← 지금 여기</li>
<li><a href="/posts/part-02-ubuntu-tailscale">Ubuntu Server 설치 + Tailscale VPN</a></li>
<li><a href="/posts/part-03-ssd-filebrowser">외장 SSD 마운트 + Filebrowser 원격 파일 관리</a></li>
<li><a href="/posts/part-04-immich">Immich로 구글 포토 대체하기</a></li>
<li><a href="/posts/part-05-vaultwarden">Vaultwarden으로 비밀번호 자체 호스팅</a></li>
<li><a href="/posts/part-06-portainer">Portainer CE로 Docker GUI 관리</a></li>
<li><a href="/posts/part-07-monitoring">Grafana + Prometheus로 홈서버 모니터링</a></li>
<li><a href="/posts/part-08-fail2ban">Fail2ban으로 SSH 브루트포스 차단</a></li>
<li><a href="/posts/part-09-ssl">certbot &ndash;expand로 SSL 서브도메인 추가</a></li>
<li><a href="/posts/part-10-postgresql-backup">PostgreSQL 자동 백업 (pg_dump + cron)</a></li>
<li><a href="/posts/part-11-tuning">TLP + thinkfan + Swap 튜닝으로 운영 최적화</a></li>
<li><a href="/posts/part-12-dashboard">직접 만든 컨테이너 대시보드 (Spring Boot + React + SSE)</a></li>
</ol>
<p>다음 편에서는 Ubuntu Server 설치부터 Tailscale VPN 연결까지 다룬다.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
