<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://blog.sakura-idc.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://blog.sakura-idc.com/" rel="alternate" type="text/html" /><updated>2026-04-19T01:40:21+09:00</updated><id>https://blog.sakura-idc.com/feed.xml</id><title type="html">사쿠라 호스팅 - 한국어 지원 일본웹호스팅 사쿠라호스팅</title><subtitle>사쿠라 호스팅 - 한국어를 지원하는 일본 웹호스팅, 일본 가상서버호스팅. 일본서버에서 제공되는 서버구축없이 즉시 개통 가능한 호스팅 서비스. 사쿠라IDC의 사쿠라호스팅</subtitle><author><name>사쿠라호스팅</name></author><entry><title type="html">Rocky Linux 9 SELinux 실무 트러블슈팅</title><link href="https://blog.sakura-idc.com/linux/%EB%B3%B4%EC%95%88/Rocky-Linux-9-SELinux-%EC%8B%A4%EB%AC%B4-%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85/" rel="alternate" type="text/html" title="Rocky Linux 9 SELinux 실무 트러블슈팅" /><published>2026-04-15T10:00:00+09:00</published><updated>2026-04-15T10:00:00+09:00</updated><id>https://blog.sakura-idc.com/linux/%EB%B3%B4%EC%95%88/Rocky-Linux-9-SELinux-%EC%8B%A4%EB%AC%B4-%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85</id><content type="html" xml:base="https://blog.sakura-idc.com/linux/%EB%B3%B4%EC%95%88/Rocky-Linux-9-SELinux-%EC%8B%A4%EB%AC%B4-%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85/"><![CDATA[<p>“포트를 8080으로 바꿨는데 서비스가 시작은 되고 접속이 안 된다”, “웹 서버가 <code class="language-plaintext highlighter-rouge">/data</code> 경로의 파일을 못 읽는다” 같은 증상의 상당수는 SELinux의 거부 로그를 확인하지 않아 생긴 일입니다. Rocky Linux 9는 enforcing이 기본이고 <code class="language-plaintext highlighter-rouge">targeted</code> 정책이 활성화돼 있으므로, 운영자는 문제가 생겼을 때 먼저 audit 로그를 읽을 줄 알아야 합니다.</p>

<h2 id="1-현재-상태-빠르게-파악">1. 현재 상태 빠르게 파악</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 현재 모드</span>
getenforce         <span class="c"># Enforcing / Permissive / Disabled</span>

<span class="c"># 정책·모드 종합</span>
sestatus

<span class="c"># 최근 거부 건수 요약</span>
ausearch <span class="nt">-m</span> AVC,USER_AVC <span class="nt">-ts</span> recent <span class="nt">--format</span> text | <span class="nb">head</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">Enforcing</code>에서 거부가 자꾸 발생한다면, 정책을 건드리기 전에 로그를 먼저 읽는 것이 순서입니다.</p>

<h2 id="2-audit-로그-해석하는-최소-루틴">2. audit 로그 해석하는 최소 루틴</h2>

<h3 id="2-1-무엇이-거부됐는지-찾기">2-1. 무엇이 거부됐는지 찾기</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 최근 10분치 AVC 거부</span>
ausearch <span class="nt">-m</span> AVC <span class="nt">-ts</span> recent

<span class="c"># 특정 서비스 관련</span>
ausearch <span class="nt">-m</span> AVC <span class="nt">-c</span> nginx

<span class="c"># 타임스탬프로 범위 지정</span>
ausearch <span class="nt">-m</span> AVC <span class="nt">-ts</span> today <span class="nt">-te</span> now
</code></pre></div></div>

<p>일반적인 출력 형식은 다음과 같습니다.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>type=AVC msg=audit(1728720012.345:678): avc: denied { write } for
  pid=1234 comm="nginx" name="uploads" dev="dm-0" ino=98765
  scontext=system_u:system_r:httpd_t:s0
  tcontext=system_u:object_r:var_t:s0
  tclass=dir permissive=0
</code></pre></div></div>

<p>읽을 때 주목해야 할 세 가지:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">denied { ... }</code> — 어떤 권한이 거부됐는지</li>
  <li><code class="language-plaintext highlighter-rouge">scontext</code> — 누가(예: <code class="language-plaintext highlighter-rouge">httpd_t</code>)</li>
  <li><code class="language-plaintext highlighter-rouge">tcontext</code>, <code class="language-plaintext highlighter-rouge">tclass</code> — 무엇에 대해(예: <code class="language-plaintext highlighter-rouge">var_t</code> 타입의 디렉터리)</li>
</ul>

<h3 id="2-2-사람이-읽는-요약과-권장-해결책">2-2. 사람이 읽는 요약과 권장 해결책</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 간단 요약</span>
sealert <span class="nt">-a</span> /var/log/audit/audit.log | <span class="nb">head</span> <span class="nt">-80</span>

<span class="c"># 특정 거부에 대한 추천 정책 생성</span>
ausearch <span class="nt">-m</span> AVC <span class="nt">-ts</span> recent | audit2allow <span class="nt">-a</span> <span class="nt">-M</span> nginx_uploads
<span class="nb">ls </span>nginx_uploads.pp nginx_uploads.te
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">audit2allow</code>의 출력은 참고용이지 그대로 적용하는 것은 위험합니다. 꼭 <code class="language-plaintext highlighter-rouge">.te</code> 파일을 열어 허용되는 범위를 확인한 뒤 로드합니다.</p>

<h2 id="3-자주-마주치는-시나리오">3. 자주 마주치는 시나리오</h2>

<h3 id="3-1-포트-변경">3-1. 포트 변경</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 웹 서버를 8080 대신 8181로 서비스</span>
semanage port <span class="nt">-l</span> | <span class="nb">grep</span> <span class="nt">-E</span> <span class="s1">'http_port_t|8181'</span>
semanage port <span class="nt">-a</span> <span class="nt">-t</span> http_port_t <span class="nt">-p</span> tcp 8181
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">-a</code> 대신 이미 다른 타입에 할당된 포트라면 <code class="language-plaintext highlighter-rouge">-m</code>(modify)를 써야 합니다.</p>

<h3 id="3-2-커스텀-경로의-파일-컨텍스트">3-2. 커스텀 경로의 파일 컨텍스트</h3>

<p>데이터 디렉터리를 <code class="language-plaintext highlighter-rouge">/var/www</code> 외 위치에 두는 경우:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 영구 규칙 등록</span>
semanage fcontext <span class="nt">-a</span> <span class="nt">-t</span> httpd_sys_content_t <span class="s1">'/srv/app(/.*)?'</span>

<span class="c"># 기존 파일에 적용</span>
restorecon <span class="nt">-Rv</span> /srv/app

<span class="c"># 확인</span>
<span class="nb">ls</span> <span class="nt">-lZ</span> /srv/app | <span class="nb">head</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">-a</code> 없이 <code class="language-plaintext highlighter-rouge">-l | grep /srv/app</code>으로 현재 등록 상태를 먼저 살펴봅니다. <code class="language-plaintext highlighter-rouge">/data/logs</code>처럼 쓰기가 필요한 경로라면 <code class="language-plaintext highlighter-rouge">httpd_sys_rw_content_t</code>를 씁니다.</p>

<h3 id="3-3-부울boolean-토글">3-3. 부울(boolean) 토글</h3>

<p>SELinux 정책에는 상황별 on/off 스위치가 많습니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 설명과 함께 모두 보기</span>
getsebool <span class="nt">-a</span> | <span class="nb">grep</span> <span class="nt">-i</span> httpd | <span class="nb">head</span>

<span class="c"># 외부 네트워크로 나가는 HTTP 요청 허용</span>
setsebool <span class="nt">-P</span> httpd_can_network_connect on

<span class="c"># DB 커넥션 허용</span>
setsebool <span class="nt">-P</span> httpd_can_network_connect_db on
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">-P</code>는 재부팅 후에도 유지됩니다.</p>

<h3 id="3-4-일시적-허용-모드--단-끄지-말고-전환">3-4. 일시적 허용 모드 — 단, 끄지 말고 전환</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 특정 도메인만 일시적으로 permissive로 전환</span>
semanage permissive <span class="nt">-a</span> httpd_t

<span class="c"># 되돌리기</span>
semanage permissive <span class="nt">-d</span> httpd_t
</code></pre></div></div>

<p>전체를 <code class="language-plaintext highlighter-rouge">setenforce 0</code>으로 끄는 것보다, 문제 도메인만 좁혀서 거부 로그를 수집하고 정책을 만든 뒤 다시 enforcing으로 돌리는 흐름이 안전합니다.</p>

<h3 id="3-5-커스텀-정책-모듈">3-5. 커스텀 정책 모듈</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 문제 상황 재현 → 로그 수집 → 정책 생성</span>
setenforce 0
systemctl restart myapp
<span class="c"># ... 여기서 문제 재현 ...</span>
setenforce 1

ausearch <span class="nt">-m</span> AVC <span class="nt">-c</span> myapp | audit2allow <span class="nt">-M</span> myapp_local
semodule <span class="nt">-i</span> myapp_local.pp

<span class="c"># 로드된 모듈 확인</span>
semodule <span class="nt">-l</span> | <span class="nb">grep </span>myapp_local

<span class="c"># 롤백</span>
semodule <span class="nt">-r</span> myapp_local
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">audit2allow</code>가 생성한 <code class="language-plaintext highlighter-rouge">.te</code> 파일은 반드시 열어 수정합니다. 예를 들어 <code class="language-plaintext highlighter-rouge">allow myapp_t self:capability sys_admin;</code> 같은 광범위한 권한이 들어가 있다면, 실제 필요한 것만 남기고 나머지는 지워 다시 컴파일합니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>checkmodule <span class="nt">-M</span> <span class="nt">-m</span> <span class="nt">-o</span> myapp_local.mod myapp_local.te
semodule_package <span class="nt">-o</span> myapp_local.pp <span class="nt">-m</span> myapp_local.mod
semodule <span class="nt">-i</span> myapp_local.pp
</code></pre></div></div>

<h2 id="4-컨테이너-환경">4. 컨테이너 환경</h2>

<p>Podman·Docker가 설치된 Rocky 9에서는 컨테이너 내 프로세스가 <code class="language-plaintext highlighter-rouge">container_t</code>로 라벨됩니다. 호스트 디렉터리를 바인드 마운트할 때는 접미사 <code class="language-plaintext highlighter-rouge">:z</code>(공유) 또는 <code class="language-plaintext highlighter-rouge">:Z</code>(전용)를 붙여 자동 리라벨을 활용합니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 자동 리라벨로 마운트</span>
podman run <span class="nt">-d</span> <span class="se">\</span>
  <span class="nt">-v</span> /srv/app/data:/data:Z <span class="se">\</span>
  <span class="nt">-p</span> 8080:8080 <span class="se">\</span>
  myapp:latest
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">:Z</code>는 호스트 디렉터리의 컨텍스트를 해당 컨테이너 전용 라벨로 바꾸므로, 여러 컨테이너가 같은 디렉터리를 공유한다면 <code class="language-plaintext highlighter-rouge">:z</code>를 써야 합니다.</p>

<h2 id="5-빠른-진단-알리아스">5. 빠른 진단 알리아스</h2>

<p>매번 긴 명령을 치기 번거로우면 다음 함수를 <code class="language-plaintext highlighter-rouge">~/.bashrc</code>에 두면 편합니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 최근 5분간의 SELinux 거부 요약</span>
selrecent<span class="o">()</span> <span class="o">{</span>
    ausearch <span class="nt">-m</span> AVC,USER_AVC <span class="nt">-ts</span> recent 2&gt;/dev/null <span class="se">\</span>
      | audit2allow <span class="nt">-a</span> <span class="se">\</span>
      | <span class="nb">head</span> <span class="nt">-40</span>
<span class="o">}</span>

<span class="c"># 특정 서비스의 거부만</span>
selfor<span class="o">()</span> <span class="o">{</span>
    ausearch <span class="nt">-m</span> AVC <span class="nt">-c</span> <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span> <span class="nt">-ts</span> today 2&gt;/dev/null | audit2allow <span class="nt">-a</span>
<span class="o">}</span>
</code></pre></div></div>

<h2 id="6-영구-비활성화는-최후의-선택">6. 영구 비활성화는 최후의 선택</h2>

<p><code class="language-plaintext highlighter-rouge">/etc/selinux/config</code>에서 <code class="language-plaintext highlighter-rouge">SELINUX=disabled</code>로 두면 보호 기능이 완전히 사라집니다. 더불어 Rocky 9에서는 <code class="language-plaintext highlighter-rouge">disabled</code>로 부팅 시 파일 컨텍스트가 갱신되지 않아, 나중에 다시 활성화하려 할 때 전체 재라벨링(<code class="language-plaintext highlighter-rouge">fixfiles -F onboot</code> 후 재부팅)이 필요합니다. 트러블슈팅 시간 대신 비활성화를 택하는 흐름은 장기적으로 더 많은 비용을 불러옵니다.</p>

<hr />

<p>SELinux의 거부 메시지는 얼핏 불친절해 보이지만, <code class="language-plaintext highlighter-rouge">ausearch + audit2allow + sealert</code> 세 도구를 익히면 대부분의 문제는 10분 안에 추적됩니다. 운영 중인 Rocky 9 서버가 있다면, 장애가 나기 전에 한 번씩 <code class="language-plaintext highlighter-rouge">sealert -a</code>를 돌려 누적된 경고를 점검해 두는 습관이 든든합니다.</p>]]></content><author><name>사쿠라호스팅</name></author><category term="Linux" /><category term="보안" /><category term="SELinux" /><category term="Rocky Linux" /><category term="보안" /><category term="트러블슈팅" /><category term="audit" /><summary type="html"><![CDATA[Rocky Linux 9는 SELinux가 enforcing 기본값이라, 서비스가 겉보기엔 멀쩡히 떠 있어도 요청이 거부되는 경우가 종종 생깁니다. audit 로그 읽는 법부터 포트·경로·부울 변경, 커스텀 정책 생성까지 실무에서 바로 쓰는 트러블슈팅 루틴을 정리합니다.]]></summary></entry><entry><title type="html">Nginx에 HTTP/3(QUIC) 적용하고 성능 비교하기</title><link href="https://blog.sakura-idc.com/%EC%9B%B9%EC%84%9C%EB%B2%84/%EC%84%B1%EB%8A%A5/Nginx%EC%97%90-HTTP3-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B3%A0-%EC%84%B1%EB%8A%A5-%EB%B9%84%EA%B5%90%ED%95%98%EA%B8%B0/" rel="alternate" type="text/html" title="Nginx에 HTTP/3(QUIC) 적용하고 성능 비교하기" /><published>2026-04-05T10:00:00+09:00</published><updated>2026-04-05T10:00:00+09:00</updated><id>https://blog.sakura-idc.com/%EC%9B%B9%EC%84%9C%EB%B2%84/%EC%84%B1%EB%8A%A5/Nginx%EC%97%90-HTTP3-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B3%A0-%EC%84%B1%EB%8A%A5-%EB%B9%84%EA%B5%90%ED%95%98%EA%B8%B0</id><content type="html" xml:base="https://blog.sakura-idc.com/%EC%9B%B9%EC%84%9C%EB%B2%84/%EC%84%B1%EB%8A%A5/Nginx%EC%97%90-HTTP3-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B3%A0-%EC%84%B1%EB%8A%A5-%EB%B9%84%EA%B5%90%ED%95%98%EA%B8%B0/"><![CDATA[<p>HTTP/3는 TCP 대신 UDP 위에서 동작하는 QUIC 프로토콜을 사용합니다. 첫 핸드셰이크가 1-RTT(재방문은 0-RTT)로 끝나고, 한 연결 안의 여러 스트림이 서로 블록되지 않는 구조이기 때문에 패킷 손실이 잦은 모바일 회선이나 장거리 국제 회선에서 체감 성능 개선이 특히 큽니다. Nginx 1.25부터 QUIC가 메인라인에 포함되었고, 1.27 이상에서는 대부분의 배포판 패키지로도 바로 쓸 수 있게 되면서 도입 문턱이 크게 낮아졌습니다.</p>

<h2 id="1-전제와-버전-확인">1. 전제와 버전 확인</h2>

<ul>
  <li>Nginx 1.25 이상 (1.27+ 권장)</li>
  <li>OpenSSL 3.x 또는 QUIC를 지원하는 TLS 라이브러리(BoringSSL/quictls)</li>
  <li>UDP/443 포트 인바운드 허용</li>
  <li>TLS 1.3 서버 인증서</li>
</ul>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nginx <span class="nt">-V</span> 2&gt;&amp;1 | <span class="nb">tr</span> <span class="s1">' '</span> <span class="s1">'\n'</span> | <span class="nb">grep</span> <span class="nt">-E</span> <span class="s1">'http_v3|quic|ssl'</span>
<span class="c"># 출력 예: --with-http_v3_module  --with-http_ssl_module</span>
</code></pre></div></div>

<p>Rocky 9·Ubuntu 24.04의 기본 패키지는 1.20대라 모듈이 빠져 있는 경우가 많습니다. 공식 저장소 사용을 권장합니다.</p>

<h3 id="공식-저장소로-127-설치-rocky-9">공식 저장소로 1.27 설치 (Rocky 9)</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cat</span> <span class="o">&lt;&lt;</span><span class="sh">'</span><span class="no">EOF</span><span class="sh">' | sudo tee /etc/yum.repos.d/nginx.repo
[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/9/</span><span class="nv">$basearch</span><span class="sh">/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
</span><span class="no">EOF

</span><span class="nb">sudo </span>dnf <span class="nb">install</span> <span class="nt">-y</span> nginx
</code></pre></div></div>

<h2 id="2-최소-동작-설정">2. 최소 동작 설정</h2>

<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">server</span> <span class="p">{</span>
    <span class="c1"># HTTP/2 (TCP 443)</span>
    <span class="kn">listen</span> <span class="mi">443</span> <span class="s">ssl</span><span class="p">;</span>
    <span class="kn">listen</span> <span class="s">[::]:443</span> <span class="s">ssl</span><span class="p">;</span>
    <span class="kn">http2</span> <span class="no">on</span><span class="p">;</span>

    <span class="c1"># HTTP/3 (UDP 443)</span>
    <span class="kn">listen</span> <span class="mi">443</span> <span class="s">quic</span> <span class="s">reuseport</span><span class="p">;</span>
    <span class="kn">listen</span> <span class="s">[::]:443</span> <span class="s">quic</span> <span class="s">reuseport</span><span class="p">;</span>
    <span class="kn">http3</span> <span class="no">on</span><span class="p">;</span>

    <span class="kn">server_name</span> <span class="s">example.com</span><span class="p">;</span>

    <span class="kn">ssl_certificate</span>     <span class="n">/etc/letsencrypt/live/example.com/fullchain.pem</span><span class="p">;</span>
    <span class="kn">ssl_certificate_key</span> <span class="n">/etc/letsencrypt/live/example.com/privkey.pem</span><span class="p">;</span>
    <span class="kn">ssl_protocols</span>       <span class="s">TLSv1.3</span><span class="p">;</span>
    <span class="kn">ssl_ciphers</span>         <span class="s">TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256</span><span class="p">;</span>
    <span class="kn">ssl_prefer_server_ciphers</span> <span class="no">off</span><span class="p">;</span>

    <span class="c1"># 브라우저가 HTTP/3로 업그레이드하도록 광고</span>
    <span class="kn">add_header</span> <span class="s">Alt-Svc</span> <span class="s">'h3=":443"</span><span class="p">;</span> <span class="kn">ma=86400'</span> <span class="s">always</span><span class="p">;</span>

    <span class="c1"># (선택) 0-RTT 허용</span>
    <span class="kn">ssl_early_data</span> <span class="no">on</span><span class="p">;</span>
    <span class="kn">proxy_set_header</span> <span class="s">Early-Data</span> <span class="nv">$ssl_early_data</span><span class="p">;</span>

    <span class="kn">root</span> <span class="n">/var/www/example.com</span><span class="p">;</span>
    <span class="kn">index</span> <span class="s">index.html</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<ul>
  <li><code class="language-plaintext highlighter-rouge">reuseport</code>: 여러 워커가 동일 UDP 포트를 커널 레벨에서 로드밸런싱</li>
  <li><code class="language-plaintext highlighter-rouge">Alt-Svc</code>: 최초엔 HTTP/2로 응답하고, 헤더를 본 브라우저가 다음 요청부터 HTTP/3로 업그레이드</li>
  <li><code class="language-plaintext highlighter-rouge">ssl_early_data on</code>: 0-RTT 허용. Replay 공격 위험이 있는 POST/PUT은 애플리케이션에서 별도 방어 필요</li>
</ul>

<h2 id="3-방화벽">3. 방화벽</h2>

<p>인바운드 UDP 443을 열어야 합니다. 이 부분을 놓쳐서 “curl –http3으로 안 되네” 하는 경우가 실제로 가장 흔합니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># firewalld (Rocky/Alma)</span>
firewall-cmd <span class="nt">--permanent</span> <span class="nt">--add-port</span><span class="o">=</span>443/udp
firewall-cmd <span class="nt">--reload</span>

<span class="c"># ufw (Ubuntu)</span>
ufw allow 443/udp
</code></pre></div></div>

<p>클라우드 환경에서는 VPC 시큐리티 그룹/네트워크 ACL에도 같이 추가해야 합니다.</p>

<h2 id="4-확인">4. 확인</h2>

<h3 id="curl">curl</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># HTTP/3로 강제</span>
curl <span class="nt">-v</span> <span class="nt">--http3</span> https://example.com/ 2&gt;&amp;1 | <span class="nb">grep</span> <span class="nt">-i</span> <span class="s1">'alpn\|HTTP/3'</span>

<span class="c"># 응답 헤더</span>
curl <span class="nt">-sI</span> <span class="nt">--http3</span> https://example.com/ | <span class="nb">head</span>
</code></pre></div></div>

<h3 id="브라우저">브라우저</h3>

<p>크롬 DevTools의 Network 탭 → Protocol 컬럼을 켜면 <code class="language-plaintext highlighter-rouge">h3</code> 표시가 나옵니다. 최초 방문은 보통 <code class="language-plaintext highlighter-rouge">h2</code>이고, 두 번째 요청부터 <code class="language-plaintext highlighter-rouge">h3</code>으로 바뀝니다.</p>

<h2 id="5-성능-비교">5. 성능 비교</h2>

<p>간단한 정적 파일에 대해 서울↔도쿄 회선에서 <code class="language-plaintext highlighter-rouge">curl -w</code> 타이밍을 20회 평균한 예시입니다. 실 결과는 네트워크 상태에 따라 크게 달라집니다.</p>

<table>
  <thead>
    <tr>
      <th>구간</th>
      <th>HTTP/2</th>
      <th>HTTP/3</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>DNS lookup</td>
      <td>12 ms</td>
      <td>12 ms</td>
    </tr>
    <tr>
      <td>TCP connect</td>
      <td>26 ms</td>
      <td>—</td>
    </tr>
    <tr>
      <td>TLS handshake</td>
      <td>38 ms</td>
      <td>24 ms (0-RTT 시 ~0 ms)</td>
    </tr>
    <tr>
      <td>TTFB</td>
      <td>102 ms</td>
      <td>78 ms</td>
    </tr>
    <tr>
      <td>총 전송(1MB)</td>
      <td>220 ms</td>
      <td>185 ms</td>
    </tr>
  </tbody>
</table>

<p>주목할 점은 평균보다 “꼬리 지연(tail latency)”입니다. 3G/저속 Wi-Fi처럼 패킷 손실이 있는 환경에서 HTTP/2는 헤드 오브 라인 블로킹으로 P95가 급격히 나빠지는 반면, HTTP/3는 스트림이 독립적이라 완만합니다.</p>

<h3 id="간단한-벤치-스크립트">간단한 벤치 스크립트</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/bin/env bash</span>
<span class="nv">url</span><span class="o">=</span><span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span>
<span class="k">for </span>proto <span class="k">in </span>http2 http3<span class="p">;</span> <span class="k">do
    </span><span class="nb">echo</span> <span class="s2">"== </span><span class="nv">$proto</span><span class="s2"> =="</span>
    <span class="k">for </span>i <span class="k">in</span> <span class="si">$(</span><span class="nb">seq </span>1 20<span class="si">)</span><span class="p">;</span> <span class="k">do
        </span>curl <span class="nt">-s</span> <span class="nt">-o</span> /dev/null <span class="nt">--</span><span class="s2">"</span><span class="nv">$proto</span><span class="s2">"</span> <span class="se">\</span>
            <span class="nt">-w</span> <span class="s2">"%{time_connect} %{time_appconnect} %{time_starttransfer} %{time_total}</span><span class="se">\n</span><span class="s2">"</span> <span class="se">\</span>
            <span class="s2">"</span><span class="nv">$url</span><span class="s2">"</span>
    <span class="k">done
done</span>
</code></pre></div></div>

<h2 id="6-운영-체크리스트">6. 운영 체크리스트</h2>

<ul>
  <li><strong>MTU 문제</strong>: UDP는 단편화에 취약합니다. 일부 국제 회선에서 1500 바이트 전후 패킷이 드롭되면 HTTP/3만 느려지는 현상이 생깁니다. <code class="language-plaintext highlighter-rouge">quic_gso on;</code>(Nginx 1.25.1+) 설정과 함께 경로 MTU를 확인합니다.</li>
  <li>
    <p><strong>로그</strong>: 기본 access log 포맷에 <code class="language-plaintext highlighter-rouge">$http3</code> 변수가 없으므로 다음처럼 확장합니다.</p>

    <div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">log_format</span> <span class="s">main_h3</span> <span class="s">'</span><span class="nv">$remote_addr</span> <span class="s">-</span> <span class="nv">$remote_user</span> <span class="s">[</span><span class="nv">$time_local</span><span class="s">]</span> <span class="s">'</span>
                   <span class="s">'"</span><span class="nv">$request</span><span class="s">"</span> <span class="nv">$status</span> <span class="nv">$body_bytes_sent</span> <span class="s">'</span>
                   <span class="s">'proto=</span><span class="nv">$server_protocol</span> <span class="s">h3=</span><span class="nv">$http3</span> <span class="s">rt=</span><span class="nv">$request_time</span><span class="s">'</span><span class="p">;</span>
<span class="k">access_log</span> <span class="n">/var/log/nginx/access.log</span> <span class="s">main_h3</span><span class="p">;</span>
</code></pre></div>    </div>
  </li>
  <li><strong>Alt-Svc 캐시</strong>: 서비스를 내렸다가 다시 올릴 때 이전 공지된 포트·ALPN이 계속 시도될 수 있습니다. 포트를 바꾸려면 <code class="language-plaintext highlighter-rouge">Alt-Svc: clear</code>를 일정 기간 보낸 뒤 교체합니다.</li>
  <li><strong>로드밸런서 앞단</strong>: 클라우드 L4 로드밸런서가 UDP를 지원해야 합니다. AWS NLB는 UDP 리스너, GCP Global LB는 <code class="language-plaintext highlighter-rouge">HTTPS(HTTP/3 on)</code> 옵션, CloudFront는 기본 지원.</li>
  <li><strong>프록시 뒤 Nginx</strong>: CloudFront/Cloudflare 뒤에서만 HTTP/3를 제공한다면 오리진 Nginx는 HTTP/2만 서비스해도 사용자 경험은 동일합니다.</li>
</ul>

<hr />

<p>HTTP/3는 “켜기만 하면 빨라지는 스위치”가 아니라, 모바일·장거리 사용자 비중이 높을수록 효과가 커지는 프로토콜입니다. 사용자 지역 분포와 실 측정치를 보고 전환 여부를 판단하시되, 정적 리소스 배포 사이트·SPA·스트리밍 서비스라면 거의 언제나 도입 이득이 큽니다.</p>]]></content><author><name>사쿠라호스팅</name></author><category term="웹서버" /><category term="성능" /><category term="Nginx" /><category term="HTTP/3" /><category term="QUIC" /><category term="TLS" /><category term="성능" /><summary type="html"><![CDATA[Nginx 1.25부터 기본 빌드에 포함된 HTTP/3(QUIC)를 프로덕션에 적용하는 방법을 소개합니다. 패키지 설치, listen 디렉티브, Alt-Svc 헤더, UDP 443 방화벽 개방, 그리고 HTTP/2와의 실제 지연 비교까지 정리합니다.]]></summary></entry><entry><title type="html">PostgreSQL 17 논리 복제(Logical Replication) 실전 가이드</title><link href="https://blog.sakura-idc.com/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4/postgresql/PostgreSQL-17-%EB%85%BC%EB%A6%AC-%EB%B3%B5%EC%A0%9C-%EC%8B%A4%EC%A0%84-%EA%B0%80%EC%9D%B4%EB%93%9C/" rel="alternate" type="text/html" title="PostgreSQL 17 논리 복제(Logical Replication) 실전 가이드" /><published>2026-03-24T10:00:00+09:00</published><updated>2026-03-24T10:00:00+09:00</updated><id>https://blog.sakura-idc.com/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4/postgresql/PostgreSQL-17-%EB%85%BC%EB%A6%AC-%EB%B3%B5%EC%A0%9C-%EC%8B%A4%EC%A0%84-%EA%B0%80%EC%9D%B4%EB%93%9C</id><content type="html" xml:base="https://blog.sakura-idc.com/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4/postgresql/PostgreSQL-17-%EB%85%BC%EB%A6%AC-%EB%B3%B5%EC%A0%9C-%EC%8B%A4%EC%A0%84-%EA%B0%80%EC%9D%B4%EB%93%9C/"><![CDATA[<p>PostgreSQL의 복제는 크게 둘로 나뉩니다. 물리 복제(Streaming Replication)는 WAL 블록 단위로 바이트 레벨 동기화하며, 전체 클러스터를 그대로 복사하므로 버전·아키텍처가 같아야 합니다. 논리 복제(Logical Replication)는 WAL을 디코딩해 INSERT/UPDATE/DELETE 수준으로 전달하므로 버전·스키마가 달라도 작동하며, 일부 테이블만 복제하거나 서로 다른 샤드를 합치는 용도로 활용할 수 있습니다. PostgreSQL 17부터는 논리 복제가 failover slot과 시퀀스 복제를 지원하면서 HA 구성에서도 더 적극적으로 쓸 수 있게 됐습니다.</p>

<h2 id="postgresql-17에서-달라진-점">PostgreSQL 17에서 달라진 점</h2>

<ul>
  <li><strong>failover slot</strong>: 논리 슬롯이 스탠바이로 자동 승계되어, 프라이머리 장애 후에도 구독이 깨지지 않음</li>
  <li><strong>양방향 복제(bi-directional) 공식 지원</strong>: origin 필터와 replication identity 확장으로 구성이 단순화</li>
  <li><strong>pg_createsubscriber</strong>: 물리 스탠바이를 논리 구독자로 변환하는 공식 도구</li>
  <li><strong>컬럼 단위 필터링 고도화</strong>: UPDATE 시 특정 컬럼만 전송</li>
  <li><strong>pg_logical_emit_message</strong>: 애플리케이션 메시지를 WAL에 주입해 논리 스트림으로 전달</li>
</ul>

<h2 id="1-전제-조건">1. 전제 조건</h2>

<p>프라이머리(Publisher)와 스탠바이(Subscriber) 모두 다음 설정이 필요합니다.</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># postgresql.conf (Publisher)
</span><span class="py">wal_level</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">logical               # 논리 디코딩 활성화</span>
<span class="py">max_replication_slots</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">10        # 슬롯 예약 수</span>
<span class="py">max_wal_senders</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">10              # wal sender 프로세스 수</span>
<span class="py">wal_keep_size</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">1GB               # 슬롯 없을 때 WAL 보관량</span>
<span class="w">
</span><span class="c"># postgresql.conf (Subscriber)
</span><span class="py">max_logical_replication_workers</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">4</span>
<span class="py">max_worker_processes</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">8</span>
</code></pre></div></div>

<div class="language-conf highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># pg_hba.conf (Publisher)
</span><span class="n">host</span>    <span class="n">replication</span>     <span class="n">replicator</span>     <span class="m">10</span>.<span class="m">0</span>.<span class="m">0</span>.<span class="m">0</span>/<span class="m">24</span>    <span class="n">scram</span>-<span class="n">sha</span>-<span class="m">256</span>
<span class="n">host</span>    <span class="n">appdb</span>           <span class="n">replicator</span>     <span class="m">10</span>.<span class="m">0</span>.<span class="m">0</span>.<span class="m">0</span>/<span class="m">24</span>    <span class="n">scram</span>-<span class="n">sha</span>-<span class="m">256</span>
</code></pre></div></div>

<p>전용 복제 계정을 만들고 <code class="language-plaintext highlighter-rouge">REPLICATION</code> 권한을 부여합니다.</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">-- Publisher</span>
<span class="k">CREATE</span> <span class="k">ROLE</span> <span class="n">replicator</span> <span class="k">WITH</span> <span class="n">LOGIN</span> <span class="n">REPLICATION</span> <span class="n">PASSWORD</span> <span class="s1">'StrongPass123!'</span><span class="p">;</span>
<span class="k">GRANT</span> <span class="k">CONNECT</span> <span class="k">ON</span> <span class="k">DATABASE</span> <span class="n">appdb</span> <span class="k">TO</span> <span class="n">replicator</span><span class="p">;</span>
<span class="err">\</span><span class="k">c</span> <span class="n">appdb</span>
<span class="k">GRANT</span> <span class="k">USAGE</span> <span class="k">ON</span> <span class="k">SCHEMA</span> <span class="k">public</span> <span class="k">TO</span> <span class="n">replicator</span><span class="p">;</span>
<span class="k">GRANT</span> <span class="k">SELECT</span> <span class="k">ON</span> <span class="k">ALL</span> <span class="n">TABLES</span> <span class="k">IN</span> <span class="k">SCHEMA</span> <span class="k">public</span> <span class="k">TO</span> <span class="n">replicator</span><span class="p">;</span>
<span class="k">ALTER</span> <span class="k">DEFAULT</span> <span class="k">PRIVILEGES</span> <span class="k">IN</span> <span class="k">SCHEMA</span> <span class="k">public</span>
  <span class="k">GRANT</span> <span class="k">SELECT</span> <span class="k">ON</span> <span class="n">TABLES</span> <span class="k">TO</span> <span class="n">replicator</span><span class="p">;</span>
</code></pre></div></div>

<h2 id="2-publication-만들기">2. Publication 만들기</h2>

<p>Publication은 “어떤 테이블의 어떤 변경을 내보낼지” 정의합니다.</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">-- Publisher</span>
<span class="c1">-- 전체 테이블 복제</span>
<span class="k">CREATE</span> <span class="n">PUBLICATION</span> <span class="n">pub_all</span> <span class="k">FOR</span> <span class="k">ALL</span> <span class="n">TABLES</span><span class="p">;</span>

<span class="c1">-- 일부 테이블만</span>
<span class="k">CREATE</span> <span class="n">PUBLICATION</span> <span class="n">pub_core</span>
  <span class="k">FOR</span> <span class="k">TABLE</span> <span class="n">users</span><span class="p">,</span> <span class="n">orders</span><span class="p">,</span> <span class="n">payments</span>
  <span class="k">WITH</span> <span class="p">(</span><span class="n">publish</span> <span class="o">=</span> <span class="s1">'insert, update, delete'</span><span class="p">);</span>

<span class="c1">-- 행 필터 (WHERE 절)</span>
<span class="k">CREATE</span> <span class="n">PUBLICATION</span> <span class="n">pub_kr_users</span>
  <span class="k">FOR</span> <span class="k">TABLE</span> <span class="n">users</span> <span class="k">WHERE</span> <span class="p">(</span><span class="n">country_code</span> <span class="o">=</span> <span class="s1">'KR'</span><span class="p">);</span>

<span class="c1">-- 컬럼 필터</span>
<span class="k">CREATE</span> <span class="n">PUBLICATION</span> <span class="n">pub_users_min</span>
  <span class="k">FOR</span> <span class="k">TABLE</span> <span class="n">users</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">email</span><span class="p">,</span> <span class="n">updated_at</span><span class="p">);</span>
</code></pre></div></div>

<h2 id="3-subscription-생성">3. Subscription 생성</h2>

<p>구독자 측에서는 동일한 스키마가 먼저 존재해야 합니다. 최초 스키마는 <code class="language-plaintext highlighter-rouge">pg_dump -s</code>로 복사합니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Publisher에서 스키마만 덤프</span>
pg_dump <span class="nt">-h</span> pub-host <span class="nt">-U</span> postgres <span class="nt">-s</span> appdb <span class="o">&gt;</span> schema.sql

<span class="c"># Subscriber에 적용</span>
psql <span class="nt">-h</span> sub-host <span class="nt">-U</span> postgres <span class="nt">-d</span> appdb <span class="nt">-f</span> schema.sql
</code></pre></div></div>

<p>이후 구독을 생성합니다.</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">-- Subscriber</span>
<span class="k">CREATE</span> <span class="n">SUBSCRIPTION</span> <span class="n">sub_core</span>
  <span class="k">CONNECTION</span> <span class="s1">'host=pub-host port=5432 dbname=appdb user=replicator password=StrongPass123!'</span>
  <span class="n">PUBLICATION</span> <span class="n">pub_core</span>
  <span class="k">WITH</span> <span class="p">(</span>
    <span class="n">copy_data</span>     <span class="o">=</span> <span class="k">true</span><span class="p">,</span>     <span class="c1">-- 최초 스냅샷 복사</span>
    <span class="n">create_slot</span>   <span class="o">=</span> <span class="k">true</span><span class="p">,</span>     <span class="c1">-- Publisher에 슬롯 자동 생성</span>
    <span class="n">slot_name</span>     <span class="o">=</span> <span class="s1">'sub_core_slot'</span><span class="p">,</span>
    <span class="n">failover</span>      <span class="o">=</span> <span class="k">true</span><span class="p">,</span>     <span class="c1">-- PG17: 슬롯을 스탠바이로 승계</span>
    <span class="n">synchronous_commit</span> <span class="o">=</span> <span class="s1">'off'</span>
  <span class="p">);</span>
</code></pre></div></div>

<p>초기 동기화가 끝나면 WAL 기반 실시간 스트리밍으로 전환됩니다.</p>

<h2 id="4-진행-상태-모니터링">4. 진행 상태 모니터링</h2>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">-- Publisher: 슬롯·lag 확인</span>
<span class="k">SELECT</span> <span class="n">slot_name</span><span class="p">,</span> <span class="n">active</span><span class="p">,</span> <span class="n">restart_lsn</span><span class="p">,</span>
       <span class="n">confirmed_flush_lsn</span><span class="p">,</span>
       <span class="n">pg_size_pretty</span><span class="p">(</span>
         <span class="n">pg_wal_lsn_diff</span><span class="p">(</span><span class="n">pg_current_wal_lsn</span><span class="p">(),</span> <span class="n">confirmed_flush_lsn</span><span class="p">)</span>
       <span class="p">)</span> <span class="k">AS</span> <span class="n">lag</span>
<span class="k">FROM</span> <span class="n">pg_replication_slots</span><span class="p">;</span>

<span class="c1">-- Publisher: WAL sender 상태</span>
<span class="k">SELECT</span> <span class="n">application_name</span><span class="p">,</span> <span class="k">state</span><span class="p">,</span> <span class="n">sync_state</span><span class="p">,</span>
       <span class="n">write_lag</span><span class="p">,</span> <span class="n">flush_lag</span><span class="p">,</span> <span class="n">replay_lag</span>
<span class="k">FROM</span> <span class="n">pg_stat_replication</span><span class="p">;</span>

<span class="c1">-- Subscriber: 구독별 수신 상태</span>
<span class="k">SELECT</span> <span class="n">subname</span><span class="p">,</span> <span class="n">received_lsn</span><span class="p">,</span> <span class="n">latest_end_lsn</span><span class="p">,</span>
       <span class="n">latest_end_time</span><span class="p">,</span> <span class="n">last_msg_send_time</span>
<span class="k">FROM</span> <span class="n">pg_stat_subscription</span><span class="p">;</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">pg_stat_replication.replay_lag</code>가 꾸준히 증가하면 구독자 측 I/O·CPU가 병목입니다. <code class="language-plaintext highlighter-rouge">pg_replication_slots.lag</code>가 수 GB 이상으로 쌓이면 Publisher 디스크가 고갈될 수 있으니 경보를 꼭 걸어 둡니다.</p>

<h2 id="5-장애재동기화">5. 장애·재동기화</h2>

<p>구독이 오래 끊어져 WAL이 삭제됐다면 <code class="language-plaintext highlighter-rouge">ALTER SUBSCRIPTION sub_core REFRESH PUBLICATION</code>으로는 복구되지 않습니다. 이때는 구독을 지우고 대상 테이블을 <code class="language-plaintext highlighter-rouge">TRUNCATE</code> 후 재생성합니다.</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">-- Subscriber</span>
<span class="k">ALTER</span> <span class="n">SUBSCRIPTION</span> <span class="n">sub_core</span> <span class="n">DISABLE</span><span class="p">;</span>
<span class="k">ALTER</span> <span class="n">SUBSCRIPTION</span> <span class="n">sub_core</span> <span class="k">SET</span> <span class="p">(</span><span class="n">slot_name</span> <span class="o">=</span> <span class="k">NONE</span><span class="p">);</span>
<span class="k">DROP</span> <span class="n">SUBSCRIPTION</span> <span class="n">sub_core</span><span class="p">;</span>

<span class="c1">-- 대상 테이블 비우기</span>
<span class="k">TRUNCATE</span> <span class="n">users</span><span class="p">,</span> <span class="n">orders</span><span class="p">,</span> <span class="n">payments</span><span class="p">;</span>

<span class="c1">-- 재생성</span>
<span class="k">CREATE</span> <span class="n">SUBSCRIPTION</span> <span class="n">sub_core</span> <span class="p">...;</span>
</code></pre></div></div>

<h2 id="6-주의-사항-체크리스트">6. 주의 사항 체크리스트</h2>

<ul>
  <li><strong>Primary Key 필수</strong>: UPDATE·DELETE를 복제하려면 REPLICA IDENTITY가 필요합니다. 대부분 PK로 충분하지만, PK 없는 테이블은 <code class="language-plaintext highlighter-rouge">ALTER TABLE ... REPLICA IDENTITY FULL</code>이 필요하며 성능 비용이 큽니다.</li>
  <li><strong>DDL은 복제되지 않음</strong>: <code class="language-plaintext highlighter-rouge">CREATE TABLE</code>, <code class="language-plaintext highlighter-rouge">ALTER TABLE</code> 등은 구독자에게 전달되지 않으므로 양쪽에 수동 반영하거나 <a href="https://github.com/mbanck/pglogical_ddl">ddl-trigger 확장</a>을 사용합니다.</li>
  <li><strong>시퀀스 값</strong>: PG17부터 시퀀스 복제가 지원되지만 기본값은 꺼짐(<code class="language-plaintext highlighter-rouge">publish_via_partition_root = false</code>). 필요 시 별도 옵션 지정.</li>
  <li><strong>TOAST 컬럼</strong>: UPDATE 시 변경되지 않은 TOAST 값은 전송되지 않으므로 구독자가 값을 못 받는 경우가 있습니다. <code class="language-plaintext highlighter-rouge">REPLICA IDENTITY FULL</code>로 해결하거나 값이 자주 바뀐다면 복제 외 동기화를 별도로 둡니다.</li>
</ul>

<h2 id="7-pg_createsubscriber로-스탠바이-전환">7. pg_createsubscriber로 스탠바이 전환</h2>

<p>기존 물리 스탠바이를 논리 구독자로 빠르게 변환할 수 있습니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 스탠바이 정지 후 실행</span>
pg_ctl stop <span class="nt">-D</span> /var/lib/pgsql/17/data <span class="nt">-m</span> fast

pg_createsubscriber <span class="se">\</span>
  <span class="nt">--pgdata</span><span class="o">=</span>/var/lib/pgsql/17/data <span class="se">\</span>
  <span class="nt">--publisher-server</span><span class="o">=</span><span class="s1">'host=pub-host dbname=appdb user=postgres'</span> <span class="se">\</span>
  <span class="nt">--publication</span><span class="o">=</span>pub_core <span class="se">\</span>
  <span class="nt">--subscription</span><span class="o">=</span>sub_core <span class="se">\</span>
  <span class="nt">--database</span><span class="o">=</span>appdb

pg_ctl start <span class="nt">-D</span> /var/lib/pgsql/17/data
</code></pre></div></div>

<p>전체 데이터를 재복사하지 않고 기존 물리 복제 상태에서 논리 복제로 전환하므로, 수 TB급 환경에서도 다운타임을 분 단위로 제한할 수 있습니다.</p>

<hr />

<p>논리 복제는 마이그레이션(버전 업그레이드, 리전 이동), 읽기 전용 분석 DB 운영, 샤딩된 데이터를 통합 리포팅 DB로 수집하는 시나리오에 특히 강점이 있습니다. 물리 복제와 배타적 관계가 아니므로 HA는 물리 복제로, 데이터 수집·마이그레이션은 논리 복제로 쓰는 하이브리드 구성이 실무에서 흔합니다.</p>]]></content><author><name>사쿠라호스팅</name></author><category term="데이터베이스" /><category term="PostgreSQL" /><category term="PostgreSQL" /><category term="논리복제" /><category term="Logical Replication" /><category term="이중화" /><category term="데이터베이스" /><summary type="html"><![CDATA[PostgreSQL 17의 논리 복제는 failover slot·양방향 복제·컬럼 단위 필터링 등 실무 친화적인 기능이 추가되면서 물리 복제의 대안으로 확실히 자리 잡았습니다. Publication·Subscription 설정부터 모니터링·주의 사항까지 운영 관점에서 정리합니다.]]></summary></entry><entry><title type="html">Tailscale로 팀 전용 사설 네트워크 5분 안에 구성하기</title><link href="https://blog.sakura-idc.com/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC/%EB%B3%B4%EC%95%88/Tailscale%EB%A1%9C-%ED%8C%80-%EC%A0%84%EC%9A%A9-%EC%82%AC%EC%84%A4-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-5%EB%B6%84-%EC%95%88%EC%97%90-%EA%B5%AC%EC%84%B1%ED%95%98%EA%B8%B0/" rel="alternate" type="text/html" title="Tailscale로 팀 전용 사설 네트워크 5분 안에 구성하기" /><published>2026-03-10T10:00:00+09:00</published><updated>2026-03-10T10:00:00+09:00</updated><id>https://blog.sakura-idc.com/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC/%EB%B3%B4%EC%95%88/Tailscale%EB%A1%9C-%ED%8C%80-%EC%A0%84%EC%9A%A9-%EC%82%AC%EC%84%A4-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-5%EB%B6%84-%EC%95%88%EC%97%90-%EA%B5%AC%EC%84%B1%ED%95%98%EA%B8%B0</id><content type="html" xml:base="https://blog.sakura-idc.com/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC/%EB%B3%B4%EC%95%88/Tailscale%EB%A1%9C-%ED%8C%80-%EC%A0%84%EC%9A%A9-%EC%82%AC%EC%84%A4-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-5%EB%B6%84-%EC%95%88%EC%97%90-%EA%B5%AC%EC%84%B1%ED%95%98%EA%B8%B0/"><![CDATA[<p>원격 근무가 일반화되면서 “사무실에 있는 서버에 어떻게 안전하게 접속할 것인가”는 모든 팀의 공통 과제입니다. 전통적인 OpenVPN·IPsec은 서버 구축·인증서 관리·방화벽 포트 개방까지 상당한 준비가 필요합니다. Tailscale은 WireGuard 위에 컨트롤 플레인을 얹어, 클라이언트 간 직접(P2P) 연결을 자동으로 만들어 주는 Mesh VPN 서비스입니다. 방화벽에 들어오는 포트를 열 필요가 없고, 설치 후 로그인 한 번이면 연결이 됩니다.</p>

<h2 id="왜-tailscale인가">왜 Tailscale인가</h2>

<ul>
  <li><strong>NAT 뚫기 자동화</strong>: STUN·DERP 릴레이를 거쳐 양쪽 모두 NAT 뒤에 있어도 연결 성립</li>
  <li><strong>제로 컨피그 MagicDNS</strong>: 장비 호스트네임이 그대로 도메인처럼 동작</li>
  <li><strong>세분화된 ACL</strong>: JSON 기반으로 “누가 어떤 포트에 접근 가능한지” 정의</li>
  <li><strong>Exit Node / Subnet Router</strong>: 팀 전체 트래픽을 특정 노드로 보내거나, 기존 회사 네트워크와 브릿지</li>
</ul>

<h2 id="1-계정과-테일넷-준비">1. 계정과 테일넷 준비</h2>

<p><a href="https://tailscale.com">tailscale.com</a>에서 Google·Microsoft·GitHub 등으로 로그인하면 자동으로 개인·팀 전용 테일넷(tailnet)이 만들어집니다. 관리자 콘솔에서 조직 도메인을 지정하면 동일 도메인 사용자가 자동으로 같은 테일넷에 합류합니다.</p>

<h2 id="2-리눅스-서버에-설치">2. 리눅스 서버에 설치</h2>

<h3 id="rocky-linux-9--alma">Rocky Linux 9 / Alma</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dnf config-manager <span class="nt">--add-repo</span> https://pkgs.tailscale.com/stable/rhel/9/tailscale.repo
dnf <span class="nb">install</span> <span class="nt">-y</span> tailscale
systemctl <span class="nb">enable</span> <span class="nt">--now</span> tailscaled

<span class="c"># 브라우저 로그인 흐름</span>
tailscale up
</code></pre></div></div>

<h3 id="ubuntu-2204--2404">Ubuntu 22.04 / 24.04</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-fsSL</span> https://tailscale.com/install.sh | sh
<span class="nb">sudo </span>tailscale up
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">tailscale up</code>을 실행하면 한 번 사용 URL이 출력됩니다. 브라우저에서 열어 로그인하면 해당 머신이 테일넷에 합류합니다.</p>

<h3 id="macos--windows">macOS / Windows</h3>

<p>Mac App Store 또는 <a href="https://tailscale.com/download">tailscale.com/download</a>에서 GUI 클라이언트를 설치하고 같은 계정으로 로그인하면 끝입니다.</p>

<h2 id="3-연결-확인과-magicdns">3. 연결 확인과 MagicDNS</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 현재 테일넷 상태</span>
tailscale status

<span class="c"># 각 장비 IP (100.x.x.x 대역)</span>
tailscale ip <span class="nt">-4</span>

<span class="c"># MagicDNS로 바로 접근</span>
ssh user@web-server
ping db-01
</code></pre></div></div>

<p>MagicDNS가 켜져 있으면 <code class="language-plaintext highlighter-rouge">100.x.x.x</code> IP를 외울 필요 없이 장비 이름으로 접근할 수 있습니다. 관리자 콘솔의 <code class="language-plaintext highlighter-rouge">DNS</code> 탭에서 활성화합니다.</p>

<h2 id="4-acl로-접근-권한-세분화">4. ACL로 접근 권한 세분화</h2>

<p>Tailscale의 가장 강력한 기능은 JSON ACL입니다. 관리자 콘솔의 <code class="language-plaintext highlighter-rouge">Access Controls</code>에서 다음처럼 작성합니다.</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"tagOwners"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"tag:prod"</span><span class="p">:</span><span class="w">    </span><span class="p">[</span><span class="s2">"group:sre"</span><span class="p">],</span><span class="w">
    </span><span class="nl">"tag:dev"</span><span class="p">:</span><span class="w">     </span><span class="p">[</span><span class="s2">"group:engineering"</span><span class="p">],</span><span class="w">
    </span><span class="nl">"tag:db"</span><span class="p">:</span><span class="w">      </span><span class="p">[</span><span class="s2">"group:sre"</span><span class="p">]</span><span class="w">
  </span><span class="p">},</span><span class="w">

  </span><span class="nl">"groups"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"group:sre"</span><span class="p">:</span><span class="w">         </span><span class="p">[</span><span class="s2">"alice@example.com"</span><span class="p">,</span><span class="w"> </span><span class="s2">"bob@example.com"</span><span class="p">],</span><span class="w">
    </span><span class="nl">"group:engineering"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"group:sre"</span><span class="p">,</span><span class="w"> </span><span class="s2">"carol@example.com"</span><span class="p">]</span><span class="w">
  </span><span class="p">},</span><span class="w">

  </span><span class="nl">"acls"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w"> </span><span class="nl">"action"</span><span class="p">:</span><span class="w"> </span><span class="s2">"accept"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"src"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"group:sre"</span><span class="p">],</span><span class="w">
      </span><span class="nl">"dst"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"tag:prod:*"</span><span class="p">,</span><span class="w"> </span><span class="s2">"tag:db:5432"</span><span class="p">]</span><span class="w"> </span><span class="p">},</span><span class="w">

    </span><span class="p">{</span><span class="w"> </span><span class="nl">"action"</span><span class="p">:</span><span class="w"> </span><span class="s2">"accept"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"src"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"group:engineering"</span><span class="p">],</span><span class="w">
      </span><span class="nl">"dst"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"tag:dev:*"</span><span class="p">]</span><span class="w"> </span><span class="p">}</span><span class="w">
  </span><span class="p">],</span><span class="w">

  </span><span class="nl">"ssh"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w"> </span><span class="nl">"action"</span><span class="p">:</span><span class="w"> </span><span class="s2">"check"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"src"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"group:sre"</span><span class="p">],</span><span class="w">
      </span><span class="nl">"dst"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"tag:prod"</span><span class="p">],</span><span class="w">
      </span><span class="nl">"users"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"root"</span><span class="p">,</span><span class="w"> </span><span class="s2">"user"</span><span class="p">]</span><span class="w"> </span><span class="p">}</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<ul>
  <li>SRE 그룹은 프로덕션과 DB(5432 포트)에 접근</li>
  <li>엔지니어링 그룹은 개발 서버에만 접근</li>
  <li>SRE는 프로덕션에 Tailscale SSH로 로그인 가능(2FA check 필요)</li>
</ul>

<p>장비에 태그를 부여하려면 <code class="language-plaintext highlighter-rouge">tailscale up --advertise-tags=tag:prod</code> 옵션을 씁니다.</p>

<h2 id="5-exit-node--팀-트래픽을-특정-노드로-나가게-하기">5. Exit Node — 팀 트래픽을 특정 노드로 나가게 하기</h2>

<p>고정 IP가 필요한 SaaS IP 화이트리스트, 지역 제한 우회 등에 유용합니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 서버에서 Exit Node로 광고</span>
<span class="nb">sudo </span>tailscale <span class="nb">set</span> <span class="nt">--advertise-exit-node</span>

<span class="c"># 관리자 콘솔에서 해당 노드의 Exit Node를 승인한 후,</span>
<span class="c"># 클라이언트에서 사용</span>
<span class="nb">sudo </span>tailscale <span class="nb">set</span> <span class="nt">--exit-node</span><span class="o">=</span>100.64.0.10 <span class="nt">--exit-node-allow-lan-access</span><span class="o">=</span><span class="nb">true</span>
</code></pre></div></div>

<h2 id="6-subnet-router--기존-사내-lan과-브릿지">6. Subnet Router — 기존 사내 LAN과 브릿지</h2>

<p>사내 <code class="language-plaintext highlighter-rouge">192.168.10.0/24</code> 대역의 프린터·NAS까지 외부에서 접근하고 싶을 때 사용합니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 라우터 역할을 할 노드에서 IP 포워딩 활성화</span>
<span class="nb">echo</span> <span class="s1">'net.ipv4.ip_forward = 1'</span> | <span class="nb">sudo tee</span> <span class="nt">-a</span> /etc/sysctl.d/99-tailscale.conf
<span class="nb">echo</span> <span class="s1">'net.ipv6.conf.all.forwarding = 1'</span> | <span class="nb">sudo tee</span> <span class="nt">-a</span> /etc/sysctl.d/99-tailscale.conf
<span class="nb">sudo </span>sysctl <span class="nt">-p</span> /etc/sysctl.d/99-tailscale.conf

<span class="c"># 경로 광고</span>
<span class="nb">sudo </span>tailscale up <span class="nt">--advertise-routes</span><span class="o">=</span>192.168.10.0/24
</code></pre></div></div>

<p>관리자 콘솔에서 광고된 라우트를 승인하면, 다른 테일넷 장비에서 <code class="language-plaintext highlighter-rouge">192.168.10.x</code> 주소에 바로 접근할 수 있습니다.</p>

<h2 id="7-tailscale-ssh--ssh-키-관리-없이-접근">7. Tailscale SSH — SSH 키 관리 없이 접근</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># SSH 허용 플래그와 함께 노드 등록</span>
<span class="nb">sudo </span>tailscale up <span class="nt">--ssh</span>
</code></pre></div></div>

<p>이렇게 하면 원격에서 <code class="language-plaintext highlighter-rouge">tailscale ssh user@web-server</code>로 접근할 수 있고, 키 파일이 아니라 Tailscale ID로 인증합니다. 위 ACL의 <code class="language-plaintext highlighter-rouge">ssh</code> 섹션과 결합하면 프로덕션에 대해 2FA check를 강제할 수 있습니다.</p>

<h2 id="8-운영-팁">8. 운영 팁</h2>

<ul>
  <li><strong>key expiry 끄기</strong>: 무인 서버는 관리자 콘솔에서 <code class="language-plaintext highlighter-rouge">Disable key expiry</code>를 체크해 180일 만료로 인한 갑작스런 연결 단절을 예방합니다.</li>
  <li><strong>device authorization 켜기</strong>: 새 장비 합류 시 관리자 승인을 요구하도록 설정하면 외부인이 조직 계정으로 로그인해도 자동 합류되지 않습니다.</li>
  <li><strong>로그 감사</strong>: <code class="language-plaintext highlighter-rouge">Logs</code> 탭에서 접속·거부 이벤트를 S3나 Panther 등에 스트리밍할 수 있습니다.</li>
  <li><strong>상태 확인</strong>: <code class="language-plaintext highlighter-rouge">tailscale netcheck</code>로 현재 장비의 NAT 유형, DERP 지연을 점검합니다.</li>
</ul>

<hr />

<p>Tailscale은 무료 요금제로도 사용자 3명·장비 100대까지 지원하므로 소규모 팀이나 개인 홈 랩에서 바로 도입할 수 있습니다. 자체 호스팅을 선호한다면 오픈소스 대안인 <a href="https://github.com/juanfont/headscale">Headscale</a>로 동일한 구성을 구축할 수 있습니다.</p>]]></content><author><name>사쿠라호스팅</name></author><category term="네트워크" /><category term="보안" /><category term="Tailscale" /><category term="VPN" /><category term="WireGuard" /><category term="사설네트워크" /><category term="원격접속" /><summary type="html"><![CDATA[WireGuard 기반 Mesh VPN인 Tailscale을 활용해 팀 전용 사설 네트워크를 5분 안에 구축하는 방법을 소개합니다. 설치, MagicDNS, ACL, Exit Node, Subnet Router, Tailscale SSH까지 실무에서 바로 쓰는 구성을 정리합니다.]]></summary></entry><entry><title type="html">Caddy로 자동 HTTPS 웹서버 구축하기</title><link href="https://blog.sakura-idc.com/linux/%EC%9B%B9%EC%84%9C%EB%B2%84/Caddy%EB%A1%9C-%EC%9E%90%EB%8F%99-HTTPS-%EC%9B%B9%EC%84%9C%EB%B2%84-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0/" rel="alternate" type="text/html" title="Caddy로 자동 HTTPS 웹서버 구축하기" /><published>2026-02-25T10:00:00+09:00</published><updated>2026-02-25T10:00:00+09:00</updated><id>https://blog.sakura-idc.com/linux/%EC%9B%B9%EC%84%9C%EB%B2%84/Caddy%EB%A1%9C-%EC%9E%90%EB%8F%99-HTTPS-%EC%9B%B9%EC%84%9C%EB%B2%84-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0</id><content type="html" xml:base="https://blog.sakura-idc.com/linux/%EC%9B%B9%EC%84%9C%EB%B2%84/Caddy%EB%A1%9C-%EC%9E%90%EB%8F%99-HTTPS-%EC%9B%B9%EC%84%9C%EB%B2%84-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0/"><![CDATA[<p>HTTPS 인증서 자동 발급·갱신을 위해 <code class="language-plaintext highlighter-rouge">certbot</code> 크론을 걸고, Nginx 설정을 복사하며 씨름한 경험이 있다면 Caddy를 한 번 써볼 가치가 있습니다. Caddy는 ACME 클라이언트가 서버에 내장돼 있어, 도메인만 지정하면 TLS 인증서 발급부터 자동 갱신까지 모두 처리합니다. 이 글에서는 Caddy 설치, Caddyfile 문법, 리버스 프록시 구성, 그리고 실무에서 자주 마주치는 설정 패턴을 정리합니다.</p>

<h2 id="caddy가-해결하는-문제">Caddy가 해결하는 문제</h2>

<p>Nginx를 기준으로 보면, HTTPS를 갖춘 단순 리버스 프록시 하나를 띄우기 위해 보통 다음이 필요합니다.</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">certbot</code>으로 초기 인증서 발급</li>
  <li>Nginx <code class="language-plaintext highlighter-rouge">server</code> 블록(80/443) 각각 작성</li>
  <li><code class="language-plaintext highlighter-rouge">ssl_certificate</code>, <code class="language-plaintext highlighter-rouge">ssl_certificate_key</code>, OCSP stapling, HSTS 등 수십 줄</li>
  <li><code class="language-plaintext highlighter-rouge">certbot renew</code> 크론 및 hook</li>
</ul>

<p>Caddy는 이 모든 것을 기본값으로 끌어안습니다. 별도 모듈 없이도 현대적 TLS 기본값(HTTP/2, OCSP, HSTS, 자동 리다이렉트)이 켜진 상태로 시작합니다.</p>

<h2 id="설치">설치</h2>

<h3 id="rocky-linux-9">Rocky Linux 9</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 공식 저장소 추가</span>
dnf <span class="nb">install</span> <span class="nt">-y</span> <span class="s1">'dnf-command(copr)'</span>
dnf copr <span class="nb">enable</span> <span class="nt">-y</span> @caddy/caddy
dnf <span class="nb">install</span> <span class="nt">-y</span> caddy

systemctl <span class="nb">enable</span> <span class="nt">--now</span> caddy
</code></pre></div></div>

<h3 id="ubuntu-2204--2404">Ubuntu 22.04 / 24.04</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt <span class="nb">install</span> <span class="nt">-y</span> debian-keyring debian-archive-keyring apt-transport-https curl
curl <span class="nt">-1sLf</span> <span class="s1">'https://dl.cloudsmith.io/public/caddy/stable/gpg.key'</span> <span class="se">\</span>
  | <span class="nb">sudo </span>gpg <span class="nt">--dearmor</span> <span class="nt">-o</span> /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl <span class="nt">-1sLf</span> <span class="s1">'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt'</span> <span class="se">\</span>
  | <span class="nb">sudo tee</span> /etc/apt/sources.list.d/caddy-stable.list

<span class="nb">sudo </span>apt update
<span class="nb">sudo </span>apt <span class="nb">install</span> <span class="nt">-y</span> caddy
</code></pre></div></div>

<h2 id="caddyfile-기본-문법">Caddyfile 기본 문법</h2>

<p>기본 설정 파일 위치는 <code class="language-plaintext highlighter-rouge">/etc/caddy/Caddyfile</code>입니다. 가장 단순한 정적 사이트는 두 줄이면 끝입니다.</p>

<pre><code class="language-caddy">example.com {
    root * /var/www/example.com
    file_server
}
</code></pre>

<p>위 설정으로 Caddy는 자동으로 다음을 수행합니다.</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">example.com</code>에 대한 Let’s Encrypt 인증서 발급</li>
  <li>HTTP(80) → HTTPS(443) 자동 리다이렉트</li>
  <li>HTTP/2 활성화 및 TLS 1.3 우선</li>
  <li>90일마다 인증서 자동 갱신</li>
</ol>

<h2 id="리버스-프록시-구성">리버스 프록시 구성</h2>

<p>내부 애플리케이션을 HTTPS로 노출하는 가장 흔한 패턴입니다.</p>

<pre><code class="language-caddy">app.example.com {
    reverse_proxy 127.0.0.1:3000

    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains"
        X-Content-Type-Options "nosniff"
        Referrer-Policy "strict-origin-when-cross-origin"
    }

    encode zstd gzip

    log {
        output file /var/log/caddy/app.log
        format json
    }
}
</code></pre>

<p><code class="language-plaintext highlighter-rouge">reverse_proxy</code> 지시문은 기본적으로 <code class="language-plaintext highlighter-rouge">X-Forwarded-For</code>, <code class="language-plaintext highlighter-rouge">X-Forwarded-Proto</code>, <code class="language-plaintext highlighter-rouge">X-Forwarded-Host</code> 헤더를 설정합니다. 백엔드가 WebSocket을 쓰더라도 별도 설정이 필요 없습니다.</p>

<h3 id="헬스체크와-로드밸런싱">헬스체크와 로드밸런싱</h3>

<pre><code class="language-caddy">api.example.com {
    reverse_proxy 10.0.0.11:8080 10.0.0.12:8080 10.0.0.13:8080 {
        lb_policy       round_robin
        health_uri      /healthz
        health_interval 10s
        health_timeout  3s
        health_status   200
    }
}
</code></pre>

<h2 id="여러-사이트를-한-파일로-운영">여러 사이트를 한 파일로 운영</h2>

<p>단일 Caddyfile에서 수십 개 사이트를 운영해도 문제없습니다.</p>

<pre><code class="language-caddy">{
    email admin@example.com
    # 스테이징 환경에서는 Let's Encrypt staging 사용
    # acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
}

blog.example.com {
    reverse_proxy 127.0.0.1:4000
}

shop.example.com {
    root * /var/www/shop
    file_server
    try_files {path} {path}/ /index.html
}

admin.example.com {
    reverse_proxy unix//run/admin/admin.sock
    basicauth {
        admin JDJhJDEwJEVCNmdaNEg2Ti5iejRMYkF3MFZhZ3VtV3E1SzBWZEZ5Q3UvZ1lKLzNKNldnSkI2d2VKcmtq
    }
}
</code></pre>

<p><code class="language-plaintext highlighter-rouge">basicauth</code>의 해시는 <code class="language-plaintext highlighter-rouge">caddy hash-password</code> 명령으로 생성합니다.</p>

<h2 id="인증서-저장-위치와-마이그레이션">인증서 저장 위치와 마이그레이션</h2>

<p>Caddy는 인증서와 키를 <code class="language-plaintext highlighter-rouge">/var/lib/caddy/.local/share/caddy/certificates/</code> 아래에 저장합니다. 서버 이전 시 이 디렉터리 전체를 복사하면 재발급 없이 인증서를 그대로 옮길 수 있어, Let’s Encrypt rate limit을 피할 수 있습니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 원본 서버</span>
<span class="nb">tar </span>czf caddy-data.tar.gz <span class="nt">-C</span> /var/lib/caddy/.local/share caddy

<span class="c"># 대상 서버</span>
systemctl stop caddy
<span class="nb">tar </span>xzf caddy-data.tar.gz <span class="nt">-C</span> /var/lib/caddy/.local/share
<span class="nb">chown</span> <span class="nt">-R</span> caddy:caddy /var/lib/caddy
systemctl start caddy
</code></pre></div></div>

<h2 id="설정-검증과-무중단-리로드">설정 검증과 무중단 리로드</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 문법 검증</span>
caddy validate <span class="nt">--config</span> /etc/caddy/Caddyfile

<span class="c"># 무중단 리로드 (in-flight 요청 유지)</span>
systemctl reload caddy
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">systemctl reload</code>는 내부적으로 Caddy의 admin API(<code class="language-plaintext highlighter-rouge">/load</code>)를 호출해 새 설정을 적용합니다. 기존 연결은 끊어지지 않습니다.</p>

<h2 id="nginx와-비교할-때-주의할-점">Nginx와 비교할 때 주의할 점</h2>

<table>
  <thead>
    <tr>
      <th>항목</th>
      <th>Caddy</th>
      <th>Nginx</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>자동 HTTPS</td>
      <td>내장</td>
      <td>certbot 별도</td>
    </tr>
    <tr>
      <td>설정 문법</td>
      <td>간결, 기본값 안전</td>
      <td>유연, 기본값 약함</td>
    </tr>
    <tr>
      <td>모듈 확장</td>
      <td>빌드 시 포함 (<code class="language-plaintext highlighter-rouge">xcaddy</code>)</td>
      <td>동적/정적 모듈</td>
    </tr>
    <tr>
      <td>성능</td>
      <td>단일 서버 수준에서 동등</td>
      <td>동등, 튜닝 여지 많음</td>
    </tr>
    <tr>
      <td>상세 튜닝</td>
      <td>제한적</td>
      <td>풍부한 디렉티브</td>
    </tr>
  </tbody>
</table>

<p>트래픽이 매우 많고 세밀한 튜닝이 필요하다면 Nginx가 여전히 유리합니다. 반면 운영자 한 명이 수십 개의 사이트를 돌봐야 하는 상황이라면 Caddy의 설정 간결성은 압도적입니다.</p>

<hr />

<p>자동 HTTPS는 단순히 편의 기능이 아니라 운영 실수로 인증서가 만료되는 사고를 구조적으로 차단합니다. 소규모 서비스, 스테이징·개발 환경, 혹은 내부용 리버스 프록시라면 Caddy를 기본 선택지로 두어도 손해볼 것이 없습니다.</p>]]></content><author><name>사쿠라호스팅</name></author><category term="Linux" /><category term="웹서버" /><category term="Caddy" /><category term="HTTPS" /><category term="Let&apos;s Encrypt" /><category term="리버스프록시" /><category term="자동화" /><summary type="html"><![CDATA[Caddy는 단 한 줄 설정으로 Let's Encrypt 인증서를 자동 발급·갱신해 주는 모던 웹서버입니다. Nginx·Apache 대비 운영 부담을 극적으로 줄이는 Caddy의 설치·Caddyfile 문법·리버스 프록시·멀티 사이트 운영까지 실무 관점으로 정리합니다.]]></summary></entry><entry><title type="html">Windows Server 성능 모니터링 완벽 가이드</title><link href="https://blog.sakura-idc.com/%EC%9C%88%EB%8F%84%EC%9A%B0%20%EC%84%9C%EB%B2%84/%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81/Windows-Server-%EC%84%B1%EB%8A%A5-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81/" rel="alternate" type="text/html" title="Windows Server 성능 모니터링 완벽 가이드" /><published>2026-02-15T10:00:00+09:00</published><updated>2026-02-15T10:00:00+09:00</updated><id>https://blog.sakura-idc.com/%EC%9C%88%EB%8F%84%EC%9A%B0%20%EC%84%9C%EB%B2%84/%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81/Windows-Server-%EC%84%B1%EB%8A%A5-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81</id><content type="html" xml:base="https://blog.sakura-idc.com/%EC%9C%88%EB%8F%84%EC%9A%B0%20%EC%84%9C%EB%B2%84/%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81/Windows-Server-%EC%84%B1%EB%8A%A5-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81/"><![CDATA[<p>서버 성능 문제는 증상이 나타나기 전에 선제적으로 모니터링해야 합니다. Windows Server는 성능 카운터, 이벤트 로그, WMI 등 다양한 모니터링 인터페이스를 제공합니다. 이 글에서는 실무에서 바로 활용 가능한 모니터링 방법을 소개합니다.</p>

<p><img src="/assets/images/2026-02-15/1.jpg" alt="서버 코드 이미지" />
<em><center>Windows Server 성능 지표를 실시간으로 모니터링하는 화면</center></em></p>

<h2 id="성능-카운터-기본-이해">성능 카운터 기본 이해</h2>

<h3 id="핵심-성능-카운터">핵심 성능 카운터</h3>

<table>
  <thead>
    <tr>
      <th>카운터</th>
      <th>정상 범위</th>
      <th>경고 기준</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">\Processor(_Total)\% Processor Time</code></td>
      <td>&lt; 70%</td>
      <td>&gt; 85% 지속</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">\Memory\Available MBytes</code></td>
      <td>&gt; 전체의 20%</td>
      <td>&lt; 10%</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">\PhysicalDisk(_Total)\% Disk Time</code></td>
      <td>&lt; 50%</td>
      <td>&gt; 80%</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">\Network Interface(*)\Bytes Total/sec</code></td>
      <td>&lt; 80% 대역폭</td>
      <td>&gt; 90%</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">\System\Processor Queue Length</code></td>
      <td>&lt; CPU 수 × 2</td>
      <td>&gt; CPU 수 × 4</td>
    </tr>
  </tbody>
</table>

<h2 id="powershell로-실시간-모니터링">PowerShell로 실시간 모니터링</h2>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># CPU 사용률 실시간 확인 (5초 간격)</span><span class="w">
</span><span class="n">Get-Counter</span><span class="w"> </span><span class="nt">-Counter</span><span class="w"> </span><span class="s2">"\Processor(_Total)\% Processor Time"</span><span class="w"> </span><span class="se">`
</span><span class="w">    </span><span class="nt">-SampleInterval</span><span class="w"> </span><span class="nx">5</span><span class="w"> </span><span class="nt">-MaxSamples</span><span class="w"> </span><span class="nx">12</span><span class="w"> </span><span class="o">|</span><span class="w">
    </span><span class="n">ForEach-Object</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nv">$cpu</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">math</span><span class="p">]::</span><span class="n">Round</span><span class="p">(</span><span class="bp">$_</span><span class="o">.</span><span class="n">CounterSamples</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="nf">CookedValue</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
        </span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"</span><span class="si">$(</span><span class="n">Get-Date</span><span class="w"> </span><span class="nt">-Format</span><span class="w"> </span><span class="s1">'HH:mm:ss'</span><span class="p">)</span><span class="s2"> CPU: </span><span class="nv">$cpu</span><span class="s2">%"</span><span class="w">
    </span><span class="p">}</span><span class="w">

</span><span class="c"># 메모리 사용량 확인</span><span class="w">
</span><span class="nv">$os</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-WmiObject</span><span class="w"> </span><span class="nx">Win32_OperatingSystem</span><span class="w">
</span><span class="nv">$totalMB</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">math</span><span class="p">]::</span><span class="n">Round</span><span class="p">(</span><span class="nv">$os</span><span class="o">.</span><span class="nf">TotalVisibleMemorySize</span><span class="w"> </span><span class="n">/</span><span class="w"> </span><span class="nx">1KB</span><span class="p">)</span><span class="w">
</span><span class="nv">$freeMB</span><span class="w">  </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">math</span><span class="p">]::</span><span class="n">Round</span><span class="p">(</span><span class="nv">$os</span><span class="o">.</span><span class="nf">FreePhysicalMemory</span><span class="w"> </span><span class="n">/</span><span class="w"> </span><span class="nx">1KB</span><span class="p">)</span><span class="w">
</span><span class="nv">$usedMB</span><span class="w">  </span><span class="o">=</span><span class="w"> </span><span class="nv">$totalMB</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="nv">$freeMB</span><span class="w">
</span><span class="nv">$usedPct</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">math</span><span class="p">]::</span><span class="n">Round</span><span class="p">(</span><span class="nv">$usedMB</span><span class="w"> </span><span class="n">/</span><span class="w"> </span><span class="nv">$totalMB</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">100</span><span class="p">,</span><span class="w"> </span><span class="nx">1</span><span class="p">)</span><span class="w">

</span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"메모리: </span><span class="nv">${usedMB}</span><span class="s2">MB 사용 / </span><span class="nv">${totalMB}</span><span class="s2">MB 전체 (</span><span class="nv">$usedPct</span><span class="s2">%)"</span><span class="w">
</span></code></pre></div></div>

<h3 id="종합-성능-대시보드">종합 성능 대시보드</h3>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">function</span><span class="w"> </span><span class="nf">Get-ServerPerformance</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="kr">param</span><span class="p">([</span><span class="n">string</span><span class="p">]</span><span class="nv">$ComputerName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">COMPUTERNAME</span><span class="p">)</span><span class="w">

    </span><span class="nv">$counters</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">@(</span><span class="w">
        </span><span class="s2">"\Processor(_Total)\% Processor Time"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"\Memory\Available MBytes"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"\PhysicalDisk(_Total)\Disk Reads/sec"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"\PhysicalDisk(_Total)\Disk Writes/sec"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"\Network Interface(*)\Bytes Total/sec"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"\System\Processor Queue Length"</span><span class="w">
    </span><span class="p">)</span><span class="w">

    </span><span class="nv">$samples</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-Counter</span><span class="w"> </span><span class="nt">-ComputerName</span><span class="w"> </span><span class="nv">$ComputerName</span><span class="w"> </span><span class="se">`
</span><span class="w">        </span><span class="nt">-Counter</span><span class="w"> </span><span class="nv">$counters</span><span class="w"> </span><span class="nt">-SampleInterval</span><span class="w"> </span><span class="nx">3</span><span class="w"> </span><span class="nt">-MaxSamples</span><span class="w"> </span><span class="nx">3</span><span class="w">

    </span><span class="nv">$avg</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$samples</span><span class="o">.</span><span class="nf">CounterSamples</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Group-Object</span><span class="w"> </span><span class="nx">Path</span><span class="w"> </span><span class="o">|</span><span class="w">
        </span><span class="n">ForEach-Object</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="p">[</span><span class="n">PSCustomObject</span><span class="p">]@{</span><span class="w">
                </span><span class="nx">Counter</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$_</span><span class="err">.</span><span class="nx">Name</span><span class="w"> </span><span class="err">-</span><span class="nx">replace</span><span class="w"> </span><span class="s2">"^\\\\[^\\]+"</span><span class="p">,</span><span class="w"> </span><span class="s2">""</span><span class="w">
                </span><span class="nx">Average</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">math</span><span class="p">]</span><span class="err">::</span><span class="nx">Round</span><span class="err">(</span><span class="w">
                    </span><span class="err">(</span><span class="bp">$_</span><span class="err">.</span><span class="nx">Group</span><span class="w"> </span><span class="err">|</span><span class="w"> </span><span class="nx">Measure</span><span class="err">-</span><span class="nx">Object</span><span class="w"> </span><span class="nx">CookedValue</span><span class="w"> </span><span class="err">-</span><span class="nx">Average</span><span class="err">).</span><span class="nx">Average</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="w">
                </span><span class="err">)</span><span class="w">
            </span><span class="p">}</span><span class="w">
        </span><span class="p">}</span><span class="w">

    </span><span class="nv">$avg</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Format-Table</span><span class="w"> </span><span class="nt">-AutoSize</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="n">Get-ServerPerformance</span><span class="w">
</span></code></pre></div></div>

<h2 id="성능-데이터-수집기perfmon">성능 데이터 수집기(PerfMon)</h2>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 성능 데이터 수집기 세트 생성 및 시작</span><span class="w">
</span><span class="nv">$query</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">System.Diagnostics.Eventing.Reader.EventLogQuery</span><span class="w">

</span><span class="c"># logman으로 성능 로그 생성</span><span class="w">
</span><span class="n">logman</span><span class="w"> </span><span class="nx">create</span><span class="w"> </span><span class="nx">counter</span><span class="w"> </span><span class="s2">"ServerMonitor"</span><span class="w"> </span><span class="se">`
</span><span class="w">    </span><span class="nt">--v</span><span class="w"> </span><span class="se">`
</span><span class="w">    </span><span class="nt">-c</span><span class="w"> </span><span class="s2">"\Processor(_Total)\% Processor Time"</span><span class="w"> </span><span class="se">`
</span><span class="w">       </span><span class="s2">"\Memory\Available MBytes"</span><span class="w"> </span><span class="se">`
</span><span class="w">       </span><span class="s2">"\PhysicalDisk(_Total)\% Disk Time"</span><span class="w"> </span><span class="se">`
</span><span class="w">    </span><span class="nt">-si</span><span class="w"> </span><span class="nx">30</span><span class="w"> </span><span class="se">`
</span><span class="w">    </span><span class="nt">-f</span><span class="w"> </span><span class="nx">csv</span><span class="w"> </span><span class="se">`
</span><span class="w">    </span><span class="nt">-o</span><span class="w"> </span><span class="s2">"C:\PerfLogs\ServerMonitor"</span><span class="w">

</span><span class="c"># 수집 시작/중지</span><span class="w">
</span><span class="n">logman</span><span class="w"> </span><span class="nx">start</span><span class="w"> </span><span class="nx">ServerMonitor</span><span class="w">
</span><span class="n">logman</span><span class="w"> </span><span class="nx">stop</span><span class="w"> </span><span class="nx">ServerMonitor</span><span class="w">

</span><span class="c"># 현재 수집기 목록</span><span class="w">
</span><span class="n">logman</span><span class="w"> </span><span class="nx">query</span><span class="w">
</span></code></pre></div></div>

<h2 id="프로세스별-리소스-사용량">프로세스별 리소스 사용량</h2>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># CPU 사용률 상위 프로세스</span><span class="w">
</span><span class="n">Get-Process</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Sort-Object</span><span class="w"> </span><span class="nx">CPU</span><span class="w"> </span><span class="nt">-Descending</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Select-Object</span><span class="w"> </span><span class="nt">-First</span><span class="w"> </span><span class="nx">10</span><span class="w"> </span><span class="o">|</span><span class="w">
    </span><span class="n">Format-Table</span><span class="w"> </span><span class="nx">Name</span><span class="p">,</span><span class="w"> </span><span class="nx">ID</span><span class="p">,</span><span class="w">
    </span><span class="p">@{</span><span class="nx">N</span><span class="o">=</span><span class="s1">'CPU(s)'</span><span class="p">;</span><span class="w"> </span><span class="nx">E</span><span class="o">=</span><span class="p">{[</span><span class="n">math</span><span class="p">]::</span><span class="n">Round</span><span class="p">(</span><span class="bp">$_</span><span class="o">.</span><span class="nf">CPU</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">)}},</span><span class="w">
    </span><span class="p">@{</span><span class="nx">N</span><span class="o">=</span><span class="s1">'MemMB'</span><span class="p">;</span><span class="w"> </span><span class="nx">E</span><span class="o">=</span><span class="p">{[</span><span class="n">math</span><span class="p">]::</span><span class="n">Round</span><span class="p">(</span><span class="bp">$_</span><span class="o">.</span><span class="nf">WorkingSet</span><span class="n">/1MB</span><span class="p">,</span><span class="w"> </span><span class="nx">1</span><span class="p">)}},</span><span class="w">
    </span><span class="nx">Handles</span><span class="w"> </span><span class="err">-</span><span class="nx">AutoSize</span><span class="w">

</span><span class="c"># 메모리 사용 상위 프로세스</span><span class="w">
</span><span class="nx">Get</span><span class="err">-</span><span class="nx">Process</span><span class="w"> </span><span class="err">|</span><span class="w"> </span><span class="nx">Sort</span><span class="err">-</span><span class="nx">Object</span><span class="w"> </span><span class="nx">WorkingSet</span><span class="w"> </span><span class="err">-</span><span class="nx">Descending</span><span class="w"> </span><span class="err">|</span><span class="w"> </span><span class="nx">Select</span><span class="err">-</span><span class="nx">Object</span><span class="w"> </span><span class="err">-</span><span class="nx">First</span><span class="w"> </span><span class="mi">10</span><span class="w"> </span><span class="err">|</span><span class="w">
    </span><span class="nx">Format</span><span class="err">-</span><span class="nx">Table</span><span class="w"> </span><span class="nx">Name</span><span class="p">,</span><span class="w"> </span><span class="nx">ID</span><span class="p">,</span><span class="w">
    </span><span class="p">@{</span><span class="nx">N</span><span class="o">=</span><span class="s1">'MemMB'</span><span class="p">;</span><span class="w"> </span><span class="nx">E</span><span class="o">=</span><span class="p">{[</span><span class="n">math</span><span class="p">]::</span><span class="n">Round</span><span class="p">(</span><span class="bp">$_</span><span class="o">.</span><span class="nf">WorkingSet</span><span class="n">/1MB</span><span class="p">,</span><span class="w"> </span><span class="nx">1</span><span class="p">)}},</span><span class="w">
    </span><span class="p">@{</span><span class="nx">N</span><span class="o">=</span><span class="s1">'VirtualMB'</span><span class="p">;</span><span class="w"> </span><span class="nx">E</span><span class="o">=</span><span class="p">{[</span><span class="n">math</span><span class="p">]::</span><span class="n">Round</span><span class="p">(</span><span class="bp">$_</span><span class="o">.</span><span class="nf">VirtualMemorySize64</span><span class="n">/1MB</span><span class="p">,</span><span class="w"> </span><span class="nx">1</span><span class="p">)}}</span><span class="w"> </span><span class="err">-</span><span class="nx">AutoSize</span><span class="w">

</span><span class="c"># 특정 프로세스 상세 모니터링</span><span class="w">
</span><span class="nx">while</span><span class="w"> </span><span class="err">(</span><span class="bp">$true</span><span class="err">)</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nv">$proc</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-Process</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="s2">"w3wp"</span><span class="w"> </span><span class="nt">-ErrorAction</span><span class="w"> </span><span class="nx">SilentlyContinue</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$proc</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nv">$proc</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Format-Table</span><span class="w"> </span><span class="nx">Name</span><span class="p">,</span><span class="w"> </span><span class="nx">ID</span><span class="p">,</span><span class="w">
            </span><span class="p">@{</span><span class="nx">N</span><span class="o">=</span><span class="s1">'CPU'</span><span class="p">;</span><span class="w"> </span><span class="nx">E</span><span class="o">=</span><span class="p">{[</span><span class="n">math</span><span class="p">]::</span><span class="n">Round</span><span class="p">(</span><span class="bp">$_</span><span class="o">.</span><span class="nf">CPU</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">)}},</span><span class="w">
            </span><span class="p">@{</span><span class="nx">N</span><span class="o">=</span><span class="s1">'MemMB'</span><span class="p">;</span><span class="w"> </span><span class="nx">E</span><span class="o">=</span><span class="p">{[</span><span class="n">math</span><span class="p">]::</span><span class="n">Round</span><span class="p">(</span><span class="bp">$_</span><span class="o">.</span><span class="nf">WorkingSet</span><span class="n">/1MB</span><span class="p">,</span><span class="w"> </span><span class="nx">1</span><span class="p">)}}</span><span class="w">
    </span><span class="p">}</span><span class="w">
    </span><span class="n">Start-Sleep</span><span class="w"> </span><span class="nt">-Seconds</span><span class="w"> </span><span class="nx">5</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p><img src="/assets/images/2026-02-15/2.jpg" alt="데이터센터 이미지" />
<em><center>대시보드에서 Windows Server 성능을 시각화하는 모습</center></em></p>

<h2 id="디스크-io-분석">디스크 I/O 분석</h2>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 디스크 사용량 확인</span><span class="w">
</span><span class="n">Get-PSDrive</span><span class="w"> </span><span class="nt">-PSProvider</span><span class="w"> </span><span class="nx">FileSystem</span><span class="w"> </span><span class="o">|</span><span class="w">
    </span><span class="n">Select-Object</span><span class="w"> </span><span class="nx">Name</span><span class="p">,</span><span class="w">
    </span><span class="p">@{</span><span class="nx">N</span><span class="o">=</span><span class="s1">'UsedGB'</span><span class="p">;</span><span class="w"> </span><span class="nx">E</span><span class="o">=</span><span class="p">{[</span><span class="n">math</span><span class="p">]::</span><span class="n">Round</span><span class="p">(</span><span class="bp">$_</span><span class="o">.</span><span class="nf">Used</span><span class="n">/1GB</span><span class="p">,</span><span class="w"> </span><span class="nx">1</span><span class="p">)}},</span><span class="w">
    </span><span class="p">@{</span><span class="nx">N</span><span class="o">=</span><span class="s1">'FreeGB'</span><span class="p">;</span><span class="w"> </span><span class="nx">E</span><span class="o">=</span><span class="p">{[</span><span class="n">math</span><span class="p">]::</span><span class="n">Round</span><span class="p">(</span><span class="bp">$_</span><span class="o">.</span><span class="nf">Free</span><span class="n">/1GB</span><span class="p">,</span><span class="w"> </span><span class="nx">1</span><span class="p">)}},</span><span class="w">
    </span><span class="p">@{</span><span class="nx">N</span><span class="o">=</span><span class="s1">'TotalGB'</span><span class="p">;</span><span class="w"> </span><span class="nx">E</span><span class="o">=</span><span class="p">{[</span><span class="n">math</span><span class="p">]::</span><span class="n">Round</span><span class="p">((</span><span class="bp">$_</span><span class="o">.</span><span class="nf">Used</span><span class="o">+</span><span class="bp">$_</span><span class="o">.</span><span class="nf">Free</span><span class="p">)</span><span class="n">/1GB</span><span class="p">,</span><span class="w"> </span><span class="nx">1</span><span class="p">)}},</span><span class="w">
    </span><span class="p">@{</span><span class="nx">N</span><span class="o">=</span><span class="s1">'UsedPct'</span><span class="p">;</span><span class="w"> </span><span class="nx">E</span><span class="o">=</span><span class="p">{[</span><span class="n">math</span><span class="p">]::</span><span class="n">Round</span><span class="p">(</span><span class="bp">$_</span><span class="o">.</span><span class="nf">Used</span><span class="n">/</span><span class="p">(</span><span class="bp">$_</span><span class="o">.</span><span class="nf">Used</span><span class="o">+</span><span class="bp">$_</span><span class="o">.</span><span class="nf">Free</span><span class="p">)</span><span class="o">*</span><span class="mi">100</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">)}}</span><span class="w"> </span><span class="err">|</span><span class="w">
    </span><span class="nx">Format</span><span class="err">-</span><span class="nx">Table</span><span class="w"> </span><span class="err">-</span><span class="nx">AutoSize</span><span class="w">

</span><span class="c"># 디스크 I/O 성능</span><span class="w">
</span><span class="nx">Get</span><span class="err">-</span><span class="nx">Counter</span><span class="w"> </span><span class="err">-</span><span class="nx">Counter</span><span class="w"> </span><span class="p">@(</span><span class="w">
    </span><span class="s2">"\PhysicalDisk(_Total)\Disk Reads/sec"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"\PhysicalDisk(_Total)\Disk Writes/sec"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"\PhysicalDisk(_Total)\Avg. Disk Queue Length"</span><span class="w">
</span><span class="p">)</span><span class="w"> </span><span class="err">-</span><span class="nx">SampleInterval</span><span class="w"> </span><span class="mi">5</span><span class="w"> </span><span class="err">-</span><span class="nx">MaxSamples</span><span class="w"> </span><span class="mi">6</span><span class="w"> </span><span class="err">|</span><span class="w">
    </span><span class="nx">ForEach</span><span class="err">-</span><span class="nx">Object</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="bp">$_</span><span class="o">.</span><span class="nf">CounterSamples</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Format-Table</span><span class="w"> </span><span class="nx">Path</span><span class="p">,</span><span class="w">
        </span><span class="p">@{</span><span class="nx">N</span><span class="o">=</span><span class="s1">'Value'</span><span class="p">;</span><span class="w"> </span><span class="nx">E</span><span class="o">=</span><span class="p">{[</span><span class="n">math</span><span class="p">]::</span><span class="n">Round</span><span class="p">(</span><span class="bp">$_</span><span class="o">.</span><span class="nf">CookedValue</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">)}}</span><span class="w"> </span><span class="nt">-AutoSize</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h2 id="네트워크-성능-모니터링">네트워크 성능 모니터링</h2>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 네트워크 어댑터 현재 상태</span><span class="w">
</span><span class="n">Get-NetAdapterStatistics</span><span class="w"> </span><span class="o">|</span><span class="w">
    </span><span class="n">Select-Object</span><span class="w"> </span><span class="nx">Name</span><span class="p">,</span><span class="w">
    </span><span class="p">@{</span><span class="nx">N</span><span class="o">=</span><span class="s1">'ReceivedMB'</span><span class="p">;</span><span class="w"> </span><span class="nx">E</span><span class="o">=</span><span class="p">{[</span><span class="n">math</span><span class="p">]::</span><span class="n">Round</span><span class="p">(</span><span class="bp">$_</span><span class="o">.</span><span class="nf">ReceivedBytes</span><span class="n">/1MB</span><span class="p">,</span><span class="w"> </span><span class="nx">2</span><span class="p">)}},</span><span class="w">
    </span><span class="p">@{</span><span class="nx">N</span><span class="o">=</span><span class="s1">'SentMB'</span><span class="p">;</span><span class="w"> </span><span class="nx">E</span><span class="o">=</span><span class="p">{[</span><span class="n">math</span><span class="p">]::</span><span class="n">Round</span><span class="p">(</span><span class="bp">$_</span><span class="o">.</span><span class="nf">SentBytes</span><span class="n">/1MB</span><span class="p">,</span><span class="w"> </span><span class="nx">2</span><span class="p">)}},</span><span class="w">
    </span><span class="nx">ReceivedUnicastPackets</span><span class="p">,</span><span class="w"> </span><span class="nx">SentUnicastPackets</span><span class="w"> </span><span class="err">|</span><span class="w">
    </span><span class="nx">Format</span><span class="err">-</span><span class="nx">Table</span><span class="w"> </span><span class="err">-</span><span class="nx">AutoSize</span><span class="w">

</span><span class="c"># 네트워크 연결 상태</span><span class="w">
</span><span class="nx">Get</span><span class="err">-</span><span class="nx">NetTCPConnection</span><span class="w"> </span><span class="err">|</span><span class="w">
    </span><span class="nx">Group</span><span class="err">-</span><span class="nx">Object</span><span class="w"> </span><span class="nx">State</span><span class="w"> </span><span class="err">|</span><span class="w">
    </span><span class="nx">Sort</span><span class="err">-</span><span class="nx">Object</span><span class="w"> </span><span class="nx">Count</span><span class="w"> </span><span class="err">-</span><span class="nx">Descending</span><span class="w"> </span><span class="err">|</span><span class="w">
    </span><span class="nx">Format</span><span class="err">-</span><span class="nx">Table</span><span class="w"> </span><span class="nx">Name</span><span class="p">,</span><span class="w"> </span><span class="nx">Count</span><span class="w"> </span><span class="err">-</span><span class="nx">AutoSize</span><span class="w">

</span><span class="c"># 대역폭 사용률 실시간 (10초 평균)</span><span class="w">
</span><span class="nv">$adapter</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Ethernet"</span><span class="w">
</span><span class="nx">Get</span><span class="err">-</span><span class="nx">Counter</span><span class="w"> </span><span class="s2">"\Network Interface(</span><span class="nv">$adapter</span><span class="s2">)\Bytes Total/sec"</span><span class="w"> </span><span class="err">`</span><span class="w">
    </span><span class="err">-</span><span class="nx">SampleInterval</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="err">-</span><span class="nx">MaxSamples</span><span class="w"> </span><span class="mi">10</span><span class="w"> </span><span class="err">|</span><span class="w">
    </span><span class="nx">ForEach</span><span class="err">-</span><span class="nx">Object</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nv">$mbps</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">math</span><span class="p">]::</span><span class="n">Round</span><span class="p">(</span><span class="bp">$_</span><span class="o">.</span><span class="n">CounterSamples</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="nf">CookedValue</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="n">/</span><span class="w"> </span><span class="nx">1MB</span><span class="p">,</span><span class="w"> </span><span class="nx">2</span><span class="p">)</span><span class="w">
        </span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"</span><span class="si">$(</span><span class="n">Get-Date</span><span class="w"> </span><span class="nt">-Format</span><span class="w"> </span><span class="s1">'HH:mm:ss'</span><span class="p">)</span><span class="s2"> </span><span class="nv">$adapter</span><span class="s2">: </span><span class="nv">$mbps</span><span class="s2"> Mbps"</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h2 id="성능-알림-스크립트">성능 알림 스크립트</h2>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 임계값 초과 시 이메일 알림</span><span class="w">
</span><span class="kr">function</span><span class="w"> </span><span class="nf">Monitor-ServerResources</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="kr">param</span><span class="p">(</span><span class="w">
        </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="nv">$CpuThreshold</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">85</span><span class="p">,</span><span class="w">
        </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="nv">$MemThresholdMB</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1024</span><span class="p">,</span><span class="w">
        </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="nv">$DiskThreshold</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">90</span><span class="w">
    </span><span class="p">)</span><span class="w">

    </span><span class="c"># CPU 확인</span><span class="w">
    </span><span class="nv">$cpu</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">Get-Counter</span><span class="w"> </span><span class="s2">"\Processor(_Total)\% Processor Time"</span><span class="p">)</span><span class="o">.</span><span class="n">CounterSamples</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="nf">CookedValue</span><span class="w">
    </span><span class="nx">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$cpu</span><span class="w"> </span><span class="o">-gt</span><span class="w"> </span><span class="nv">$CpuThreshold</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="n">Write-Warning</span><span class="w"> </span><span class="s2">"CPU 경고: </span><span class="si">$(</span><span class="p">[</span><span class="n">math</span><span class="p">]::</span><span class="n">Round</span><span class="p">(</span><span class="nv">$cpu</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="si">)</span><span class="s2">)% (임계값: </span><span class="nv">$CpuThreshold</span><span class="s2">%)"</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="c"># 메모리 확인</span><span class="w">
    </span><span class="nv">$freeMem</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">Get-WmiObject</span><span class="w"> </span><span class="nx">Win32_OperatingSystem</span><span class="p">)</span><span class="o">.</span><span class="nf">FreePhysicalMemory</span><span class="w"> </span><span class="nx">/</span><span class="w"> </span><span class="nx">1KB</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$freeMem</span><span class="w"> </span><span class="o">-lt</span><span class="w"> </span><span class="nv">$MemThresholdMB</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="n">Write-Warning</span><span class="w"> </span><span class="s2">"메모리 경고: 여유 </span><span class="si">$(</span><span class="p">[</span><span class="n">math</span><span class="p">]::</span><span class="n">Round</span><span class="p">(</span><span class="nv">$freeMem</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="si">)</span><span class="s2">)MB (임계값: </span><span class="nv">$MemThresholdMB</span><span class="s2"> MB)"</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="c"># 디스크 확인</span><span class="w">
    </span><span class="n">Get-PSDrive</span><span class="w"> </span><span class="nt">-PSProvider</span><span class="w"> </span><span class="nx">FileSystem</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">ForEach-Object</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nv">$used</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">Used</span><span class="w"> </span><span class="n">/</span><span class="w"> </span><span class="p">(</span><span class="bp">$_</span><span class="o">.</span><span class="nf">Used</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">Free</span><span class="p">)</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">100</span><span class="w">
        </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$used</span><span class="w"> </span><span class="o">-gt</span><span class="w"> </span><span class="nv">$DiskThreshold</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="n">Write-Warning</span><span class="w"> </span><span class="s2">"디스크 경고: </span><span class="si">$(</span><span class="bp">$_</span><span class="o">.</span><span class="nf">Name</span><span class="si">)</span><span class="s2">: 드라이브 사용률 </span><span class="si">$(</span><span class="p">[</span><span class="n">math</span><span class="p">]::</span><span class="n">Round</span><span class="p">(</span><span class="nv">$used</span><span class="p">,</span><span class="mi">1</span><span class="si">)</span><span class="s2">)%"</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="c"># 5분마다 모니터링</span><span class="w">
</span><span class="kr">while</span><span class="w"> </span><span class="p">(</span><span class="bp">$true</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">Monitor-ServerResources</span><span class="w">
    </span><span class="nx">Start-Sleep</span><span class="w"> </span><span class="nt">-Seconds</span><span class="w"> </span><span class="nx">300</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<hr />

<p>성능 모니터링의 핵심은 정상 기준선(Baseline)을 파악하는 것입니다. 서버를 처음 구성할 때부터 성능 데이터를 수집하여 정상 패턴을 파악해두면, 이후 문제 발생 시 빠르게 이상 징후를 발견할 수 있습니다. 수집된 데이터는 Grafana, Azure Monitor 같은 도구로 시각화하면 더욱 효과적입니다.</p>]]></content><author><name>사쿠라호스팅</name></author><category term="윈도우 서버" /><category term="모니터링" /><category term="성능모니터링" /><category term="윈도우서버" /><category term="PowerShell" /><category term="성능카운터" /><category term="트러블슈팅" /><summary type="html"><![CDATA[Windows Server의 성능 카운터, 리소스 모니터, PowerShell을 활용하여 CPU, 메모리, 디스크, 네트워크 성능을 효과적으로 모니터링하고 병목을 진단하는 방법을 알아봅니다.]]></summary></entry><entry><title type="html">Nginx 리버스 프록시 완벽 설정 가이드</title><link href="https://blog.sakura-idc.com/%EB%A6%AC%EB%88%85%EC%8A%A4/%EC%9B%B9%EC%84%9C%EB%B2%84/Nginx-%EB%A6%AC%EB%B2%84%EC%8A%A4-%ED%94%84%EB%A1%9D%EC%8B%9C-%EC%99%84%EB%B2%BD-%EC%84%A4%EC%A0%95/" rel="alternate" type="text/html" title="Nginx 리버스 프록시 완벽 설정 가이드" /><published>2026-02-05T10:00:00+09:00</published><updated>2026-02-05T10:00:00+09:00</updated><id>https://blog.sakura-idc.com/%EB%A6%AC%EB%88%85%EC%8A%A4/%EC%9B%B9%EC%84%9C%EB%B2%84/Nginx-%EB%A6%AC%EB%B2%84%EC%8A%A4-%ED%94%84%EB%A1%9D%EC%8B%9C-%EC%99%84%EB%B2%BD-%EC%84%A4%EC%A0%95</id><content type="html" xml:base="https://blog.sakura-idc.com/%EB%A6%AC%EB%88%85%EC%8A%A4/%EC%9B%B9%EC%84%9C%EB%B2%84/Nginx-%EB%A6%AC%EB%B2%84%EC%8A%A4-%ED%94%84%EB%A1%9D%EC%8B%9C-%EC%99%84%EB%B2%BD-%EC%84%A4%EC%A0%95/"><![CDATA[<p>리버스 프록시는 클라이언트와 백엔드 서버 사이에서 요청을 중계하는 서버입니다. Nginx를 리버스 프록시로 활용하면 SSL 처리, 정적 파일 캐싱, 압축, 보안 헤더 추가 등의 기능을 백엔드에서 분리하여 처리할 수 있습니다.</p>

<p><img src="/assets/images/2026-02-05/1.jpg" alt="보안 이미지" />
<em><center>Nginx 리버스 프록시로 구성된 웹 서비스 아키텍처</center></em></p>

<h2 id="기본-리버스-프록시-설정">기본 리버스 프록시 설정</h2>

<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># /etc/nginx/conf.d/proxy.conf</span>

<span class="k">server</span> <span class="p">{</span>
    <span class="kn">listen</span> <span class="mi">80</span><span class="p">;</span>
    <span class="kn">server_name</span> <span class="s">example.com</span><span class="p">;</span>

    <span class="kn">location</span> <span class="n">/</span> <span class="p">{</span>
        <span class="kn">proxy_pass</span> <span class="s">http://127.0.0.1:8080</span><span class="p">;</span>

        <span class="c1"># 원래 클라이언트 정보 전달</span>
        <span class="kn">proxy_set_header</span> <span class="s">Host</span>              <span class="nv">$host</span><span class="p">;</span>
        <span class="kn">proxy_set_header</span> <span class="s">X-Real-IP</span>         <span class="nv">$remote_addr</span><span class="p">;</span>
        <span class="kn">proxy_set_header</span> <span class="s">X-Forwarded-For</span>   <span class="nv">$proxy_add_x_forwarded_for</span><span class="p">;</span>
        <span class="kn">proxy_set_header</span> <span class="s">X-Forwarded-Proto</span> <span class="nv">$scheme</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="ssl-종료termination-설정">SSL 종료(Termination) 설정</h2>

<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">server</span> <span class="p">{</span>
    <span class="kn">listen</span> <span class="mi">443</span> <span class="s">ssl</span> <span class="s">http2</span><span class="p">;</span>
    <span class="kn">server_name</span> <span class="s">example.com</span><span class="p">;</span>

    <span class="c1"># SSL 인증서</span>
    <span class="kn">ssl_certificate</span>     <span class="n">/etc/letsencrypt/live/example.com/fullchain.pem</span><span class="p">;</span>
    <span class="kn">ssl_certificate_key</span> <span class="n">/etc/letsencrypt/live/example.com/privkey.pem</span><span class="p">;</span>

    <span class="c1"># SSL 보안 설정</span>
    <span class="kn">ssl_protocols</span> <span class="s">TLSv1.2</span> <span class="s">TLSv1.3</span><span class="p">;</span>
    <span class="kn">ssl_ciphers</span> <span class="s">ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256</span><span class="p">;</span>
    <span class="kn">ssl_prefer_server_ciphers</span> <span class="no">off</span><span class="p">;</span>
    <span class="kn">ssl_session_cache</span> <span class="s">shared:SSL:10m</span><span class="p">;</span>
    <span class="kn">ssl_session_timeout</span> <span class="s">1d</span><span class="p">;</span>

    <span class="c1"># 보안 헤더</span>
    <span class="kn">add_header</span> <span class="s">Strict-Transport-Security</span> <span class="s">"max-age=63072000"</span> <span class="s">always</span><span class="p">;</span>
    <span class="kn">add_header</span> <span class="s">X-Frame-Options</span> <span class="s">DENY</span><span class="p">;</span>
    <span class="kn">add_header</span> <span class="s">X-Content-Type-Options</span> <span class="s">nosniff</span><span class="p">;</span>
    <span class="kn">add_header</span> <span class="s">Referrer-Policy</span> <span class="s">"strict-origin-when-cross-origin"</span><span class="p">;</span>

    <span class="kn">location</span> <span class="n">/</span> <span class="p">{</span>
        <span class="kn">proxy_pass</span> <span class="s">http://127.0.0.1:3000</span><span class="p">;</span>
        <span class="kn">proxy_set_header</span> <span class="s">Host</span>              <span class="nv">$host</span><span class="p">;</span>
        <span class="kn">proxy_set_header</span> <span class="s">X-Real-IP</span>         <span class="nv">$remote_addr</span><span class="p">;</span>
        <span class="kn">proxy_set_header</span> <span class="s">X-Forwarded-For</span>   <span class="nv">$proxy_add_x_forwarded_for</span><span class="p">;</span>
        <span class="kn">proxy_set_header</span> <span class="s">X-Forwarded-Proto</span> <span class="s">https</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="c1"># HTTP → HTTPS 리다이렉트</span>
<span class="k">server</span> <span class="p">{</span>
    <span class="kn">listen</span> <span class="mi">80</span><span class="p">;</span>
    <span class="kn">server_name</span> <span class="s">example.com</span><span class="p">;</span>
    <span class="kn">return</span> <span class="mi">301</span> <span class="s">https://</span><span class="nv">$host$request_uri</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="경로-기반-프록시-api-게이트웨이">경로 기반 프록시 (API 게이트웨이)</h2>

<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">server</span> <span class="p">{</span>
    <span class="kn">listen</span> <span class="mi">443</span> <span class="s">ssl</span><span class="p">;</span>
    <span class="kn">server_name</span> <span class="s">api.example.com</span><span class="p">;</span>

    <span class="c1"># 정적 파일</span>
    <span class="kn">location</span> <span class="n">/static/</span> <span class="p">{</span>
        <span class="kn">root</span> <span class="n">/var/www</span><span class="p">;</span>
        <span class="kn">expires</span> <span class="s">30d</span><span class="p">;</span>
        <span class="kn">add_header</span> <span class="s">Cache-Control</span> <span class="s">"public,</span> <span class="s">immutable"</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1"># REST API → Node.js</span>
    <span class="kn">location</span> <span class="n">/api/v1/</span> <span class="p">{</span>
        <span class="kn">proxy_pass</span> <span class="s">http://127.0.0.1:3000</span><span class="p">;</span>
        <span class="kn">proxy_read_timeout</span> <span class="s">30s</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1"># WebSocket → Node.js</span>
    <span class="kn">location</span> <span class="n">/ws/</span> <span class="p">{</span>
        <span class="kn">proxy_pass</span> <span class="s">http://127.0.0.1:3001</span><span class="p">;</span>
        <span class="kn">proxy_http_version</span> <span class="mf">1.1</span><span class="p">;</span>
        <span class="kn">proxy_set_header</span> <span class="s">Upgrade</span> <span class="nv">$http_upgrade</span><span class="p">;</span>
        <span class="kn">proxy_set_header</span> <span class="s">Connection</span> <span class="s">"upgrade"</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1"># 파일 업로드 → Python 서비스</span>
    <span class="kn">location</span> <span class="n">/upload/</span> <span class="p">{</span>
        <span class="kn">proxy_pass</span> <span class="s">http://127.0.0.1:5000</span><span class="p">;</span>
        <span class="kn">client_max_body_size</span> <span class="mi">100M</span><span class="p">;</span>
        <span class="kn">proxy_read_timeout</span> <span class="s">300s</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="프록시-캐싱-설정">프록시 캐싱 설정</h2>

<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># /etc/nginx/nginx.conf (http 블록 내)</span>
<span class="k">proxy_cache_path</span> <span class="n">/var/cache/nginx</span>
    <span class="s">levels=1:2</span>
    <span class="s">keys_zone=my_cache:10m</span>
    <span class="s">max_size=10g</span>
    <span class="s">inactive=60m</span>
    <span class="s">use_temp_path=off</span><span class="p">;</span>

<span class="c1"># /etc/nginx/conf.d/proxy.conf</span>
<span class="k">server</span> <span class="p">{</span>
    <span class="kn">location</span> <span class="n">/</span> <span class="p">{</span>
        <span class="kn">proxy_pass</span> <span class="s">http://backend</span><span class="p">;</span>
        <span class="kn">proxy_cache</span> <span class="s">my_cache</span><span class="p">;</span>
        <span class="kn">proxy_cache_valid</span> <span class="mi">200</span> <span class="mi">302</span> <span class="mi">10m</span><span class="p">;</span>
        <span class="kn">proxy_cache_valid</span> <span class="mi">404</span> <span class="mi">1m</span><span class="p">;</span>
        <span class="kn">proxy_cache_use_stale</span> <span class="s">error</span> <span class="s">timeout</span> <span class="s">http_500</span> <span class="s">http_502</span> <span class="s">http_503</span><span class="p">;</span>
        <span class="kn">proxy_cache_lock</span> <span class="no">on</span><span class="p">;</span>

        <span class="c1"># 캐시 상태를 응답 헤더에 추가 (디버깅)</span>
        <span class="kn">add_header</span> <span class="s">X-Cache-Status</span> <span class="nv">$upstream_cache_status</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1"># 캐시 무효화 (특정 IP에서만 허용)</span>
    <span class="kn">location</span> <span class="p">~</span> <span class="sr">/purge(/.*)</span> <span class="p">{</span>
        <span class="kn">allow</span> <span class="mf">127.0</span><span class="s">.0.1</span><span class="p">;</span>
        <span class="kn">deny</span> <span class="s">all</span><span class="p">;</span>
        <span class="kn">proxy_cache_purge</span> <span class="s">my_cache</span> <span class="nv">$scheme$host$1</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><img src="/assets/images/2026-02-05/2.jpg" alt="데이터 서버 이미지" />
<em><center>리버스 프록시를 통한 트래픽 흐름 모니터링</center></em></p>

<h2 id="속도-제한rate-limiting">속도 제한(Rate Limiting)</h2>

<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># 요청 속도 제한 존 정의 (http 블록)</span>
<span class="k">limit_req_zone</span> <span class="nv">$binary_remote_addr</span> <span class="s">zone=api_limit:10m</span> <span class="s">rate=10r/s</span><span class="p">;</span>
<span class="k">limit_req_zone</span> <span class="nv">$binary_remote_addr</span> <span class="s">zone=login_limit:10m</span> <span class="s">rate=5r/m</span><span class="p">;</span>

<span class="k">server</span> <span class="p">{</span>
    <span class="c1"># API 엔드포인트 속도 제한</span>
    <span class="kn">location</span> <span class="n">/api/</span> <span class="p">{</span>
        <span class="kn">limit_req</span> <span class="s">zone=api_limit</span> <span class="s">burst=20</span> <span class="s">nodelay</span><span class="p">;</span>
        <span class="kn">limit_req_status</span> <span class="mi">429</span><span class="p">;</span>
        <span class="kn">proxy_pass</span> <span class="s">http://backend</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1"># 로그인 엔드포인트 엄격한 제한</span>
    <span class="kn">location</span> <span class="n">/api/auth/login</span> <span class="p">{</span>
        <span class="kn">limit_req</span> <span class="s">zone=login_limit</span> <span class="s">burst=5</span><span class="p">;</span>
        <span class="kn">proxy_pass</span> <span class="s">http://backend</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="타임아웃-설정">타임아웃 설정</h2>

<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">server</span> <span class="p">{</span>
    <span class="kn">location</span> <span class="n">/</span> <span class="p">{</span>
        <span class="kn">proxy_pass</span> <span class="s">http://backend</span><span class="p">;</span>

        <span class="c1"># 연결 타임아웃 (백엔드 연결 시도 최대 시간)</span>
        <span class="kn">proxy_connect_timeout</span> <span class="s">5s</span><span class="p">;</span>

        <span class="c1"># 쓰기 타임아웃 (백엔드로 요청 전송 최대 시간)</span>
        <span class="kn">proxy_send_timeout</span> <span class="s">60s</span><span class="p">;</span>

        <span class="c1"># 읽기 타임아웃 (백엔드 응답 대기 최대 시간)</span>
        <span class="kn">proxy_read_timeout</span> <span class="s">60s</span><span class="p">;</span>

        <span class="c1"># 긴 작업용 별도 location</span>
    <span class="p">}</span>

    <span class="kn">location</span> <span class="n">/api/export</span> <span class="p">{</span>
        <span class="kn">proxy_pass</span> <span class="s">http://backend</span><span class="p">;</span>
        <span class="kn">proxy_read_timeout</span> <span class="s">300s</span><span class="p">;</span>  <span class="c1"># 5분</span>
        <span class="kn">proxy_send_timeout</span> <span class="s">300s</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="업스트림-헬스체크">업스트림 헬스체크</h2>

<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Nginx Plus 또는 nginx_upstream_check_module 사용</span>
<span class="k">upstream</span> <span class="s">backend</span> <span class="p">{</span>
    <span class="kn">server</span> <span class="nf">192.168.1.10</span><span class="p">:</span><span class="mi">8080</span><span class="p">;</span>
    <span class="kn">server</span> <span class="nf">192.168.1.11</span><span class="p">:</span><span class="mi">8080</span><span class="p">;</span>
    <span class="kn">server</span> <span class="nf">192.168.1.12</span><span class="p">:</span><span class="mi">8080</span><span class="p">;</span>

    <span class="kn">keepalive</span> <span class="mi">32</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">server</span> <span class="p">{</span>
    <span class="kn">location</span> <span class="n">/</span> <span class="p">{</span>
        <span class="kn">proxy_pass</span> <span class="s">http://backend</span><span class="p">;</span>
        <span class="kn">proxy_next_upstream</span> <span class="s">error</span> <span class="s">timeout</span> <span class="s">http_500</span> <span class="s">http_502</span> <span class="s">http_503</span><span class="p">;</span>
        <span class="kn">proxy_next_upstream_tries</span> <span class="mi">2</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="설정-적용-및-테스트">설정 적용 및 테스트</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 설정 문법 검사</span>
nginx <span class="nt">-t</span>

<span class="c"># 설정 리로드 (무중단)</span>
systemctl reload nginx

<span class="c"># 프록시 동작 확인</span>
curl <span class="nt">-I</span> https://example.com/api/health
curl <span class="nt">-v</span> http://127.0.0.1:8080  <span class="c"># 백엔드 직접 확인</span>

<span class="c"># 캐시 상태 확인</span>
curl <span class="nt">-I</span> https://example.com/api/data | <span class="nb">grep </span>X-Cache-Status

<span class="c"># 속도 제한 테스트</span>
<span class="k">for </span>i <span class="k">in</span> <span class="o">{</span>1..20<span class="o">}</span><span class="p">;</span> <span class="k">do </span>curl <span class="nt">-s</span> <span class="nt">-o</span> /dev/null <span class="nt">-w</span> <span class="s2">"%{http_code}</span><span class="se">\n</span><span class="s2">"</span> https://example.com/api/test<span class="p">;</span> <span class="k">done</span>
</code></pre></div></div>

<hr />

<p>Nginx 리버스 프록시는 단순 중계 기능을 넘어 SSL 처리, 캐싱, 속도 제한, 보안 헤더 주입 등 다양한 미들웨어 역할을 합니다. 백엔드 애플리케이션이 비즈니스 로직에만 집중할 수 있도록 인프라 관심사를 Nginx에서 처리하는 아키텍처를 권장합니다.</p>]]></content><author><name>사쿠라호스팅</name></author><category term="리눅스" /><category term="웹서버" /><category term="nginx" /><category term="리버스프록시" /><category term="웹서버" /><category term="보안" /><category term="성능" /><summary type="html"><![CDATA[Nginx를 리버스 프록시로 설정하여 백엔드 서버를 보호하고, SSL 종료, 캐싱, 압축 등의 기능으로 성능을 최적화하는 방법을 알아봅니다.]]></summary></entry><entry><title type="html">리눅스 LVM 고급 관리: 온라인 확장부터 스냅샷까지</title><link href="https://blog.sakura-idc.com/%EB%A6%AC%EB%88%85%EC%8A%A4/%EC%8A%A4%ED%86%A0%EB%A6%AC%EC%A7%80/%EB%A6%AC%EB%88%85%EC%8A%A4-LVM-%EA%B3%A0%EA%B8%89-%EA%B4%80%EB%A6%AC/" rel="alternate" type="text/html" title="리눅스 LVM 고급 관리: 온라인 확장부터 스냅샷까지" /><published>2026-01-28T10:00:00+09:00</published><updated>2026-01-28T10:00:00+09:00</updated><id>https://blog.sakura-idc.com/%EB%A6%AC%EB%88%85%EC%8A%A4/%EC%8A%A4%ED%86%A0%EB%A6%AC%EC%A7%80/%EB%A6%AC%EB%88%85%EC%8A%A4-LVM-%EA%B3%A0%EA%B8%89-%EA%B4%80%EB%A6%AC</id><content type="html" xml:base="https://blog.sakura-idc.com/%EB%A6%AC%EB%88%85%EC%8A%A4/%EC%8A%A4%ED%86%A0%EB%A6%AC%EC%A7%80/%EB%A6%AC%EB%88%85%EC%8A%A4-LVM-%EA%B3%A0%EA%B8%89-%EA%B4%80%EB%A6%AC/"><![CDATA[<p>LVM은 리눅스의 유연한 디스크 관리 시스템으로, 파티션과 달리 서비스 중단 없이 볼륨을 확장하거나 스냅샷을 생성할 수 있습니다. 운영 중인 서버에서 디스크 공간이 부족할 때 LVM을 활용하면 무중단으로 문제를 해결할 수 있습니다.</p>

<p><img src="/assets/images/2026-01-28/1.jpg" alt="데이터 서버 이미지" />
<em><center>LVM으로 유연하게 관리되는 스토리지 시스템</center></em></p>

<h2 id="lvm-기본-구조">LVM 기본 구조</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>물리 디스크 (/dev/sdb, /dev/sdc)
    └── PV(Physical Volume)
         └── VG(Volume Group: vg_data)
              ├── LV(Logical Volume: lv_app)  → /dev/vg_data/lv_app
              └── LV(Logical Volume: lv_log)  → /dev/vg_data/lv_log
</code></pre></div></div>

<h2 id="디스크-추가-및-pv-생성">디스크 추가 및 PV 생성</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 새 디스크 확인</span>
lsblk
fdisk <span class="nt">-l</span> /dev/sdb

<span class="c"># 파티션 없이 디스크 전체를 PV로 사용</span>
pvcreate /dev/sdb

<span class="c"># 파티션으로 PV 생성</span>
fdisk /dev/sdc   <span class="c"># n → p → 1 → 기본값 → 기본값 → t → 8e → w</span>
pvcreate /dev/sdc1

<span class="c"># PV 목록 확인</span>
pvs
pvdisplay /dev/sdb
</code></pre></div></div>

<h2 id="vg에-디스크-추가-온라인-확장">VG에 디스크 추가 (온라인 확장)</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 기존 VG에 새 PV 추가 (서비스 중단 불필요)</span>
vgextend vg_data /dev/sdb

<span class="c"># VG 정보 확인</span>
vgs
vgdisplay vg_data

<span class="c"># 사용 가능한 PE(Physical Extent) 확인</span>
vgs <span class="nt">-o</span> +free
</code></pre></div></div>

<h2 id="lv-확장-온라인">LV 확장 (온라인)</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 모든 여유 공간을 LV에 추가</span>
lvextend <span class="nt">-l</span> +100%FREE /dev/vg_data/lv_app

<span class="c"># 특정 크기만큼 추가</span>
lvextend <span class="nt">-L</span> +50G /dev/vg_data/lv_app

<span class="c"># 특정 크기로 설정</span>
lvextend <span class="nt">-L</span> 200G /dev/vg_data/lv_app

<span class="c"># LV 확장 후 파일시스템도 즉시 확장</span>
lvextend <span class="nt">-L</span> +50G <span class="nt">-r</span> /dev/vg_data/lv_app   <span class="c"># -r: --resizefs 자동 실행</span>

<span class="c"># 파일시스템만 별도 확장</span>
<span class="c"># ext4:</span>
resize2fs /dev/vg_data/lv_app

<span class="c"># xfs:</span>
xfs_growfs /mount/point
</code></pre></div></div>

<h2 id="lv-축소-ext4만-가능-위험-작업">LV 축소 (ext4만 가능, 위험 작업)</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 반드시 언마운트 후 진행 (xfs는 불가)</span>
umount /app

<span class="c"># 파일시스템 검사</span>
e2fsck <span class="nt">-f</span> /dev/vg_data/lv_app

<span class="c"># 파일시스템 먼저 축소</span>
resize2fs /dev/vg_data/lv_app 100G

<span class="c"># 그 다음 LV 축소</span>
lvreduce <span class="nt">-L</span> 100G /dev/vg_data/lv_app

<span class="c"># 재마운트</span>
mount /dev/vg_data/lv_app /app
</code></pre></div></div>

<h2 id="lvm-스냅샷">LVM 스냅샷</h2>

<p>스냅샷은 백업 전 일관된 상태를 보장하거나 롤백 포인트를 만들 때 유용합니다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 스냅샷 생성 (10GB 스냅샷 공간 할당)</span>
lvcreate <span class="nt">-L</span> 10G <span class="nt">-s</span> <span class="nt">-n</span> lv_app_snap /dev/vg_data/lv_app

<span class="c"># 스냅샷 목록 확인</span>
lvs <span class="nt">-a</span> | <span class="nb">grep </span>snap
lvdisplay /dev/vg_data/lv_app_snap

<span class="c"># 스냅샷 마운트 (읽기 전용 백업)</span>
<span class="nb">mkdir</span> <span class="nt">-p</span> /mnt/snap
mount <span class="nt">-o</span> ro,nouuid /dev/vg_data/lv_app_snap /mnt/snap

<span class="c"># 스냅샷으로 백업</span>
rsync <span class="nt">-av</span> /mnt/snap/ /backup/app-<span class="si">$(</span><span class="nb">date</span> +%Y%m%d<span class="si">)</span>/
umount /mnt/snap

<span class="c"># 스냅샷 제거</span>
lvremove /dev/vg_data/lv_app_snap
</code></pre></div></div>

<h3 id="스냅샷으로-롤백">스냅샷으로 롤백</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 서비스 중지</span>
systemctl stop myapp

<span class="c"># LV 언마운트</span>
umount /app

<span class="c"># 스냅샷으로 원본 복원</span>
lvconvert <span class="nt">--merge</span> /dev/vg_data/lv_app_snap

<span class="c"># LV 재마운트 및 서비스 시작</span>
mount /dev/vg_data/lv_app /app
systemctl start myapp
</code></pre></div></div>

<p><img src="/assets/images/2026-01-28/2.jpg" alt="서버 코드 이미지" />
<em><center>LVM 볼륨 관리 명령어 실행 화면</center></em></p>

<h2 id="씬-프로비저닝-thin-provisioning">씬 프로비저닝 (Thin Provisioning)</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 씬 풀 생성 (100GB 풀)</span>
lvcreate <span class="nt">-L</span> 100G <span class="nt">--thinpool</span> tp_pool vg_data

<span class="c"># 씬 볼륨 생성 (실제 공간보다 크게 할당 가능)</span>
lvcreate <span class="nt">-V</span> 200G <span class="nt">--thin</span> <span class="nt">-n</span> lv_app_thin vg_data/tp_pool

<span class="c"># 씬 볼륨 정보</span>
lvs <span class="nt">-a</span> vg_data
</code></pre></div></div>

<h2 id="lvm-캐시-ssd-캐싱">LVM 캐시 (SSD 캐싱)</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># SSD를 캐시 PV로 추가</span>
pvcreate /dev/nvme0n1
vgextend vg_data /dev/nvme0n1

<span class="c"># 캐시 풀 생성 (SSD 디바이스에)</span>
lvcreate <span class="nt">-L</span> 20G <span class="nt">--type</span> cache-pool <span class="se">\</span>
  <span class="nt">--cachemode</span> writethrough <span class="se">\</span>
  <span class="nt">-n</span> lv_cache_pool <span class="se">\</span>
  vg_data /dev/nvme0n1

<span class="c"># 기존 LV에 캐시 연결</span>
lvconvert <span class="nt">--type</span> cache <span class="se">\</span>
  <span class="nt">--cachepool</span> vg_data/lv_cache_pool <span class="se">\</span>
  vg_data/lv_app
</code></pre></div></div>

<h2 id="주요-lvm-명령어-정리">주요 LVM 명령어 정리</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 전체 LVM 상태 한눈에 확인</span>
pvs<span class="p">;</span> vgs<span class="p">;</span> lvs

<span class="c"># 상세 정보</span>
pvdisplay<span class="p">;</span> vgdisplay<span class="p">;</span> lvdisplay

<span class="c"># PV/VG/LV 삭제 순서</span>
lvremove /dev/vg_data/lv_app
vgremove vg_data
pvremove /dev/sdb

<span class="c"># LVM 메타데이터 백업</span>
vgcfgbackup vg_data
<span class="c"># 저장 위치: /etc/lvm/backup/vg_data</span>

<span class="c"># LVM 메타데이터 복원</span>
vgcfgrestore vg_data
</code></pre></div></div>

<hr />

<p>LVM은 한 번 익숙해지면 서버 스토리지 관리의 필수 도구가 됩니다. 특히 운영 중인 서버에서 무중단 디스크 확장이 필요할 때 진가를 발휘합니다. 스냅샷을 활용한 데이터베이스 백업 자동화와 함께 사용하면 더욱 강력한 스토리지 관리 체계를 갖출 수 있습니다.</p>]]></content><author><name>사쿠라호스팅</name></author><category term="리눅스" /><category term="스토리지" /><category term="LVM" /><category term="스토리지" /><category term="디스크관리" /><category term="리눅스" /><category term="볼륨관리" /><summary type="html"><![CDATA[LVM(Logical Volume Manager)을 활용하여 서비스 중단 없이 디스크를 확장하고, LVM 스냅샷으로 데이터를 보호하는 고급 관리 방법을 알아봅니다.]]></summary></entry><entry><title type="html">윈도우 서버 PowerShell 자동화 실전 가이드</title><link href="https://blog.sakura-idc.com/%EC%9C%88%EB%8F%84%EC%9A%B0%20%EC%84%9C%EB%B2%84/%EC%9E%90%EB%8F%99%ED%99%94/%EC%9C%88%EB%8F%84%EC%9A%B0-%EC%84%9C%EB%B2%84-PowerShell-%EC%9E%90%EB%8F%99%ED%99%94-%EC%8B%A4%EC%A0%84-%EA%B0%80%EC%9D%B4%EB%93%9C/" rel="alternate" type="text/html" title="윈도우 서버 PowerShell 자동화 실전 가이드" /><published>2026-01-20T10:00:00+09:00</published><updated>2026-01-20T10:00:00+09:00</updated><id>https://blog.sakura-idc.com/%EC%9C%88%EB%8F%84%EC%9A%B0%20%EC%84%9C%EB%B2%84/%EC%9E%90%EB%8F%99%ED%99%94/%EC%9C%88%EB%8F%84%EC%9A%B0-%EC%84%9C%EB%B2%84-PowerShell-%EC%9E%90%EB%8F%99%ED%99%94-%EC%8B%A4%EC%A0%84-%EA%B0%80%EC%9D%B4%EB%93%9C</id><content type="html" xml:base="https://blog.sakura-idc.com/%EC%9C%88%EB%8F%84%EC%9A%B0%20%EC%84%9C%EB%B2%84/%EC%9E%90%EB%8F%99%ED%99%94/%EC%9C%88%EB%8F%84%EC%9A%B0-%EC%84%9C%EB%B2%84-PowerShell-%EC%9E%90%EB%8F%99%ED%99%94-%EC%8B%A4%EC%A0%84-%EA%B0%80%EC%9D%B4%EB%93%9C/"><![CDATA[<p>윈도우 서버 관리에서 반복적인 작업은 시간과 에너지를 낭비합니다. PowerShell은 마이크로소프트가 제공하는 강력한 스크립팅 언어이자 쉘로, 윈도우 서버의 거의 모든 기능을 자동화할 수 있습니다. 이 글에서는 실무에서 바로 활용 가능한 PowerShell 자동화 스크립트를 소개합니다.</p>

<p><img src="/assets/images/2026-01-20/1.jpg" alt="코드가 표시된 모니터 화면" />
<em><center>PowerShell 스크립트가 실행되는 서버 터미널</center></em></p>

<h2 id="powershell-실행-정책-설정">PowerShell 실행 정책 설정</h2>

<p>PowerShell 스크립트를 실행하기 전에 먼저 실행 정책을 확인하고 설정해야 합니다.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 현재 실행 정책 확인</span><span class="w">
</span><span class="n">Get-ExecutionPolicy</span><span class="w">

</span><span class="c"># 로컬 스크립트 실행 허용 (원격 스크립트는 서명 필요)</span><span class="w">
</span><span class="n">Set-ExecutionPolicy</span><span class="w"> </span><span class="nx">RemoteSigned</span><span class="w"> </span><span class="nt">-Scope</span><span class="w"> </span><span class="nx">CurrentUser</span><span class="w">

</span><span class="c"># 관리자 권한으로 전체 시스템에 적용</span><span class="w">
</span><span class="n">Set-ExecutionPolicy</span><span class="w"> </span><span class="nx">RemoteSigned</span><span class="w"> </span><span class="nt">-Scope</span><span class="w"> </span><span class="nx">LocalMachine</span><span class="w"> </span><span class="nt">-Force</span><span class="w">
</span></code></pre></div></div>

<h2 id="시스템-정보-수집-자동화">시스템 정보 수집 자동화</h2>

<h3 id="기본-시스템-정보-조회">기본 시스템 정보 조회</h3>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 운영체제 정보</span><span class="w">
</span><span class="n">Get-ComputerInfo</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Select-Object</span><span class="w"> </span><span class="nx">WindowsProductName</span><span class="p">,</span><span class="w"> </span><span class="nx">WindowsVersion</span><span class="p">,</span><span class="w"> </span><span class="nx">TotalPhysicalMemory</span><span class="w">

</span><span class="c"># CPU 정보</span><span class="w">
</span><span class="n">Get-WmiObject</span><span class="w"> </span><span class="nx">Win32_Processor</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Select-Object</span><span class="w"> </span><span class="nx">Name</span><span class="p">,</span><span class="w"> </span><span class="nx">NumberOfCores</span><span class="p">,</span><span class="w"> </span><span class="nx">MaxClockSpeed</span><span class="w">

</span><span class="c"># 디스크 사용량 확인</span><span class="w">
</span><span class="n">Get-PSDrive</span><span class="w"> </span><span class="nt">-PSProvider</span><span class="w"> </span><span class="nx">FileSystem</span><span class="w"> </span><span class="o">|</span><span class="w">
  </span><span class="n">Select-Object</span><span class="w"> </span><span class="nx">Name</span><span class="p">,</span><span class="w"> </span><span class="p">@{</span><span class="nx">N</span><span class="o">=</span><span class="s1">'사용량(GB)'</span><span class="p">;</span><span class="nx">E</span><span class="o">=</span><span class="p">{[</span><span class="n">math</span><span class="p">]::</span><span class="n">Round</span><span class="p">(</span><span class="bp">$_</span><span class="o">.</span><span class="nf">Used</span><span class="n">/1GB</span><span class="p">,</span><span class="nx">2</span><span class="p">)}},</span><span class="w">
                      </span><span class="p">@{</span><span class="nx">N</span><span class="o">=</span><span class="s1">'여유공간(GB)'</span><span class="p">;</span><span class="nx">E</span><span class="o">=</span><span class="p">{[</span><span class="n">math</span><span class="p">]::</span><span class="n">Round</span><span class="p">(</span><span class="bp">$_</span><span class="o">.</span><span class="nf">Free</span><span class="n">/1GB</span><span class="p">,</span><span class="nx">2</span><span class="p">)}},</span><span class="w">
                      </span><span class="p">@{</span><span class="nx">N</span><span class="o">=</span><span class="s1">'전체(GB)'</span><span class="p">;</span><span class="nx">E</span><span class="o">=</span><span class="p">{[</span><span class="n">math</span><span class="p">]::</span><span class="n">Round</span><span class="p">((</span><span class="bp">$_</span><span class="o">.</span><span class="nf">Used</span><span class="o">+</span><span class="bp">$_</span><span class="o">.</span><span class="nf">Free</span><span class="p">)</span><span class="n">/1GB</span><span class="p">,</span><span class="nx">2</span><span class="p">)}}</span><span class="w">
</span></code></pre></div></div>

<h3 id="서버-상태-보고서-자동-생성">서버 상태 보고서 자동 생성</h3>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 서버 상태 보고서를 HTML 파일로 생성</span><span class="w">
</span><span class="nv">$reportPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"C:\Reports\server-status-</span><span class="si">$(</span><span class="n">Get-Date</span><span class="w"> </span><span class="nt">-Format</span><span class="w"> </span><span class="s1">'yyyyMMdd'</span><span class="p">)</span><span class="s2">.html"</span><span class="w">

</span><span class="nv">$cpuLoad</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">Get-WmiObject</span><span class="w"> </span><span class="nx">Win32_Processor</span><span class="p">)</span><span class="o">.</span><span class="nf">LoadPercentage</span><span class="w">
</span><span class="nv">$memTotal</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">math</span><span class="p">]::</span><span class="n">Round</span><span class="p">((</span><span class="n">Get-WmiObject</span><span class="w"> </span><span class="nx">Win32_ComputerSystem</span><span class="p">)</span><span class="o">.</span><span class="nf">TotalPhysicalMemory</span><span class="w"> </span><span class="nx">/</span><span class="w"> </span><span class="nx">1GB</span><span class="p">,</span><span class="w"> </span><span class="nx">2</span><span class="p">)</span><span class="w">
</span><span class="nv">$memFree</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">math</span><span class="p">]::</span><span class="n">Round</span><span class="p">((</span><span class="n">Get-WmiObject</span><span class="w"> </span><span class="nx">Win32_OperatingSystem</span><span class="p">)</span><span class="o">.</span><span class="nf">FreePhysicalMemory</span><span class="w"> </span><span class="nx">/</span><span class="w"> </span><span class="nx">1MB</span><span class="p">,</span><span class="w"> </span><span class="nx">2</span><span class="p">)</span><span class="w">

</span><span class="nv">$html</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="sh">@"
&lt;html&gt;
&lt;body&gt;
&lt;h1&gt;서버 상태 보고서 - </span><span class="si">$(</span><span class="n">Get-Date</span><span class="w"> </span><span class="nt">-Format</span><span class="w"> </span><span class="s1">'yyyy-MM-dd HH:mm'</span><span class="p">)</span><span class="sh">&lt;/h1&gt;
&lt;p&gt;CPU 사용률: </span><span class="nv">$cpuLoad</span><span class="sh">%&lt;/p&gt;
&lt;p&gt;메모리: </span><span class="nv">${memFree}</span><span class="sh">GB / </span><span class="nv">${memTotal}</span><span class="sh">GB 여유&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;
"@</span><span class="w">

</span><span class="nv">$html</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Out-File</span><span class="w"> </span><span class="nt">-FilePath</span><span class="w"> </span><span class="nv">$reportPath</span><span class="w"> </span><span class="nt">-Encoding</span><span class="w"> </span><span class="nx">UTF8</span><span class="w">
</span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"보고서 생성 완료: </span><span class="nv">$reportPath</span><span class="s2">"</span><span class="w">
</span></code></pre></div></div>

<h2 id="사용자-계정-일괄-관리">사용자 계정 일괄 관리</h2>

<h3 id="csv-파일로-사용자-대량-생성">CSV 파일로 사용자 대량 생성</h3>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># users.csv 형식: Name,SamAccountName,Department,Password</span><span class="w">
</span><span class="n">Import-Csv</span><span class="w"> </span><span class="s2">"C:\users.csv"</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">ForEach-Object</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nv">$securePassword</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">ConvertTo-SecureString</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">Password</span><span class="w"> </span><span class="nt">-AsPlainText</span><span class="w"> </span><span class="nt">-Force</span><span class="w">

    </span><span class="n">New-LocalUser</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">SamAccountName</span><span class="w"> </span><span class="se">`
</span><span class="w">                  </span><span class="nt">-Password</span><span class="w"> </span><span class="nv">$securePassword</span><span class="w"> </span><span class="se">`
</span><span class="w">                  </span><span class="nt">-FullName</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">Name</span><span class="w"> </span><span class="se">`
</span><span class="w">                  </span><span class="nt">-Description</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">Department</span><span class="w"> </span><span class="se">`
</span><span class="w">                  </span><span class="nt">-PasswordNeverExpires</span><span class="p">:</span><span class="bp">$false</span><span class="w">

    </span><span class="n">Add-LocalGroupMember</span><span class="w"> </span><span class="nt">-Group</span><span class="w"> </span><span class="s2">"Users"</span><span class="w"> </span><span class="nt">-Member</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">SamAccountName</span><span class="w">

    </span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"사용자 생성 완료: </span><span class="si">$(</span><span class="bp">$_</span><span class="o">.</span><span class="nf">SamAccountName</span><span class="si">)</span><span class="s2">"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h3 id="비활성-사용자-자동-비활성화">비활성 사용자 자동 비활성화</h3>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 90일 이상 로그인하지 않은 계정 비활성화</span><span class="w">
</span><span class="nv">$inactiveDays</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">90</span><span class="w">
</span><span class="nv">$cutoffDate</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">Get-Date</span><span class="p">)</span><span class="o">.</span><span class="nf">AddDays</span><span class="p">(</span><span class="o">-</span><span class="nv">$inactiveDays</span><span class="p">)</span><span class="w">

</span><span class="n">Get-ADUser</span><span class="w"> </span><span class="nt">-Filter</span><span class="w"> </span><span class="p">{</span><span class="n">LastLogonDate</span><span class="w"> </span><span class="o">-lt</span><span class="w"> </span><span class="nv">$cutoffDate</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="nx">Enabled</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="bp">$true</span><span class="p">}</span><span class="w"> </span><span class="se">`</span><span class="w">
           </span><span class="nt">-Properties</span><span class="w"> </span><span class="n">LastLogonDate</span><span class="w"> </span><span class="o">|</span><span class="w">
</span><span class="n">ForEach-Object</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">Disable-ADAccount</span><span class="w"> </span><span class="nt">-Identity</span><span class="w"> </span><span class="bp">$_</span><span class="w">
    </span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"비활성화: </span><span class="si">$(</span><span class="bp">$_</span><span class="o">.</span><span class="nf">SamAccountName</span><span class="si">)</span><span class="s2"> (마지막 로그인: </span><span class="si">$(</span><span class="bp">$_</span><span class="o">.</span><span class="nf">LastLogonDate</span><span class="si">)</span><span class="s2">)"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h2 id="서비스-모니터링-및-자동-재시작">서비스 모니터링 및 자동 재시작</h2>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 감시할 서비스 목록</span><span class="w">
</span><span class="nv">$monitoredServices</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">@(</span><span class="s2">"W3SVC"</span><span class="p">,</span><span class="w"> </span><span class="s2">"MSSQLSERVER"</span><span class="p">,</span><span class="w"> </span><span class="s2">"WinRM"</span><span class="p">)</span><span class="w">

</span><span class="kr">foreach</span><span class="w"> </span><span class="p">(</span><span class="nv">$serviceName</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="nv">$monitoredServices</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nv">$service</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-Service</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nv">$serviceName</span><span class="w"> </span><span class="nt">-ErrorAction</span><span class="w"> </span><span class="nx">SilentlyContinue</span><span class="w">

    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="bp">$null</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="nv">$service</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="n">Write-Warning</span><span class="w"> </span><span class="s2">"서비스를 찾을 수 없음: </span><span class="nv">$serviceName</span><span class="s2">"</span><span class="w">
        </span><span class="kr">continue</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$service</span><span class="o">.</span><span class="nf">Status</span><span class="w"> </span><span class="o">-ne</span><span class="w"> </span><span class="s2">"Running"</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"서비스 재시작 중: </span><span class="nv">$serviceName</span><span class="s2">"</span><span class="w">
        </span><span class="n">Start-Service</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nv">$serviceName</span><span class="w">

        </span><span class="c"># 이벤트 로그에 기록</span><span class="w">
        </span><span class="n">Write-EventLog</span><span class="w"> </span><span class="nt">-LogName</span><span class="w"> </span><span class="nx">Application</span><span class="w"> </span><span class="se">`
</span><span class="w">                       </span><span class="nt">-Source</span><span class="w"> </span><span class="s2">"PowerShell Monitoring"</span><span class="w"> </span><span class="se">`
</span><span class="w">                       </span><span class="nt">-EventId</span><span class="w"> </span><span class="nx">1001</span><span class="w"> </span><span class="se">`
</span><span class="w">                       </span><span class="nt">-EntryType</span><span class="w"> </span><span class="nx">Warning</span><span class="w"> </span><span class="se">`
</span><span class="w">                       </span><span class="nt">-Message</span><span class="w"> </span><span class="s2">"</span><span class="nv">$serviceName</span><span class="s2"> 서비스가 중지되어 재시작했습니다."</span><span class="w">
    </span><span class="p">}</span><span class="w"> </span><span class="kr">else</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"</span><span class="nv">$serviceName</span><span class="s2"> : 정상 실행 중"</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p><img src="/assets/images/2026-01-20/2.jpg" alt="서버실 랙 장비" />
<em><center>PowerShell로 관리되는 윈도우 서버 인프라</center></em></p>

<h2 id="로그-파일-자동-정리">로그 파일 자동 정리</h2>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 30일 이상 된 로그 파일 자동 삭제</span><span class="w">
</span><span class="nv">$logPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"C:\Windows\Logs"</span><span class="w">
</span><span class="nv">$daysToKeep</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">30</span><span class="w">
</span><span class="nv">$cutoffDate</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">Get-Date</span><span class="p">)</span><span class="o">.</span><span class="nf">AddDays</span><span class="p">(</span><span class="o">-</span><span class="nv">$daysToKeep</span><span class="p">)</span><span class="w">

</span><span class="nv">$deletedFiles</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$logPath</span><span class="w"> </span><span class="nt">-Recurse</span><span class="w"> </span><span class="nt">-File</span><span class="w"> </span><span class="o">|</span><span class="w">
    </span><span class="n">Where-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">LastWriteTime</span><span class="w"> </span><span class="o">-lt</span><span class="w"> </span><span class="nv">$cutoffDate</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">Extension</span><span class="w"> </span><span class="nt">-in</span><span class="w"> </span><span class="p">@(</span><span class="s1">'.log'</span><span class="p">,</span><span class="w"> </span><span class="s1">'.txt'</span><span class="p">)</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="o">|</span><span class="w">
    </span><span class="n">ForEach-Object</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nv">$fileSize</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">math</span><span class="p">]::</span><span class="n">Round</span><span class="p">(</span><span class="bp">$_</span><span class="o">.</span><span class="nf">Length</span><span class="w"> </span><span class="n">/</span><span class="w"> </span><span class="nx">1KB</span><span class="p">,</span><span class="w"> </span><span class="nx">2</span><span class="p">)</span><span class="w">
        </span><span class="n">Remove-Item</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">FullName</span><span class="w"> </span><span class="nt">-Force</span><span class="w">
        </span><span class="s2">"</span><span class="si">$(</span><span class="bp">$_</span><span class="o">.</span><span class="nf">Name</span><span class="si">)</span><span class="s2"> (</span><span class="nv">$fileSize</span><span class="s2"> KB)"</span><span class="w">
    </span><span class="p">}</span><span class="w">

</span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"삭제된 파일 수: </span><span class="si">$(</span><span class="nv">$deletedFiles</span><span class="o">.</span><span class="nf">Count</span><span class="si">)</span><span class="s2">"</span><span class="w">
</span><span class="nv">$deletedFiles</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">ForEach-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"  - </span><span class="bp">$_</span><span class="s2">"</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h2 id="백업-자동화">백업 자동화</h2>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 중요 디렉토리를 날짜별 폴더에 백업</span><span class="w">
</span><span class="kr">function</span><span class="w"> </span><span class="nf">Backup-ServerData</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="kr">param</span><span class="p">(</span><span class="w">
        </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$SourcePath</span><span class="p">,</span><span class="w">
        </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$BackupRoot</span><span class="p">,</span><span class="w">
        </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="nv">$RetentionDays</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">14</span><span class="w">
    </span><span class="p">)</span><span class="w">

    </span><span class="nv">$dateStamp</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-Date</span><span class="w"> </span><span class="nt">-Format</span><span class="w"> </span><span class="s2">"yyyy-MM-dd"</span><span class="w">
    </span><span class="nv">$backupPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Join-Path</span><span class="w"> </span><span class="nv">$BackupRoot</span><span class="w"> </span><span class="nv">$dateStamp</span><span class="w">

    </span><span class="c"># 백업 디렉토리 생성</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">-not</span><span class="w"> </span><span class="p">(</span><span class="n">Test-Path</span><span class="w"> </span><span class="nv">$backupPath</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="n">New-Item</span><span class="w"> </span><span class="nt">-ItemType</span><span class="w"> </span><span class="nx">Directory</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$backupPath</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Out-Null</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="c"># 파일 복사</span><span class="w">
    </span><span class="n">Copy-Item</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$SourcePath</span><span class="w"> </span><span class="nt">-Destination</span><span class="w"> </span><span class="nv">$backupPath</span><span class="w"> </span><span class="nt">-Recurse</span><span class="w"> </span><span class="nt">-Force</span><span class="w">
    </span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"백업 완료: </span><span class="nv">$backupPath</span><span class="s2">"</span><span class="w">

    </span><span class="c"># 오래된 백업 정리</span><span class="w">
    </span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$BackupRoot</span><span class="w"> </span><span class="nt">-Directory</span><span class="w"> </span><span class="o">|</span><span class="w">
        </span><span class="n">Where-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">CreationTime</span><span class="w"> </span><span class="o">-lt</span><span class="w"> </span><span class="p">(</span><span class="n">Get-Date</span><span class="p">)</span><span class="o">.</span><span class="nf">AddDays</span><span class="p">(</span><span class="o">-</span><span class="nv">$RetentionDays</span><span class="p">)</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="o">|</span><span class="w">
        </span><span class="n">ForEach-Object</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="n">Remove-Item</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">FullName</span><span class="w"> </span><span class="nt">-Recurse</span><span class="w"> </span><span class="nt">-Force</span><span class="w">
            </span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"오래된 백업 삭제: </span><span class="si">$(</span><span class="bp">$_</span><span class="o">.</span><span class="nf">Name</span><span class="si">)</span><span class="s2">"</span><span class="w">
        </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="c"># 사용 예시</span><span class="w">
</span><span class="n">Backup-ServerData</span><span class="w"> </span><span class="nt">-SourcePath</span><span class="w"> </span><span class="s2">"C:\InetPub\wwwroot"</span><span class="w"> </span><span class="se">`
</span><span class="w">                  </span><span class="nt">-BackupRoot</span><span class="w"> </span><span class="s2">"D:\Backups\Web"</span><span class="w"> </span><span class="se">`
</span><span class="w">                  </span><span class="nt">-RetentionDays</span><span class="w"> </span><span class="nx">14</span><span class="w">
</span></code></pre></div></div>

<h2 id="작업-스케줄러-등록">작업 스케줄러 등록</h2>

<p>PowerShell 스크립트를 작업 스케줄러에 등록하여 정기적으로 실행합니다.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 매일 새벽 2시에 백업 스크립트 실행</span><span class="w">
</span><span class="nv">$action</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-ScheduledTaskAction</span><span class="w"> </span><span class="se">`
</span><span class="w">    </span><span class="nt">-Execute</span><span class="w"> </span><span class="s2">"PowerShell.exe"</span><span class="w"> </span><span class="se">`
</span><span class="w">    </span><span class="nt">-Argument</span><span class="w"> </span><span class="s2">"-NonInteractive -ExecutionPolicy Bypass -File C:\Scripts\backup.ps1"</span><span class="w">

</span><span class="nv">$trigger</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-ScheduledTaskTrigger</span><span class="w"> </span><span class="nt">-Daily</span><span class="w"> </span><span class="nt">-At</span><span class="w"> </span><span class="s2">"02:00"</span><span class="w">

</span><span class="nv">$settings</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-ScheduledTaskSettingsSet</span><span class="w"> </span><span class="se">`
</span><span class="w">    </span><span class="nt">-StartWhenAvailable</span><span class="w"> </span><span class="se">`
</span><span class="w">    </span><span class="nt">-RunOnlyIfNetworkAvailable</span><span class="p">:</span><span class="bp">$false</span><span class="w">

</span><span class="n">Register-ScheduledTask</span><span class="w"> </span><span class="se">`
</span><span class="w">    </span><span class="nt">-TaskName</span><span class="w"> </span><span class="s2">"DailyBackup"</span><span class="w"> </span><span class="se">`
</span><span class="w">    </span><span class="nt">-Action</span><span class="w"> </span><span class="nv">$action</span><span class="w"> </span><span class="se">`
</span><span class="w">    </span><span class="nt">-Trigger</span><span class="w"> </span><span class="nv">$trigger</span><span class="w"> </span><span class="se">`
</span><span class="w">    </span><span class="nt">-Settings</span><span class="w"> </span><span class="nv">$settings</span><span class="w"> </span><span class="se">`
</span><span class="w">    </span><span class="nt">-RunLevel</span><span class="w"> </span><span class="nx">Highest</span><span class="w"> </span><span class="se">`
</span><span class="w">    </span><span class="nt">-Force</span><span class="w">

</span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"작업 스케줄러 등록 완료"</span><span class="w">
</span></code></pre></div></div>

<h2 id="원격-서버-일괄-관리">원격 서버 일괄 관리</h2>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 여러 서버에 동시에 명령 실행</span><span class="w">
</span><span class="nv">$servers</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">@(</span><span class="s2">"WEB01"</span><span class="p">,</span><span class="w"> </span><span class="s2">"WEB02"</span><span class="p">,</span><span class="w"> </span><span class="s2">"DB01"</span><span class="p">,</span><span class="w"> </span><span class="s2">"APP01"</span><span class="p">)</span><span class="w">

</span><span class="nv">$results</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Invoke-Command</span><span class="w"> </span><span class="nt">-ComputerName</span><span class="w"> </span><span class="nv">$servers</span><span class="w"> </span><span class="nt">-ScriptBlock</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="p">[</span><span class="n">PSCustomObject</span><span class="p">]@{</span><span class="w">
        </span><span class="nx">ServerName</span><span class="w">  </span><span class="o">=</span><span class="w"> </span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">COMPUTERNAME</span><span class="w">
        </span><span class="nx">OS</span><span class="w">          </span><span class="o">=</span><span class="w"> </span><span class="err">(</span><span class="nx">Get</span><span class="err">-</span><span class="nx">WmiObject</span><span class="w"> </span><span class="nx">Win32_OperatingSystem</span><span class="err">).</span><span class="nx">Caption</span><span class="w">
        </span><span class="nx">CPU</span><span class="w">         </span><span class="o">=</span><span class="w"> </span><span class="err">(</span><span class="nx">Get</span><span class="err">-</span><span class="nx">WmiObject</span><span class="w"> </span><span class="nx">Win32_Processor</span><span class="err">).</span><span class="nx">LoadPercentage</span><span class="w">
        </span><span class="nx">FreeDiskGB</span><span class="w">  </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">math</span><span class="p">]</span><span class="err">::</span><span class="nx">Round</span><span class="err">(</span><span class="w">
            </span><span class="err">(</span><span class="nx">Get</span><span class="err">-</span><span class="nx">PSDrive</span><span class="w"> </span><span class="nx">C</span><span class="err">).</span><span class="nx">Free</span><span class="w"> </span><span class="err">/</span><span class="w"> </span><span class="mi">1</span><span class="nx">GB</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="w">
        </span><span class="err">)</span><span class="w">
        </span><span class="nx">Uptime</span><span class="w">      </span><span class="o">=</span><span class="w"> </span><span class="err">(</span><span class="nx">Get</span><span class="err">-</span><span class="nx">Date</span><span class="err">)</span><span class="w"> </span><span class="err">-</span><span class="w"> </span><span class="err">(</span><span class="nx">Get</span><span class="err">-</span><span class="nx">CimInstance</span><span class="w"> </span><span class="nx">Win32_OperatingSystem</span><span class="err">).</span><span class="nx">LastBootUpTime</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="nv">$results</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Format-Table</span><span class="w"> </span><span class="nt">-AutoSize</span><span class="w">

</span><span class="c"># CSV로 저장</span><span class="w">
</span><span class="nv">$results</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Export-Csv</span><span class="w"> </span><span class="s2">"C:\Reports\server-health-</span><span class="si">$(</span><span class="n">Get-Date</span><span class="w"> </span><span class="nt">-Format</span><span class="w"> </span><span class="s1">'yyyyMMdd'</span><span class="p">)</span><span class="s2">.csv"</span><span class="w"> </span><span class="se">`
</span><span class="w">           </span><span class="nt">-NoTypeInformation</span><span class="w"> </span><span class="nt">-Encoding</span><span class="w"> </span><span class="nx">UTF8</span><span class="w">
</span></code></pre></div></div>

<h2 id="powershell-모듈-관리">PowerShell 모듈 관리</h2>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># PowerShellGet으로 유용한 모듈 설치</span><span class="w">
</span><span class="n">Install-Module</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nx">PSWindowsUpdate</span><span class="w"> </span><span class="nt">-Scope</span><span class="w"> </span><span class="nx">AllUsers</span><span class="w"> </span><span class="nt">-Force</span><span class="w">
</span><span class="n">Install-Module</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nx">Az</span><span class="w"> </span><span class="nt">-Scope</span><span class="w"> </span><span class="nx">AllUsers</span><span class="w"> </span><span class="nt">-AllowClobber</span><span class="w"> </span><span class="nt">-Force</span><span class="w">  </span><span class="c"># Azure 관리</span><span class="w">
</span><span class="n">Install-Module</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nx">dbatools</span><span class="w"> </span><span class="nt">-Scope</span><span class="w"> </span><span class="nx">AllUsers</span><span class="w"> </span><span class="nt">-Force</span><span class="w">           </span><span class="c"># SQL Server 관리</span><span class="w">

</span><span class="c"># 설치된 모듈 확인</span><span class="w">
</span><span class="n">Get-Module</span><span class="w"> </span><span class="nt">-ListAvailable</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Select-Object</span><span class="w"> </span><span class="nx">Name</span><span class="p">,</span><span class="w"> </span><span class="nx">Version</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Sort-Object</span><span class="w"> </span><span class="nx">Name</span><span class="w">

</span><span class="c"># Windows Update 자동화 (PSWindowsUpdate 사용)</span><span class="w">
</span><span class="n">Import-Module</span><span class="w"> </span><span class="nx">PSWindowsUpdate</span><span class="w">
</span><span class="n">Get-WindowsUpdate</span><span class="w"> </span><span class="nt">-AcceptAll</span><span class="w"> </span><span class="nt">-Install</span><span class="w"> </span><span class="nt">-AutoReboot</span><span class="w">
</span></code></pre></div></div>

<hr />

<p>PowerShell 자동화는 윈도우 서버 관리의 효율을 크게 높여줍니다. 스크립트를 처음 작성할 때는 <code class="language-plaintext highlighter-rouge">-WhatIf</code> 매개변수를 활용해 실제 변경 없이 동작을 미리 확인하는 습관을 들이세요. 또한 스크립트를 Git으로 버전 관리하면 변경 이력을 추적하고 팀원과 공유하기 쉬워집니다. 작은 자동화부터 시작해 점차 범위를 넓혀가는 것이 실용적인 접근 방식입니다.</p>]]></content><author><name>사쿠라호스팅</name></author><category term="윈도우 서버" /><category term="자동화" /><category term="PowerShell" /><category term="윈도우서버" /><category term="자동화" /><category term="스크립트" /><category term="시스템관리" /><summary type="html"><![CDATA[PowerShell을 활용하여 윈도우 서버의 반복 작업을 자동화하는 방법을 실전 예제와 함께 알아봅니다. 서버 관리 효율을 극대화하는 스크립트 모음을 소개합니다.]]></summary></entry><entry><title type="html">리눅스 서버에 Elasticsearch 설치 및 기본 설정</title><link href="https://blog.sakura-idc.com/%EB%A6%AC%EB%88%85%EC%8A%A4/%EA%B2%80%EC%83%89%EC%97%94%EC%A7%84/Elasticsearch-%EC%84%A4%EC%B9%98-%EB%B0%8F-%EA%B8%B0%EB%B3%B8-%EC%84%A4%EC%A0%95/" rel="alternate" type="text/html" title="리눅스 서버에 Elasticsearch 설치 및 기본 설정" /><published>2026-01-12T10:00:00+09:00</published><updated>2026-01-12T10:00:00+09:00</updated><id>https://blog.sakura-idc.com/%EB%A6%AC%EB%88%85%EC%8A%A4/%EA%B2%80%EC%83%89%EC%97%94%EC%A7%84/Elasticsearch-%EC%84%A4%EC%B9%98-%EB%B0%8F-%EA%B8%B0%EB%B3%B8-%EC%84%A4%EC%A0%95</id><content type="html" xml:base="https://blog.sakura-idc.com/%EB%A6%AC%EB%88%85%EC%8A%A4/%EA%B2%80%EC%83%89%EC%97%94%EC%A7%84/Elasticsearch-%EC%84%A4%EC%B9%98-%EB%B0%8F-%EA%B8%B0%EB%B3%B8-%EC%84%A4%EC%A0%95/"><![CDATA[<p>Elasticsearch는 분산 검색 및 분석 엔진으로, 로그 분석(ELK 스택), 전문 검색, 실시간 분석에 널리 사용됩니다. 올바른 초기 설정이 이후 운영 안정성에 큰 영향을 미칩니다.</p>

<p><img src="/assets/images/2026-01-12/1.jpg" alt="서버룸 이미지" />
<em><center>Elasticsearch 클러스터가 운영되는 서버 환경</center></em></p>

<h2 id="elasticsearch-설치">Elasticsearch 설치</h2>

<h3 id="rhel--rocky-linux--almalinux">RHEL / Rocky Linux / AlmaLinux</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># GPG 키 추가 및 저장소 설정</span>
rpm <span class="nt">--import</span> https://artifacts.elastic.co/GPG-KEY-elasticsearch

<span class="nb">cat</span> <span class="o">&gt;</span> /etc/yum.repos.d/elasticsearch.repo <span class="o">&lt;&lt;</span> <span class="sh">'</span><span class="no">EOF</span><span class="sh">'
[elasticsearch]
name=Elasticsearch repository for 8.x packages
baseurl=https://artifacts.elastic.co/packages/8.x/yum
gpgcheck=1
gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch
enabled=1
autorefresh=1
type=rpm-md
</span><span class="no">EOF

</span><span class="c"># 설치</span>
dnf <span class="nb">install </span>elasticsearch <span class="nt">-y</span>
</code></pre></div></div>

<h3 id="ubuntu--debian">Ubuntu / Debian</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># GPG 키 추가</span>
wget <span class="nt">-qO</span> - https://artifacts.elastic.co/GPG-KEY-elasticsearch | <span class="se">\</span>
  gpg <span class="nt">--dearmor</span> <span class="nt">-o</span> /usr/share/keyrings/elasticsearch-keyring.gpg

<span class="c"># 저장소 추가</span>
<span class="nb">echo</span> <span class="s2">"deb [signed-by=/usr/share/keyrings/elasticsearch-keyring.gpg] </span><span class="se">\</span><span class="s2">
  https://artifacts.elastic.co/packages/8.x/apt stable main"</span> | <span class="se">\</span>
  <span class="nb">tee</span> /etc/apt/sources.list.d/elastic-8.x.list

<span class="c"># 설치</span>
apt update <span class="o">&amp;&amp;</span> apt <span class="nb">install </span>elasticsearch <span class="nt">-y</span>
</code></pre></div></div>

<h2 id="주요-설정-파일">주요 설정 파일</h2>

<h3 id="elasticsearchyml">elasticsearch.yml</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># /etc/elasticsearch/elasticsearch.yml</span>

<span class="c1"># 클러스터 및 노드 이름</span>
<span class="na">cluster.name</span><span class="pi">:</span> <span class="s">my-cluster</span>
<span class="na">node.name</span><span class="pi">:</span> <span class="s">node-1</span>

<span class="c1"># 데이터 및 로그 경로</span>
<span class="na">path.data</span><span class="pi">:</span> <span class="s">/var/lib/elasticsearch</span>
<span class="na">path.logs</span><span class="pi">:</span> <span class="s">/var/log/elasticsearch</span>

<span class="c1"># 네트워크 설정</span>
<span class="na">network.host</span><span class="pi">:</span> <span class="s">0.0.0.0</span>
<span class="na">http.port</span><span class="pi">:</span> <span class="m">9200</span>

<span class="c1"># 클러스터 초기 마스터 노드 (단일 노드)</span>
<span class="na">discovery.type</span><span class="pi">:</span> <span class="s">single-node</span>

<span class="c1"># JVM 힙 메모리 (RAM의 50%, 최대 32GB)</span>
<span class="c1"># /etc/elasticsearch/jvm.options.d/heap.options 에서 설정</span>

<span class="c1"># X-Pack 보안 (기본 활성화)</span>
<span class="na">xpack.security.enabled</span><span class="pi">:</span> <span class="kc">true</span>
<span class="na">xpack.security.http.ssl.enabled</span><span class="pi">:</span> <span class="kc">false</span>  <span class="c1"># 개발환경에서 비활성화</span>

<span class="c1"># 인덱스 자동 생성 허용</span>
<span class="na">action.auto_create_index</span><span class="pi">:</span> <span class="kc">true</span>
</code></pre></div></div>

<h3 id="jvm-힙-메모리-설정">JVM 힙 메모리 설정</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># /etc/elasticsearch/jvm.options.d/heap.options</span>
<span class="nb">cat</span> <span class="o">&gt;</span> /etc/elasticsearch/jvm.options.d/heap.options <span class="o">&lt;&lt;</span> <span class="sh">'</span><span class="no">EOF</span><span class="sh">'
# 서버 RAM의 50% 할당 (최대 32GB)
# 16GB RAM 서버:
-Xms8g
-Xmx8g
</span><span class="no">EOF
</span></code></pre></div></div>

<h2 id="서비스-시작-및-확인">서비스 시작 및 확인</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 시스템 최대 파일 디스크립터 설정 (필수)</span>
<span class="nb">cat</span> <span class="o">&gt;&gt;</span> /etc/security/limits.conf <span class="o">&lt;&lt;</span> <span class="sh">'</span><span class="no">EOF</span><span class="sh">'
elasticsearch soft nofile 65536
elasticsearch hard nofile 65536
</span><span class="no">EOF

</span><span class="c"># 가상 메모리 맵 설정 (필수)</span>
sysctl <span class="nt">-w</span> vm.max_map_count<span class="o">=</span>262144
<span class="nb">echo</span> <span class="s2">"vm.max_map_count=262144"</span> <span class="o">&gt;&gt;</span> /etc/sysctl.conf

<span class="c"># 서비스 시작</span>
systemctl daemon-reload
systemctl <span class="nb">enable </span>elasticsearch
systemctl start elasticsearch

<span class="c"># 상태 확인</span>
systemctl status elasticsearch

<span class="c"># 초기 비밀번호 확인 (최초 설치 시)</span>
/usr/share/elasticsearch/bin/elasticsearch-reset-password <span class="nt">-u</span> elastic
</code></pre></div></div>

<h2 id="클러스터-상태-확인">클러스터 상태 확인</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 클러스터 상태 (보안 비활성화 환경)</span>
curl <span class="nt">-X</span> GET <span class="s2">"localhost:9200/_cluster/health?pretty"</span>

<span class="c"># 인증 포함</span>
curl <span class="nt">-u</span> elastic:password <span class="nt">-X</span> GET <span class="s2">"localhost:9200/_cluster/health?pretty"</span>

<span class="c"># 출력 예시:</span>
<span class="c"># {</span>
<span class="c">#   "cluster_name" : "my-cluster",</span>
<span class="c">#   "status" : "green",</span>
<span class="c">#   "number_of_nodes" : 1,</span>
<span class="c">#   "active_shards" : 10</span>
<span class="c"># }</span>

<span class="c"># 노드 정보</span>
curl <span class="nt">-u</span> elastic:password <span class="s2">"localhost:9200/_cat/nodes?v"</span>

<span class="c"># 인덱스 목록</span>
curl <span class="nt">-u</span> elastic:password <span class="s2">"localhost:9200/_cat/indices?v"</span>
</code></pre></div></div>

<p><img src="/assets/images/2026-01-12/2.jpg" alt="서버 코드 이미지" />
<em><center>Elasticsearch API를 통해 데이터를 조회하는 화면</center></em></p>

<h2 id="인덱스-생성-및-데이터-삽입">인덱스 생성 및 데이터 삽입</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 인덱스 생성</span>
curl <span class="nt">-u</span> elastic:password <span class="nt">-X</span> PUT <span class="s2">"localhost:9200/logs-2026.01"</span> <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Content-Type: application/json"</span> <span class="nt">-d</span> <span class="s1">'{
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 0,
    "index.refresh_interval": "30s"
  },
  "mappings": {
    "properties": {
      "@timestamp": { "type": "date" },
      "level": { "type": "keyword" },
      "message": { "type": "text" },
      "host": { "type": "keyword" }
    }
  }
}'</span>

<span class="c"># 문서 삽입</span>
curl <span class="nt">-u</span> elastic:password <span class="nt">-X</span> POST <span class="s2">"localhost:9200/logs-2026.01/_doc"</span> <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Content-Type: application/json"</span> <span class="nt">-d</span> <span class="s1">'{
  "@timestamp": "2026-01-12T10:00:00Z",
  "level": "ERROR",
  "message": "Connection timeout to database",
  "host": "web01"
}'</span>

<span class="c"># 검색</span>
curl <span class="nt">-u</span> elastic:password <span class="nt">-X</span> GET <span class="s2">"localhost:9200/logs-2026.01/_search?pretty"</span> <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Content-Type: application/json"</span> <span class="nt">-d</span> <span class="s1">'{
  "query": {
    "match": { "level": "ERROR" }
  }
}'</span>
</code></pre></div></div>

<h2 id="ilm인덱스-생명주기-관리-설정">ILM(인덱스 생명주기 관리) 설정</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 30일 후 Hot → Warm, 90일 후 삭제하는 정책</span>
curl <span class="nt">-u</span> elastic:password <span class="nt">-X</span> PUT <span class="s2">"localhost:9200/_ilm/policy/log-policy"</span> <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Content-Type: application/json"</span> <span class="nt">-d</span> <span class="s1">'{
  "policy": {
    "phases": {
      "hot": {
        "min_age": "0ms",
        "actions": {
          "rollover": {
            "max_age": "7d",
            "max_size": "50gb"
          }
        }
      },
      "warm": {
        "min_age": "30d",
        "actions": {
          "shrink": { "number_of_shards": 1 },
          "forcemerge": { "max_num_segments": 1 }
        }
      },
      "delete": {
        "min_age": "90d",
        "actions": { "delete": {} }
      }
    }
  }
}'</span>
</code></pre></div></div>

<h2 id="스냅샷-백업">스냅샷 백업</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 파일 시스템 저장소 등록</span>
curl <span class="nt">-u</span> elastic:password <span class="nt">-X</span> PUT <span class="s2">"localhost:9200/_snapshot/my_backup"</span> <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Content-Type: application/json"</span> <span class="nt">-d</span> <span class="s1">'{
  "type": "fs",
  "settings": {
    "location": "/var/backups/elasticsearch"
  }
}'</span>

<span class="c"># 스냅샷 생성</span>
curl <span class="nt">-u</span> elastic:password <span class="nt">-X</span> PUT <span class="s2">"localhost:9200/_snapshot/my_backup/snapshot-</span><span class="si">$(</span><span class="nb">date</span> +%Y%m%d<span class="si">)</span><span class="s2">?wait_for_completion=true"</span>

<span class="c"># 스냅샷 목록</span>
curl <span class="nt">-u</span> elastic:password <span class="s2">"localhost:9200/_snapshot/my_backup/_all?pretty"</span>
</code></pre></div></div>

<hr />

<p>Elasticsearch 운영에서 가장 중요한 것은 적절한 JVM 힙 크기 설정과 디스크 공간 관리입니다. ILM 정책을 통해 오래된 인덱스를 자동으로 정리하고, 정기적인 스냅샷 백업으로 데이터를 보호하세요.</p>]]></content><author><name>사쿠라호스팅</name></author><category term="리눅스" /><category term="검색엔진" /><category term="Elasticsearch" /><category term="ELK" /><category term="로그분석" /><category term="검색엔진" /><category term="리눅스" /><summary type="html"><![CDATA[Elasticsearch를 리눅스 서버에 설치하고 기본 설정을 최적화하여 로그 분석 및 검색 서비스로 활용하는 방법을 알아봅니다.]]></summary></entry></feed>