<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>개발자 꿈나무</title>
    <link>https://sohxxny.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sun, 10 May 2026 14:20:23 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>카페모카 소믈리에</managingEditor>
    <item>
      <title>nvm으로 Node.js 버전업 후 Nginx 502 문제 해결하기</title>
      <link>https://sohxxny.tistory.com/8</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. Node.js 버전 관련 문제 발생&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sohxxny.tistory.com/5&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이전 글&lt;/a&gt;에서 NCP 서버에 Next.js를 배포할 때 &lt;code&gt;apt&lt;/code&gt;로 Node.js를 설치했다고 했는데, 여기에서 언급했던 이슈에 대해 작성해보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포 사이트에 접속했더니 &lt;b&gt;&lt;span data-token-index=&quot;1&quot;&gt;502 Bad Gateway&lt;/span&gt;&lt;/b&gt;가 뜨는 문제가 생겼다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-17 오후 7.10.24.png&quot; data-origin-width=&quot;604&quot; data-origin-height=&quot;230&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brTKLc/dJMcabjDb8o/js3c9unkVcxkzZTnXJ0rF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brTKLc/dJMcabjDb8o/js3c9unkVcxkzZTnXJ0rF1/img.png&quot; data-alt=&quot;배포 서버에서 발생한 502 에러&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brTKLc/dJMcabjDb8o/js3c9unkVcxkzZTnXJ0rF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrTKLc%2FdJMcabjDb8o%2Fjs3c9unkVcxkzZTnXJ0rF1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;114&quot; data-filename=&quot;스크린샷 2025-12-17 오후 7.10.24.png&quot; data-origin-width=&quot;604&quot; data-origin-height=&quot;230&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;배포 서버에서 발생한 502 에러&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 Nginx 문제인 줄 알았는데 SSH 접속 후 &lt;code&gt;pm2 list&lt;/code&gt;로 확인해보니 prod 프로세스의 status가 &lt;code&gt;errored&lt;/code&gt;였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-17 오후 7.11.51.png&quot; data-origin-width=&quot;1110&quot; data-origin-height=&quot;138&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7UPPS/dJMcagZuyMs/dLIiVPItdummmjrVr5QTk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7UPPS/dJMcagZuyMs/dLIiVPItdummmjrVr5QTk1/img.png&quot; data-alt=&quot;PM2 프로세스 리스트&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7UPPS/dJMcagZuyMs/dLIiVPItdummmjrVr5QTk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7UPPS%2FdJMcagZuyMs%2FdLIiVPItdummmjrVr5QTk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;75&quot; data-filename=&quot;스크린샷 2025-12-17 오후 7.11.51.png&quot; data-origin-width=&quot;1110&quot; data-origin-height=&quot;138&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;PM2 프로세스 리스트&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;pm2 logs&lt;/code&gt;로 최근 로그 확인 후 원인을 알 수 있었다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;You are using Node.js 18.19.1. For Next.js, Node.js version &quot;&amp;gt;=20.9.0&quot; is required.&lt;/i&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 보안 이슈로 Next.js 버전을 올리면서 요구하는 Node.js 최소 버전도 올라간 것 같았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. nvm으로 Node.js 업그레이드&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nvm을 설치해서 Node.js 버전을 올렸다.&lt;/p&gt;
&lt;pre id=&quot;code_1776583444490&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 1. nvm 설치
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash

# 2. 셸 설정 새로고침
source ~/.zshrc

# 3. nvm 설치 확인
nvm --version

# 4. Node.js 20 설치 및 설정
nvm install 20
nvm use 20
nvm alias default 20

# 5. 버전 확인
node -v&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 프로젝트 의존성 재설치 및 빌드, PM2 재시작까지 했다.&lt;/p&gt;
&lt;pre id=&quot;code_1776583457116&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cd ~/project-prod
rm -rf node_modules package-lock.json
npm install
npm run build
cd ..
pm2 restart prod&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 얼마 후 똑같은 오류가 다시 발생했다  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. PM2가 버전업된 Node.js를 인식하지 못하는 원인 찾기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;node -v&lt;/code&gt;를 치면 분명 v20이 나왔다. 그런데 왜 PM2는 여전히 Node.js 18을 쓰고 있는 걸까?!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;pm2 describe prod&lt;/code&gt; 명령어로 프로세스 상세 정보를 확인해봤다. 하단에 &lt;b&gt;Divergent env variables from local env&lt;/b&gt; 항목이 보이는데, PM2 프로세스가 갖고 있는 환경변수와 현재 셸의 환경변수가 다르다는 걸 알려주는 부분이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1112&quot; data-origin-height=&quot;199&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lIncf/dJMcagZuzaR/z2V98EzIWlDgzV2IU6A4xK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lIncf/dJMcagZuzaR/z2V98EzIWlDgzV2IU6A4xK/img.png&quot; data-alt=&quot;PM2 프로세스와 현재 셸에서 다른 환경변수들&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lIncf/dJMcagZuzaR/z2V98EzIWlDgzV2IU6A4xK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlIncf%2FdJMcagZuzaR%2Fz2V98EzIWlDgzV2IU6A4xK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;107&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1112&quot; data-origin-height=&quot;199&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;PM2 프로세스와 현재 셸에서 다른 환경변수들&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘의 PATH가 달랐다. 현재 셸은 nvm 경로를 갖고 있었지만, PM2 데몬은 nvm 설치 이전의 PATH를 그대로 갖고 있었다.&lt;/p&gt;
&lt;pre id=&quot;code_1776583521054&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# describe에서 보이는 PM2의 환경변수
# /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:...

# 실제 셸의 환경변수
echo $PATH
# /root/.nvm/versions/node/v20.19.6/bin:/usr/local/sbin:...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PM2는 &lt;b&gt;&lt;span data-token-index=&quot;1&quot;&gt;처음 실행된 시점&lt;/span&gt;&lt;/b&gt;의 환경변수를 유지한다. nvm을 나중에 설치해서 PATH가 바뀌어도 이미 떠 있는 PM2 데몬은 변경 사항을 반영하지 않는다.&lt;/p&gt;
&lt;figure id=&quot;og_1776583536359&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;PM2 - Startup Script&quot; data-og-description=&quot;PM2 is an advanced production process manager for Node.js applications with built-in load balancer, zero-downtime reload, startup scripts, monitoring, and microservice management features.&quot; data-og-host=&quot;pm2.keymetrics.io&quot; data-og-source-url=&quot;https://pm2.keymetrics.io/docs/usage/startup/&quot; data-og-url=&quot;https://pm2.keymetrics.io/docs/usage/startup/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cy2Ybx/dJMb8TCbyfJ/8mR9uye51Eq25Q9oXBMA60/img.png?width=710&amp;amp;height=200&amp;amp;face=0_0_710_200&quot;&gt;&lt;a href=&quot;https://pm2.keymetrics.io/docs/usage/startup/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pm2.keymetrics.io/docs/usage/startup/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cy2Ybx/dJMb8TCbyfJ/8mR9uye51Eq25Q9oXBMA60/img.png?width=710&amp;amp;height=200&amp;amp;face=0_0_710_200');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;PM2 - Startup Script&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;PM2 is an advanced production process manager for Node.js applications with built-in load balancer, zero-downtime reload, startup scripts, monitoring, and microservice management features.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pm2.keymetrics.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;When you upgrade the local Node.js version, be sure to update the PM2 startup script, so it runs the latest Node.js binary you have installed.&lt;/i&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. PM2 startup 스크립트 업데이트&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 다음 명령어를 이용해서 바뀐 환경을 반영할 수 있다고 되어있다.&lt;/p&gt;
&lt;pre id=&quot;code_1776583568117&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 기존 설정 제거
pm2 unstartup

# 새로운 설정 반영
pm2 startup&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PM2를 완전히 종료하고 시작하는 방법도 있다. 사실 나는 이렇게 해결했는데 뒤늦게 공식 문서 설명을 발견했다 ㅎ&lt;/p&gt;
&lt;pre id=&quot;code_1776583579949&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pm2 kill
pm2 start npm --name &quot;prod&quot; -- start&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하니 &lt;b&gt;Divergent env variables from local env&lt;/b&gt; 항목에서 PATH 부분이 사라졌고, 다음부터 버전 오류가 뜨지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음부터는 처음 Node.js를 설치할 때 nvm으로 설치하는게 좋을 것 같다!&lt;/p&gt;</description>
      <category>DevOps</category>
      <category>NCP</category>
      <category>next.js</category>
      <category>nvm</category>
      <category>pm2</category>
      <category>배포</category>
      <author>카페모카 소믈리에</author>
      <guid isPermaLink="true">https://sohxxny.tistory.com/8</guid>
      <comments>https://sohxxny.tistory.com/8#entry8comment</comments>
      <pubDate>Sun, 19 Apr 2026 16:32:14 +0900</pubDate>
    </item>
    <item>
      <title>이미지를 최적화해도 LCP가 개선되지 않을 때</title>
      <link>https://sohxxny.tistory.com/7</link>
      <description>&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. LCP 요소 파악하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lighthouse로 성능을 측정했더니 LCP(Largest Contentful Paint)가 2.9s ~ 3.1s로 높게 나왔다. LCP 기준이 2.5s 이하인데 훨씬 넘기고 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-03-30 오후 3.59.09.png&quot; data-origin-width=&quot;380&quot; data-origin-height=&quot;132&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BysM2/dJMcaakrLuF/CwkfCWkNK2JWKKr9AFVbX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BysM2/dJMcaakrLuF/CwkfCWkNK2JWKKr9AFVbX0/img.png&quot; data-alt=&quot;LCP 점수 (before)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BysM2/dJMcaakrLuF/CwkfCWkNK2JWKKr9AFVbX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBysM2%2FdJMcaakrLuF%2FCwkfCWkNK2JWKKr9AFVbX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;190&quot; height=&quot;66&quot; data-filename=&quot;스크린샷 2026-03-30 오후 3.59.09.png&quot; data-origin-width=&quot;380&quot; data-origin-height=&quot;132&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;LCP 점수 (before)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lighthouse의 INSIGHTS 부분에서 LCP Breakdown을 눌러보면 현재 LCP를 차지하는 요소가 무엇인지 확인할 수 있다. 확인해보니 배너 이미지가 LCP 요소로 잡혀 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1126&quot; data-origin-height=&quot;852&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b50RKQ/dJMcahw7vgM/xkeJaH4jqff6ymTTu9Ju5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b50RKQ/dJMcahw7vgM/xkeJaH4jqff6ymTTu9Ju5K/img.png&quot; data-alt=&quot;LCP 요소 확인&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b50RKQ/dJMcahw7vgM/xkeJaH4jqff6ymTTu9Ju5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb50RKQ%2FdJMcahw7vgM%2FxkeJaH4jqff6ymTTu9Ju5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;430&quot; height=&quot;325&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1126&quot; data-origin-height=&quot;852&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;LCP 요소 확인&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 원인 찾기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배너 이미지가 원래 CSS &lt;code&gt;background-image&lt;/code&gt;로 처리되어 있었는데, Next.js의 &lt;code&gt;&amp;lt;Image&amp;gt;&lt;/code&gt; 태그로 변경하고 아래 설정을 추가했다.&lt;/p&gt;
&lt;pre id=&quot;code_1774853147087&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;Image
  src={bannerImage}
  priority
  fetchPriority=&quot;high&quot;
  ...
/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LCP request discovery의 경고는 사라졌지만 LCP는 그대로였다. 아무리 이미지 설정을 바꿔도 개선되지 않아서 배너를 아예 제거해봤는데, 그래도 LCP가 그대로인걸 보니 배너가 문제가 아니었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지가 있는 요소들을 하나씩 제거해가며 확인하면서 결국 텍스트가 있는 footer만 남겼는데도 LCP가 똑같았다. 이때 LCP Breakdown을 다시 확인하니 &lt;b&gt;LCP 요소가 이미지가 아닌 텍스트&lt;/b&gt;로 잡혀 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시 폰트가 문제일까 싶어서 폰트 로드를 제거해봤더니, 배너 이미지가 있는 상태에서도 LCP가 정상 범위로 내려왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;원인은 폰트였다&amp;hellip;  &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3.&lt;span&gt; 폰트 Subset 적용하기&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디자인 시안을 따라야 하기 때문에 폰트를 아예 제거하는 건 불가능하다. 대신 &lt;b&gt;Subset 폰트&lt;/b&gt;를 사용하는 방법이 있다. 이 프로젝트에서는 Pretendard를 사용하고 있었는데, Pretendard는 Subset 버전도 제공한다.&lt;/p&gt;
&lt;figure id=&quot;og_1774853196916&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Pretendard&quot; data-og-description=&quot;Pretendard 프리텐다드 Pretendard 프리텐다드 글꼴 다운로드 일본어 버전 다운로드 GitHub에서 보기 폰트 제작 및 운영 클래스 ..&quot; data-og-host=&quot;cactus.tistory.com&quot; data-og-source-url=&quot;https://cactus.tistory.com/306&quot; data-og-url=&quot;https://cactus.tistory.com/306&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/TkHSr/dJMb8U8TCAA/JlnzoLxe8L6cvKCjgssnGk/img.png?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/cflWaX/dJMb8SpH0cr/wSkAlTBLK0Enkm0DKRu9J0/img.png?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450&quot;&gt;&lt;a href=&quot;https://cactus.tistory.com/306&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://cactus.tistory.com/306&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/TkHSr/dJMb8U8TCAA/JlnzoLxe8L6cvKCjgssnGk/img.png?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/cflWaX/dJMb8SpH0cr/wSkAlTBLK0Enkm0DKRu9J0/img.png?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Pretendard&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Pretendard 프리텐다드 Pretendard 프리텐다드 글꼴 다운로드 일본어 버전 다운로드 GitHub에서 보기 폰트 제작 및 운영 클래스 ..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;cactus.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다운로드 후 &lt;code&gt;web/static&lt;/code&gt; 폴더에서 &lt;code&gt;woff2-subset&lt;/code&gt; 또는 &lt;code&gt;woff-subset&lt;/code&gt;을 선택한다. 기존에 &lt;code&gt;woff2&lt;/code&gt;를 사용하고 있었기 때문에 &lt;code&gt;woff2-subset&lt;/code&gt;을 선택했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 폰트 파일을 제거하고, 프로젝트에서 실제로 사용하는 굵기의 파일만 추가했다. 이 프로젝트에서는 400, 500, 600, 700만 사용하고 있어서 이 4개만 넣고 &lt;code&gt;layout.tsx&lt;/code&gt;를 아래와 같이 수정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;변경 전&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1774853225784&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const pretendard = localFont({
  src: './fonts/pretendard/PretendardVariable.woff2',
  display: 'swap',
  variable: '--font-pretendard',
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;변경 후&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1774853238385&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const pretendard = localFont({
  src: [
    {
      path: './fonts/pretendard/Pretendard-Regular.subset.woff2',
      weight: '400',
    },
    {
      path: './fonts/pretendard/Pretendard-Medium.subset.woff2',
      weight: '500',
    },
    {
      path: './fonts/pretendard/Pretendard-SemiBold.subset.woff2',
      weight: '600',
    },
    {
      path: './fonts/pretendard/Pretendard-Bold.subset.woff2',
      weight: '700',
    },
  ],
  display: 'swap',
  variable: '--font-pretendard',
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4.&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;결과&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Subset 폰트 적용 후 &lt;b&gt;LCP가 평균 2.9s &amp;rarr; 1.4s로 약 50% 개선&lt;/b&gt;되었고, Lighthouse의 Performance 지표도 90점대로 올랐다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-03-30 오후 4.00.57.png&quot; data-origin-width=&quot;380&quot; data-origin-height=&quot;132&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crzqvW/dJMcaiW2zAF/qiy5oSQFDoKiK1gpQkkpI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crzqvW/dJMcaiW2zAF/qiy5oSQFDoKiK1gpQkkpI1/img.png&quot; data-alt=&quot;LCP 점수 (after)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crzqvW/dJMcaiW2zAF/qiy5oSQFDoKiK1gpQkkpI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrzqvW%2FdJMcaiW2zAF%2Fqiy5oSQFDoKiK1gpQkkpI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;190&quot; height=&quot;66&quot; data-filename=&quot;스크린샷 2026-03-30 오후 4.00.57.png&quot; data-origin-width=&quot;380&quot; data-origin-height=&quot;132&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;LCP 점수 (after)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LCP 요소가 이미지일 거라고 생각하기 쉽지만 폰트가 원인인 경우도 있다. LCP가 이상하게 높게 나온다면 폰트도 의심해보자!&lt;/p&gt;</description>
      <category>성능 최적화</category>
      <category>LCP</category>
      <category>Lighthouse</category>
      <category>next.js</category>
      <category>Pretendard</category>
      <category>성능 최적화</category>
      <author>카페모카 소믈리에</author>
      <guid isPermaLink="true">https://sohxxny.tistory.com/7</guid>
      <comments>https://sohxxny.tistory.com/7#entry7comment</comments>
      <pubDate>Mon, 30 Mar 2026 16:18:42 +0900</pubDate>
    </item>
    <item>
      <title>NCP 서버에 Next.js 배포하기 (2) - 프로덕션 환경 구축하기</title>
      <link>https://sohxxny.tistory.com/6</link>
      <description>&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 들어가며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1편에서는 NCP 서버를 생성하고 Next.js 프로젝트를 배포하는 기본적인 과정에 대해 썼다.&lt;/p&gt;
&lt;figure id=&quot;og_1770627180682&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;NCP 서버에 Next.js 배포하기 (1) - 서버 생성 및 실행&quot; data-og-description=&quot;1. 들어가며Vercel 같은 플랫폼이 아닌 일반 서버에 Next.js를 배포하는 것은 처음이라 공식 문서와 AI의 도움을 많이 받았다. 배포하면서 내가 네트워크에 대한 지식이 많이 부족하다는 것을 새삼 &quot; data-og-host=&quot;sohxxny.tistory.com&quot; data-og-source-url=&quot;https://sohxxny.tistory.com/5&quot; data-og-url=&quot;https://sohxxny.tistory.com/5&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bY54DP/dJMb9eTLpxh/1S2qWTQW8XNTMEKXiVhnN0/img.png?width=755&amp;amp;height=424&amp;amp;face=0_0_755_424,https://scrap.kakaocdn.net/dn/k6h3w/dJMb9kl8AKE/u4TiMFWsW7AGnTWBOKNcDK/img.png?width=755&amp;amp;height=424&amp;amp;face=0_0_755_424,https://scrap.kakaocdn.net/dn/yC0wW/dJMb8PGrV2y/7B3RdnC5jYKGxutKyKj531/img.png?width=2064&amp;amp;height=1382&amp;amp;face=0_0_2064_1382&quot;&gt;&lt;a href=&quot;https://sohxxny.tistory.com/5&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sohxxny.tistory.com/5&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bY54DP/dJMb9eTLpxh/1S2qWTQW8XNTMEKXiVhnN0/img.png?width=755&amp;amp;height=424&amp;amp;face=0_0_755_424,https://scrap.kakaocdn.net/dn/k6h3w/dJMb9kl8AKE/u4TiMFWsW7AGnTWBOKNcDK/img.png?width=755&amp;amp;height=424&amp;amp;face=0_0_755_424,https://scrap.kakaocdn.net/dn/yC0wW/dJMb8PGrV2y/7B3RdnC5jYKGxutKyKj531/img.png?width=2064&amp;amp;height=1382&amp;amp;face=0_0_2064_1382');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;NCP 서버에 Next.js 배포하기 (1) - 서버 생성 및 실행&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1. 들어가며Vercel 같은 플랫폼이 아닌 일반 서버에 Next.js를 배포하는 것은 처음이라 공식 문서와 AI의 도움을 많이 받았다. 배포하면서 내가 네트워크에 대한 지식이 많이 부족하다는 것을 새삼&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sohxxny.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 배포는 되었다고 할 수 있지만 아직 다음과 같은 문제가 있다.  &lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;IP 주소와 포트 번호로만 접속할 수 있다. (&lt;code&gt;http://서버IP:3000&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;dev 환경에 누구나 접속할 수 있다.&lt;/li&gt;
&lt;li&gt;코드를 수정할 때마다 SSH로 접속해서 수동으로 빌드 후 배포해야 한다.&lt;/li&gt;
&lt;li&gt;HTTP만 지원하고 HTTPS가 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 이런 문제들을 해결해서 실제 서비스와 비슷한 배포 환경을 만들어본 과정을 정리해보려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. Nginx 설치 및 설정&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.1 Nginx 시작하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nginx는 (웹 서버 또는) &lt;b&gt;리버스 프록시 서버&lt;/b&gt;로, 클라이언트의 요청을 받아서 Next.js 서버로 전달하는 역할을 한다. 포트 번호 없이 도메인만으로 접속하려면 80번 포트(HTTP) 또는 443번 포트(HTTPS)를 사용해야 하는데, Nginx가 이 포트에서 요청을 받아 Next.js로 전달해준다.&lt;/p&gt;
&lt;figure id=&quot;og_1770628286501&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;F5 NGINX Product Documentation&quot; data-og-description=&quot;&quot; data-og-host=&quot;docs.nginx.com&quot; data-og-source-url=&quot;https://docs.nginx.com/&quot; data-og-url=&quot;https://docs.nginx.com/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dgGAyu/dJMb85vKDRi/4ZffgwSrWA5CYASCefRJkk/img.png?width=500&amp;amp;height=300&amp;amp;face=0_0_500_300&quot;&gt;&lt;a href=&quot;https://docs.nginx.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.nginx.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dgGAyu/dJMb85vKDRi/4ZffgwSrWA5CYASCefRJkk/img.png?width=500&amp;amp;height=300&amp;amp;face=0_0_500_300');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;F5 NGINX Product Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.nginx.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;2.1.1 Nginx 설치&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ubuntu 환경에서는 다음 명령어로 설치할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;sudo apt install nginx&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치 명령어를 입력하니 아래와 같은 에러가 발생했다. 그런데 &lt;code&gt;nginx -v&lt;/code&gt; 명령어를 입력하면 버전이 나오는 것을 보니 설치는 되었지만 실행 과정에서 실패한 것 같았다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;❌ nginx.service - A high performance web server and a reverse proxy server nginx[240444]: nginx: [emerg] socket() [::]:80 failed (97: Address family not supported by protocol)&amp;hellip;&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색해보니 NCP 서버가 IPv6을 지원하지 않는데 Nginx가 IPv6로 80 포트를 열려고 해서 발생한 오류라고 한다. &lt;code&gt;/etc/nginx/sites-available/default&lt;/code&gt; 파일에서 IPv6 관련 설정을 주석 처리하면 해결된다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;vi /etc/nginx/sites-available/default&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일을 열면 &lt;code&gt;server&lt;/code&gt; 블록 맨 처음에 다음과 같은 설정이 들어있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;server {
	listen 80 default_server;          # IPv4로 80번 포트 열기
	# listen [::]:80 default_server;   # IPv6로 80번 포트 열기 (주석 처리)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;2.1.2 Nginx 실행 및 확인&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 다시 Nginx를 실행해보자~~&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;systemctl start nginx   # Nginx 시작
systemctl enable nginx  # 재부팅 시 자동 시작 설정
systemctl status nginx  # 잘 돌아가는지 확인&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;start&lt;/code&gt; 명령어는 아무것도 출력하지 않기 때문에 &lt;code&gt;status&lt;/code&gt;로 한 번 확인해준다. 아래와 같이 &lt;b&gt;active (running)&lt;/b&gt; 메시지가 뜨면 성공이다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;✅ nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled)
     Active: active (running)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시나 Nginx를 중지하고 싶다면 아래 명령어를 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;systemctl stop nginx     # Nginx 중지
systemctl disable nginx  # 자동 시작 비활성화&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 포트 번호 없이 &lt;code&gt;http://서버IP&lt;/code&gt;로 접속하면 &lt;i&gt;&lt;b&gt;Welcome to nginx&lt;/b&gt;&lt;/i&gt; 화면이 뜬다! 80번 포트는 HTTP의 기본 포트라서 생략해도 자동으로 80번 포트로 접속된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-24 오후 3.28.37.png&quot; data-origin-width=&quot;1064&quot; data-origin-height=&quot;424&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJ3iJq/dJMcai3e4b8/gHchi0hUJ2QxT4pT5UH3wK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJ3iJq/dJMcai3e4b8/gHchi0hUJ2QxT4pT5UH3wK/img.png&quot; data-alt=&quot;Nginx 접속 성공&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJ3iJq/dJMcai3e4b8/gHchi0hUJ2QxT4pT5UH3wK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJ3iJq%2FdJMcai3e4b8%2FgHchi0hUJ2QxT4pT5UH3wK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;159&quot; data-filename=&quot;스크린샷 2025-10-24 오후 3.28.37.png&quot; data-origin-width=&quot;1064&quot; data-origin-height=&quot;424&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Nginx 접속 성공&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.2 리버스 프록시 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;리버스 프록시&lt;/b&gt;는 클라이언트의 요청을 받아서 백엔드 서버로 전달하고 그 응답을 다시 클라이언트에게 돌려주는 &lt;b&gt;중간 서버&lt;/b&gt;이다. Nginx가 80번 포트에서 요청을 받아서 3000번 포트의 Next.js로 전달해주는 것이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;클라이언트 &amp;rarr; &lt;code&gt;서버IP:80&lt;/code&gt; (Nginx) &amp;rarr; &lt;code&gt;localhost:3000&lt;/code&gt; (Next.js)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;2.2.1 불필요한 설정 제거&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nginx 설정 파일 &lt;code&gt;/etc/nginx/sites-available/default&lt;/code&gt;을 열어서 일부 설정을 삭제해야 한다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot; data-ke-language=&quot;nginx&quot;&gt;&lt;code&gt;server {
	listen 80 default_server;
	# listen [::]:80 default_server;
        
	root /var/www/html; # 삭제 (1)
	server_name _;
		
	index index.html index.htm index.nginx-debian.html; # 삭제 (2)
		
	location / {
		try_files $uri $uri/ =404; # 삭제 (3)
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 설정들은 Nginx를 웹 서버로 사용할 때 필요한 것들이다. 여기에서는 리버스 프록시로 사용하기 때문에 불필요하다. (3번은 반드시 삭제해야 한다.) Nginx 문서의 리버스 프록시 예시에도 이런 설정들은 포함되어 있지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;2.2.2 기본 프록시 설정&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;location /&lt;/code&gt; 블록 안에 다음 한 줄만 추가해도 일단 동작은 한다. 문서에는 &lt;code&gt;localhost/&lt;/code&gt; 대신에 &lt;code&gt;127.0.0.1&lt;/code&gt;로 써있지만 같은 의미이다.&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot; data-ke-language=&quot;nginx&quot;&gt;&lt;code&gt;location / {
    proxy_pass http://localhost:3000;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Next.js가 제대로 동작하려면 추가 설정이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;2.2.3 헤더 설정&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nginx를 거쳐서 Next.js로 요청이 전달되면 원래 요청 정보가 일부 사라진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Host 헤더&lt;/b&gt;가 없으면 Next.js는 &lt;code&gt;localhost:3000&lt;/code&gt;으로 요청이 들어온 줄 알고, 리다이렉트할 때 &lt;code&gt;localhost:3000&lt;/code&gt;으로 보내버린다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;X-Real-IP 헤더&lt;/b&gt;가 없으면 모든 사용자 IP가 &lt;code&gt;127.0.0.1&lt;/code&gt;로 보인다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 문제를 방지하기 위해 헤더를 설정해주어야 한다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;# 원래 요청한 도메인 정보 전달
proxy_set_header Host $host;

# 실제 클라이언트의 IP 주소 전달
proxy_set_header X-Real-IP $remote_addr;

# HTTP인지 HTTPS인지 정보 전달 (HTTPS 적용 시 필요)
proxy_set_header X-Forwarded-Proto $scheme;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;2.2.4 실시간 기능 지원&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;채팅 같은 실시간 기능을 쓰려면 WebSocket 설정이 필요하다. 디테일한 정보는 아래 링크에서 찾아볼 수 있다.&lt;/p&gt;
&lt;figure id=&quot;og_1770627779117&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;WebSocket proxying&quot; data-og-description=&quot;WebSocket proxying To turn a connection between a client and server from HTTP/1.1 into WebSocket, the protocol switch mechanism available in HTTP/1.1 is used. There is one subtlety however: since the &amp;ldquo;Upgrade&amp;rdquo; is a hop-by-hop header, it is not passed f&quot; data-og-host=&quot;nginx.org&quot; data-og-source-url=&quot;https://nginx.org/en/docs/http/websocket.html&quot; data-og-url=&quot;https://nginx.org/en/docs/http/websocket.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://nginx.org/en/docs/http/websocket.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nginx.org/en/docs/http/websocket.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;WebSocket proxying&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;WebSocket proxying To turn a connection between a client and server from HTTP/1.1 into WebSocket, the protocol switch mechanism available in HTTP/1.1 is used. There is one subtlety however: since the &amp;ldquo;Upgrade&amp;rdquo; is a hop-by-hop header, it is not passed f&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;nginx.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 프로젝트에서는 채팅, 알림 기능이 있기 때문에 이 설정들을 추가해주었다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# HTTP 버전 지정 (WebSocket 지원을 위해 1.1 필요)
proxy_http_version 1.1;

# WebSocket 연결을 위한 헤더
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';

# 버퍼링 끄기 (실시간 스트리밍을 위해)
proxy_buffering off;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nginx는 기본적으로 &lt;code&gt;proxy_buffering&lt;/code&gt; 설정이 &lt;code&gt;on&lt;/code&gt;으로 되어있기 때문에 응답을 모아뒀다가 한 번에 보내는데, SSE와 같은 실시간 기능에서는 데이터를 바로바로 보내야 하므로 &lt;code&gt;off&lt;/code&gt;로 설정한다. (WebSocket은 버퍼링 영향을 받지 않는다.)&lt;/p&gt;
&lt;figure id=&quot;og_1770627825874&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Module ngx_http_proxy_module&quot; data-og-description=&quot;Module ngx_http_proxy_module The ngx_http_proxy_module module allows passing requests to another server. Example Configuration location / { proxy_pass http://localhost:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } Directives&quot; data-og-host=&quot;nginx.org&quot; data-og-source-url=&quot;https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffering&quot; data-og-url=&quot;https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffering&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffering&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffering&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Module ngx_http_proxy_module&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Module ngx_http_proxy_module The ngx_http_proxy_module module allows passing requests to another server. Example Configuration location / { proxy_pass http://localhost:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } Directives&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;nginx.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;2.2.5 완성된 설정&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지의 설정을 합치면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot; data-ke-language=&quot;nginx&quot;&gt;&lt;code&gt;server {
    listen 80 default_server;
    
    server_name _;

    location / {
        proxy_pass http://localhost:3000;

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_buffering off;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정을 저장했으면 문법 검사를 한 후 Nginx를 재시작한다.&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;sudo nginx -t  # 문법 검사

# nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
# nginx: configuration file /etc/nginx/nginx.conf test is successful

systemctl reload nginx  # Nginx 재시작
systemctl status nginx  # 상태 확인
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;code&gt;http://서버IP&lt;/code&gt;로 접속하면 Next.js 애플리케이션이 뜬다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;2.2.6 포트 환경 분리 (prod/dev)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 하나의 서버 블록만 있어서 하나의 환경만 서빙할 수 있다. 3000번 포트에 prod, 3001번 포트에 dev를 띄워놨기 때문에 이 둘을 각각 다른 포트로 접근할 수 있게 해주어야 한다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot; data-ke-language=&quot;nginx&quot;&gt;&lt;code&gt;# prod 서버 (80번 포트)
server {
    listen 80 default_server;
    server_name _;

    location / {
        proxy_pass http://localhost:3000;

        # 나머지 프록시 설정 ...
    }
}

# dev 서버 (8080번 포트)
server {
    listen 8080;
    server_name _;

    location / {
        proxy_pass http://localhost:3001;

        # 나머지 프록시 설정 ...
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ACG 설정에서 8080번 포트를 열어주면 &lt;code&gt;http://서버IP&lt;/code&gt;는 prod, &lt;code&gt;http://서버IP:8080&lt;/code&gt;은 dev로 접속할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.3 Basic Auth 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 상태에서는 dev 환경에 누구나 접속할 수 있다. 방화벽으로 IP를 제한할 수도 있지만 팀원들이 외부에서 접속해야 한다면 불편할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Basic Auth&lt;/b&gt;를 사용하면 아이디와 비밀번호로 접근을 제한할 수 있다.&lt;/p&gt;
&lt;figure id=&quot;og_1770627939321&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Restricting Access with HTTP Basic Authentication | NGINX Documentation&quot; data-og-description=&quot;Restricting Access with HTTP Basic Authentication You can restrict access to your website or some parts of it by implementing a username/password authentication. Usernames and passwords are taken from a file created and populated by a password file creatio&quot; data-og-host=&quot;docs.nginx.com&quot; data-og-source-url=&quot;https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/&quot; data-og-url=&quot;https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bcn3YP/dJMb8Z3m1O3/sPCJ7SCNBXXZPddbKs4nRk/img.png?width=500&amp;amp;height=300&amp;amp;face=0_0_500_300&quot;&gt;&lt;a href=&quot;https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bcn3YP/dJMb8Z3m1O3/sPCJ7SCNBXXZPddbKs4nRk/img.png?width=500&amp;amp;height=300&amp;amp;face=0_0_500_300');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Restricting Access with HTTP Basic Authentication | NGINX Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Restricting Access with HTTP Basic Authentication You can restrict access to your website or some parts of it by implementing a username/password authentication. Usernames and passwords are taken from a file created and populated by a password file creatio&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.nginx.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;2.3.1 비밀번호 파일 생성&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ubuntu에서 비밀번호 파일을 생성하려면 &lt;code&gt;apache2-utils&lt;/code&gt; 패키지가 필요하다.&lt;/p&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;sudo apt install apache2-utils -y
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;htpasswd&lt;/code&gt; 명령어로 비밀번호 파일을 생성한다. &lt;code&gt;-c&lt;/code&gt; 플래그는 새 파일을 생성한다는 의미이고, 첫 번째 인자는 파일 경로, 두 번째 인자는 사용자 이름이다.&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;sudo htpasswd -c /etc/apache2/.htpasswd admin
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비밀번호를 입력하라는 프롬프트가 뜨면 원하는 비밀번호를 입력한다. 그리고 &lt;code&gt;cat /etc/apache2/.htpasswd&lt;/code&gt; 명령어를 통해 파일이 잘 생성되었는지 보면 다음과 같이 파일에 사용자 이름과 해시된 비밀번호가 포함되어 있는 것을 확인할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;arcade&quot;&gt;&lt;code&gt;admin:$apr1$...
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;2.3.2 Nginx에 Basic Auth 적용&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이제 Nginx 설정 파일에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Basic Auth를 적용한다.&amp;nbsp;보호하고 싶은 &lt;code&gt;location&lt;/code&gt; 블록 안에 다음 두 줄을 추가하면 된다.&lt;/p&gt;
&lt;pre class=&quot;abnf&quot; data-ke-language=&quot;nginx&quot;&gt;&lt;code&gt;auth_basic &quot;Administrator's Area&quot;;
auth_basic_user_file /etc/apache2/.htpasswd;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;auth_basic&lt;/code&gt;: HTTP Basic Authentication을 활성화한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;auth_basic_user_file&lt;/code&gt;: 비밀번호가 있는 파일 경로를 지정한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 서버를 보호하고 싶다면 &lt;code&gt;location /&lt;/code&gt; 블록에 추가하면 되고, 특정 경로만 보호하고 싶다면 &lt;code&gt;location /api&lt;/code&gt; 같은 블록에 추가하면 된다. 나는 dev 환경만 보호하고 싶어서 dev 서버 블록의 &lt;code&gt;location /&lt;/code&gt;에만 추가했다. 전체 파일은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot; data-ke-language=&quot;nginx&quot;&gt;&lt;code&gt;# prod 서버 (80번 포트) - Basic Auth 없음
server {
    listen 80 default_server;
    server_name _;

    location / {
        proxy_pass http://localhost:3000;
        
        # ... 나머지 proxy 설정
    }
}

# dev 서버 (8080번 포트) - Basic Auth 적용
server {
    listen 8080;
    server_name _;

    location / {
        auth_basic &quot;Development Server&quot;;
        auth_basic_user_file /etc/apache2/.htpasswd;

        proxy_pass http://localhost:3001;
        
        # ... 나머지 proxy 설정
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 &lt;code&gt;auth_basic_user_file&lt;/code&gt; 경로를 잘못 입력하면 403 Forbidden 에러가 발생한다..&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-24 오후 3.30.52.png&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;208&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZtScZ/dJMb99L2deb/IcOBshHALHkIdvcQMqI0p0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZtScZ/dJMb99L2deb/IcOBshHALHkIdvcQMqI0p0/img.png&quot; data-alt=&quot;403 Forbidden 에러 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZtScZ/dJMb99L2deb/IcOBshHALHkIdvcQMqI0p0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZtScZ%2FdJMb99L2deb%2FIcOBshHALHkIdvcQMqI0p0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;200&quot; height=&quot;87&quot; data-filename=&quot;스크린샷 2025-10-24 오후 3.30.52.png&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;208&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;403 Forbidden 에러 화면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정을 저장했으면 문법 검사 후 Nginx를 재시작한다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;sudo nginx -t

systemctl reload nginx
systemctl status nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;code&gt;http://서버IP:8080&lt;/code&gt;으로 접속하면 로그인 팝업이 뜬다. 아까 설정한 사용자 이름과 비밀번호를 입력해야 접속할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;652&quot; data-origin-height=&quot;554&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bthn8A/dJMcahi23sw/7rgwhGjmJkIgHdfPoPatx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bthn8A/dJMcahi23sw/7rgwhGjmJkIgHdfPoPatx0/img.png&quot; data-alt=&quot;Basic Auth가 적용된 서버 접속 시 팝업&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bthn8A/dJMcahi23sw/7rgwhGjmJkIgHdfPoPatx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbthn8A%2FdJMcahi23sw%2F7rgwhGjmJkIgHdfPoPatx0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;255&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;652&quot; data-origin-height=&quot;554&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Basic Auth가 적용된 서버 접속 시 팝업&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 배포 자동화 (CD 구축)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 코드를 수정할 때마다 SSH로 서버에 접속해서 &lt;code&gt;git pull&lt;/code&gt;, &lt;code&gt;npm install&lt;/code&gt;, &lt;code&gt;npm run build&lt;/code&gt;, &lt;code&gt;pm2 restart&lt;/code&gt; 명령어를 직접 실행해야 한다. 이 과정을 &lt;b&gt;GitHub Actions&lt;/b&gt;로 자동화할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.1 SSH 키 기반 인증 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;appleboy/ssh-action&lt;/code&gt;이라는 액션을 사용하면 GitHub Actions에서 SSH로 서버에 접속해서 명령어를 실행할 수 있다.&lt;/p&gt;
&lt;figure id=&quot;og_1770628419134&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - appleboy/ssh-action: GitHub Actions for executing remote ssh commands.&quot; data-og-description=&quot;GitHub Actions for executing remote ssh commands. Contribute to appleboy/ssh-action development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/appleboy/ssh-action&quot; data-og-url=&quot;https://github.com/appleboy/ssh-action&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/crJ15Z/dJMb8VNq6ft/349Ocj3pLtRlTavZymLIXk/img.png?width=1200&amp;amp;height=600&amp;amp;face=988_112_1033_160,https://scrap.kakaocdn.net/dn/bmyNmm/dJMb8SXtzMk/IUGweDVTCu9kOumUshYr01/img.png?width=1200&amp;amp;height=600&amp;amp;face=988_112_1033_160&quot;&gt;&lt;a href=&quot;https://github.com/appleboy/ssh-action&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/appleboy/ssh-action&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/crJ15Z/dJMb8VNq6ft/349Ocj3pLtRlTavZymLIXk/img.png?width=1200&amp;amp;height=600&amp;amp;face=988_112_1033_160,https://scrap.kakaocdn.net/dn/bmyNmm/dJMb8SXtzMk/IUGweDVTCu9kOumUshYr01/img.png?width=1200&amp;amp;height=600&amp;amp;face=988_112_1033_160');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - appleboy/ssh-action: GitHub Actions for executing remote ssh commands.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;GitHub Actions for executing remote ssh commands. Contribute to appleboy/ssh-action development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터미널에서 직접 SSH로 서버에 접속할 때는 비밀번호를 사용했었는데, SSH 공식 문서에 따르면 &lt;b&gt;공개키 인증&lt;/b&gt;이 더 안전하기 때문에 권장한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;The motivation for using public key authentication over simple passwords is security.&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;3.1.1 공개키 추출&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 명령어를 통해 &lt;code&gt;.pem&lt;/code&gt; 파일의 내용을 확인해보면 &lt;code&gt;-----BEGIN RSA PRIVATE KEY-----&lt;/code&gt;로 시작하는 &lt;b&gt;RSA 개인키&lt;/b&gt;임을 알 수 있다.&lt;/p&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;cat my-key.pem
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 터미널에서 아래 명령어로 개인키에서 공개키를 추출해서 확인할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;vbnet&quot;&gt;&lt;code&gt;# 공개키 추출
ssh-keygen -y -f ~/Documents/Key/my-key.pem &amp;gt; my-key.pub

# 공개키 확인
cat my-key.pub
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;ssh-rsa&lt;/code&gt;로 시작하는 공개키가 출력된다.&lt;/p&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;ssh-rsa
AAAAB3N...
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;3.1.2 서버에 공개키 등록&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 공개키를 서버에 등록하면 비밀번호 없이 접속할 수 있다. SSH로 서버에 접속 후 아래와 같이 공개키를 등록한다.&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;mkdir ~/.ssh              # SSH 디렉토리 없다면 새로 생성
vi ~/.ssh/authorized_keys # 편집기 열기
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;편집기가 열리면 아까 추출한 공개키 내용을 붙여넣고 저장한다. 그리고 이 키 파일에 대한 권한을 소유자만 읽고 쓰도록 수정해주어야 한다. 파일의 권한이 너무 열려있으면 SSH가 접속을 거부할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;chmod 600 ~/.ssh/authorized_keys
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 접속 종료 후 다시 접속해보면 비밀번호를 요구하지 않는다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.2 GitHub Actions 워크플로우 작성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;3.2.1 GitHub Secrets 설정&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 SSH 개인키와 서버 IP를 GitHub에 저장해야 한다. GitHub 레포지토리에서 [Settings] -[Secrets and variables] - [Actions] - [New repository secret]에서 다음 두 개의 Secret을 등록한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;SERVER_HOST&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Name: &lt;code&gt;SERVER_HOST&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Secret: &lt;code&gt;서버IP&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SSH_PRIVATE_KEY&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Name: &lt;code&gt;SSH_PRIVATE_KEY&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Secret: &lt;code&gt;.pem&lt;/code&gt; 파일의 전체 내용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 터미널에서 아래 명령어로 &lt;code&gt;.pem&lt;/code&gt; 파일 내용을 복사할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;sas&quot;&gt;&lt;code&gt;cat ~/Documents/Key/my-key.pem
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반드시 &lt;code&gt;-----BEGIN RSA PRIVATE KEY-----&lt;/code&gt;부터 &lt;code&gt;-----END RSA PRIVATE KEY-----&lt;/code&gt;까지 전체를 포함해서 복사해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;3.2.2 dev 환경 배포 워크플로우&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;# .github/workflows/deploy-dev.yml

name: Deploy to Dev Server

on:
  push:
    branches:
      - develop

jobs:
  deploy:
    name: Deploy to NCP Server
    runs-on: ubuntu-latest                    # 가상 머신 - GitHub의 Ubuntu 서버

    steps:
      - name: Deploy to Server
        uses: appleboy/ssh-action@v1.0.0      # SSH로 원격 서버에 접속하는 action?
        with:                                 # ssh-action에 전달할 설정들
          host: ${{ secrets.SERVER_HOST }}    # 접속할 서버 IP 주소
          username: root                      # SSH 접속 사용자명
          key: ${{ secrets.SSH_PRIVATE_KEY }} # GitHub Secrets에 저장한 SSH 개인키
          script: |
            echo &quot;배포 시작&quot;
            cd /root/project-dev
            
            echo &quot;최신 코드 가져오기&quot;
            git pull origin develop
            
            echo &quot;의존성 설치&quot;
            npm ci                       # package-lock.json 기반으로 의존성 설치
            
            echo &quot;프로젝트 빌드&quot;
            npm run build
            
            echo &quot;서버 재시작&quot;
            pm2 restart dev --update-env # dev 프로세스를 재시작하면서 환경변수 업데이트
            pm2 save                     # 현재 PM2 상태 저장 (서버 재부팅 시에도 복구)
            
            echo &quot;배포 완료&quot;
            pm2 info dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;3.2.3 prod 환경 배포 워크플로우&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dev와 거의 동일하며 바꾸어야 하는 부분은 워크플로우 이름, 브랜치, 프로젝트 폴더 경로, pm2 이름 부분이다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;# .github/workflows/deploy-prod.yml

name: Deploy to Prod Server

on:
  push:
    branches:
      - main

jobs:
  deploy:
	  name: Deploy to NCP Server
    runs-on: ubuntu-latest
    
    steps:
      - name: Deploy to Server
        uses: appleboy/ssh-action@v1.0.0
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: root
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            echo &quot;배포 시작&quot;
            cd /root/project-prod
            
            echo &quot;최신 코드 가져오기&quot;
            git pull origin main
            
            echo &quot;의존성 설치&quot;
            npm ci
            
            echo &quot;프로젝트 빌드&quot;
            npm run build
            
            echo &quot;서버 재시작&quot;
            pm2 restart prod --update-env
            pm2 save
            
            echo &quot;배포 완료&quot;
            pm2 info prod
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;3.2.4 배포 확인&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;code&gt;develop&lt;/code&gt; 브랜치에 코드를 push하면 자동으로 dev 서버에 배포되고, &lt;code&gt;main&lt;/code&gt; 브랜치에 push하면 prod 서버에 배포된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GitHub 레포지토리의 Actions 탭에서 배포 진행 상황과 로그를 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.3 PR 빌드 테스트 자동화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포 서버에 문제가 생기면 안 되기 때문에 &lt;code&gt;main&lt;/code&gt; 브랜치로 PR을 올릴 때 자동으로 &lt;b&gt;빌드 검사&lt;/b&gt;를 해주면 좋다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;# .github/workflows/build-test.yml

name: Build Test

on:
  pull_request:
    branches: [ main ]              # PR이 생성되거나 업데이트될 때 실행

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4   # GitHub 저장소의 코드를 가져옴

      - name: Setup Node.js
        uses: actions/setup-node@v4 # Node.js 환경 설정
        with:
          node-version: 20

      - name: Install dependencies
        run: npm ci

      - name: Run lint
        run: npm run lint           # 린트 검사

      - name: Build Next.js
        run: npm run build          # 빌드 검사
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 도메인 연결&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지는 IP 주소로 접속했지만 실제 서비스에서는 &lt;b&gt;도메인&lt;/b&gt;을 사용하기 때문에 도메인을 연결하려고 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.1 DNS 설정 (가비아)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;4.1.1 도메인 등록&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도메인 구매 후 [DNS 정보] - [설정]을 클릭하여 DNS 관리 페이지로 이동하고, 여기서 해당 도메인의 [DNS 설정]으로 들어간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DNS 레코드 타입 중 A 레코드는 도메인을 IP 주소로 연결하는 역할을 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2680&quot; data-origin-height=&quot;540&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/R3fab/dJMcaaxrqdj/pQBZJ88navSZjWnPqT8kY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/R3fab/dJMcaaxrqdj/pQBZJ88navSZjWnPqT8kY1/img.png&quot; data-alt=&quot;가비아 DNS 설정 페이지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/R3fab/dJMcaaxrqdj/pQBZJ88navSZjWnPqT8kY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FR3fab%2FdJMcaaxrqdj%2FpQBZJ88navSZjWnPqT8kY1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;141&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2680&quot; data-origin-height=&quot;540&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;가비아 DNS 설정 페이지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호스트에 &lt;code&gt;@&lt;/code&gt;를 입력하면 루트 도메인(&lt;code&gt;project.com&lt;/code&gt;)으로 접속할 수 있고, &lt;code&gt;www&lt;/code&gt;를 입력하면 &lt;code&gt;www.project.com&lt;/code&gt;으로 접속할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 서버를 위한 도메인 포함 총 4가지 레코드를 추가해주었다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;&lt;b&gt;도메인&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;&lt;b&gt;타입&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;&lt;b&gt;호스트&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;&lt;b&gt;값/위치&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;project.com&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;A&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;&lt;code&gt;@&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;프론트엔드 서버 IP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;www.project.com&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;A&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;&lt;code&gt;www&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;프론트엔드 서버 IP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;dev.project.com&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;A&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;&lt;code&gt;dev&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;프론트엔드 서버 IP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;api.project.com&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;A&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;&lt;code&gt;api&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;백엔드 서버 IP&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;4.1.2 DNS 설정 확인&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DNS 설정이 제대로 되었는지 확인하려면 터미널에서 다음 명령어를 입력한다.&lt;/p&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;nslookup project.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 결과가 나오면 성공이다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;Server:     ***.***.**.* 
Address:    ***.***.**.***#53

Non-authoritative answer:
Name:   project.com
Address: XX.XX.XXX.XXX&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 &lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;&lt;b&gt;server can't find project.com: NXDOMAIN &lt;/b&gt;&lt;/i&gt;&lt;/span&gt;과 같은 에러가 나온다면 DNS 설정이 아직 전파되지 않은 것이다. 최대 24시간까지 걸릴 수 있다고 되어있었는데 10분도 걸리지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.2 Nginx에서 도메인 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DNS 설정이 완료되었으면 Nginx 설정에 도메인을 적용해야 한다. 다시 Nginx 설정 파일을 수정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;4.2.1 server_name 변경&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 &lt;code&gt;server_name _;&lt;/code&gt;로 설정되어 있었는데, 이것은 어떤 도메인으로 들어와도 받는다는 의미이다. 이제 설정한 도메인만 받도록 변경해준다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot; data-ke-language=&quot;nginx&quot;&gt;&lt;code&gt;# prod 서버
server {
    listen 80 default_server;
    server_name project.com www.project.com;
    
    # ... 나머지 설정
}

# dev 서버
server {
    listen 8080;
    server_name dev.project.com;
    
    # ... 나머지 설정
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;4.2.2 포트 변경&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nginx 설정에서 도메인까지 나눠주고 &lt;code&gt;dev.project.com&lt;/code&gt;로 접속하면? prod 서버가 뜬다  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DNS는 포트를 지원하지 않기 때문이다.&lt;/b&gt; A 레코드에는 IP 주소만 들어가기 때문에 &lt;code&gt;project.com&lt;/code&gt;이든 &lt;code&gt;dev.project.com&lt;/code&gt;이든 모두 같은 IP의 80번 포트로 요청이 들어온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 &lt;code&gt;dev.project.com:8080&lt;/code&gt;으로 접속하면 dev 서버에 들어갈 수 있지만 이제는 &lt;b&gt;도메인으로 구분&lt;/b&gt;할 수 있기 때문에 둘 다 80번 포트를 사용하면 된다. 이전에 80과 8080으로 나눈 건 도메인이 없어서 포트로 구분하기 위함이었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;project.com&lt;/code&gt; &amp;rarr; prod 서버 (&lt;code&gt;localhost:3000&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dev.project.com&lt;/code&gt; &amp;rarr; dev 서버 (&lt;code&gt;localhost:3001&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;routeros&quot; data-ke-language=&quot;nginx&quot;&gt;&lt;code&gt;# prod 서버
server {
    listen 80 default_server;
    
    # ... 나머지 설정
}

# dev 서버
server {
    listen 80;
    
    # ... 나머지 설정
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;4.2.3 도메인 접속 확인&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완성된 설정 파일은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot; data-ke-language=&quot;nginx&quot;&gt;&lt;code&gt;# prod 서버
server {
    listen 80 default_server;
    server_name project.com www.project.com;

    location / {
        proxy_pass http://localhost:3000;

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_buffering off;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

# dev 서버
server {
    listen 80;
    server_name dev.project.com;

    location / {
        auth_basic &quot;Development Server&quot;;
        auth_basic_user_file /etc/apache2/.htpasswd;

        proxy_pass http://localhost:3001;

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_buffering off;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정을 저장하고 Nginx를 재시작한다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;sudo nginx -t
systemctl reload nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 도메인으로 접속해보면 Next.js 애플리케이션이 잘 보인다. &lt;code&gt;server_name&lt;/code&gt;에 도메인만 지정했기 때문에 이제부터는 IP 주소로 접속할 수 없다. &lt;code&gt;http://서버IP&lt;/code&gt;로 접속하면 연결이 되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 계속 로딩만 걸리거나 연결 실패 에러가 떴는데 아래 방법으로 해결했다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;시크릿 모드에서 접속해보기&lt;/li&gt;
&lt;li&gt;브라우저가 자동으로 &lt;code&gt;https://&lt;/code&gt;로 바꾸는지 확인하고 &lt;code&gt;http://&lt;/code&gt;로 직접 입력해보기&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. HTTPS 적용&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 HTTP만 지원하기 때문에 &amp;lsquo;안전하지 않음&amp;rsquo; 경고가 뜬다. &lt;b&gt;HTTPS&lt;/b&gt;를 적용하면 데이터가 암호화되어 전송된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5.1 Certbot 설치 및 SSL 인증서 발급&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;5.1.1 Snap 설치&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Certbot&lt;/b&gt;은 &lt;b&gt;Let's Encrypt&lt;/b&gt;에서 무료 &lt;b&gt;SSL/TLS 인증서&lt;/b&gt;를 자동으로 발급받고 갱신할 수 있게 해주는 도구이다.&lt;/p&gt;
&lt;figure id=&quot;og_1770632910630&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Certbot Instructions&quot; data-og-description=&quot;Certbot Instructions&quot; data-og-host=&quot;certbot.eff.org&quot; data-og-source-url=&quot;https://certbot.eff.org/instructions?ws=nginx&amp;amp;os=snap&quot; data-og-url=&quot;https://certbot.eff.org/instructions?os=snap&amp;amp;ws=nginx&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bFgihD/dJMb9cBDTq4/U0DISHn8Y5XkzUDBaXUVh1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://certbot.eff.org/instructions?ws=nginx&amp;amp;os=snap&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://certbot.eff.org/instructions?ws=nginx&amp;amp;os=snap&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bFgihD/dJMb9cBDTq4/U0DISHn8Y5XkzUDBaXUVh1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Certbot Instructions&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Certbot Instructions&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;certbot.eff.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서에서는 snap으로 설치하는 것을 권장한다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;sudo apt update
sudo apt install snapd
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;5.1.2 Certbot 설치&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 &lt;code&gt;apt&lt;/code&gt;로 설치된 Certbot이 있다면 제거하고, snap으로 새로 설치한다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;# 기존에 설치된 certbot 패키지 제거
sudo apt remove certbot

# Certbot 설치
sudo snap install --classic certbot

# certbot 명령 준비 (링크 생성)
sudo ln -s /snap/bin/certbot /usr/bin/certbot
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막 명령어는 &lt;code&gt;/snap/bin/certbot&lt;/code&gt;을 &lt;code&gt;/usr/bin/certbot&lt;/code&gt;에 링크하는 것이다. Certbot은 &lt;code&gt;/snap/bin/&lt;/code&gt;에 설치되는데, 보통 명령어는 &lt;code&gt;/usr/bin/&lt;/code&gt;에서 찾기 때문에 바로가기를 만드는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;5.1.3 SSL 인증서 발급&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Certbot으로 인증서를 받는 방법은 두 가지가 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;인증서를 받아서 자동으로 Nginx 설정 편집&lt;/li&gt;
&lt;li&gt;인증서만 받고 수동으로 설정&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 중 자동으로 설정을 변경하는 방식을 선택했다.&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;sudo certbot --nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이메일을 입력하면 Certbot이 Nginx 설정에서 감지한 도메인들이 표시된다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Which names would you like to activate HTTPS for?
We recommend selecting either all domains, or all domains in a VirtualHost/server block.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: project.com
2: www.project.com
3: dev.project.com
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTPS를 적용할 도메인을 선택한다. 전부 선택하려면 그냥 엔터를 치면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증서 발급이 완료되면 Certbot이 자동으로 Nginx 설정을 수정한다. 기존의 80번 포트 설정은 443번 포트로 바뀌고, 80번 포트로 접속 시 자동으로 HTTPS로 리다이렉트되도록 설정이 추가된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5.2 443 포트 열기 및 HTTPS 확인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 설정해도 HTTP로 접속되어서 이유를 찾느라 오래 걸렸는데 생각해보니 &lt;b&gt;서버 ACG에 443 포트도 추가&lt;/b&gt;해주어야 한다. NCP 콘솔에서 ACG 설정에 들어가서 인바운드 규칙을 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;code&gt;http://project.com&lt;/code&gt;으로 접속해도 자동으로 &lt;code&gt;https://project.com&lt;/code&gt;으로 리다이렉트된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. 마무리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 나름 서비스 구색을 갖춘 프로젝트가 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예상보다 시간이 꽤 걸렸지만&amp;hellip; 직접 찾아보고 하나씩 적용해보면서 정말 많은 걸 배웠다. 처음 배포를 해보는 거라 완벽하게 했다고 할 수는 없지만 서버 설정, Nginx 설정, 도메인 연결, HTTPS 적용 등 전체적인 배포 흐름은 경험해볼 수 있었고, 완성된 서버가 제대로 돌아가는 것을 보니 뿌듯한 기분이었다!!  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음에는 각 설정이 정확히 어떻게 동작하는지 더 잘 이해하고 배포해보고 싶다.&lt;/p&gt;</description>
      <category>DevOps</category>
      <category>NCP</category>
      <category>next.js</category>
      <category>pm2</category>
      <category>네이버 클라우드</category>
      <category>배포</category>
      <author>카페모카 소믈리에</author>
      <guid isPermaLink="true">https://sohxxny.tistory.com/6</guid>
      <comments>https://sohxxny.tistory.com/6#entry6comment</comments>
      <pubDate>Mon, 9 Feb 2026 20:16:53 +0900</pubDate>
    </item>
    <item>
      <title>NCP 서버에 Next.js 배포하기 (1) - 서버 생성 및 실행</title>
      <link>https://sohxxny.tistory.com/5</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 들어가며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vercel 같은 플랫폼이 아닌 일반 서버에 Next.js를 배포하는 것은 처음이라 공식 문서와 AI의 도움을 많이 받았다. 배포하면서 내가 네트워크에 대한 지식이 많이 부족하다는 것을 새삼 깨달았다&amp;hellip; 시행착오를 겪으며 배운 내용들을 기록 차원에서 정리해보려 한다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.1 NCP를 선택한 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트를 진행하면서 &lt;b&gt;네이버 클라우드 플랫폼(NCP)&lt;/b&gt;의 무료 크레딧을 받게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 Next.js 배포라면 Vercel이 가장 간편한 선택지다. 하지만 우리 프로젝트는 GitHub Organization 레포지토리를 사용하고 있었고, Vercel에서 Organization 레포를 무료로 배포하려면 개인 계정으로 fork해야 하는 번거로움이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마침 받은 무료 크레딧도 있고, 실제 서버에 직접 배포해보는 경험도 쌓을 겸 NCP를 선택했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.2 Object Storage vs Compute Server&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NCP에는 Object Storage라는 정적 호스팅 서비스도 있다. 하지만 Next.js는 단순 정적 사이트가 아니라 &lt;b&gt;Node.js 런타임이 필요한 프레임워크&lt;/b&gt;이다. Object Storage에 배포하면 다음과 같은 기능들이 제대로 작동하지 않는다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SSR (Server-Side Rendering)&lt;/li&gt;
&lt;li&gt;API Routes&lt;/li&gt;
&lt;li&gt;ISR (Incremental Static Regeneration)&lt;/li&gt;
&lt;li&gt;Middleware&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Next.js의 모든 기능을 사용하려면 Node.js를 실행할 수 있는 서버 환경이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 서버 생성하기&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.1 서버 이미지 선택&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 이미지는 가상 서버를 만들 때 사용할 &lt;b&gt;운영체제&lt;/b&gt;와 기본 설정이 포함된 템플릿이다. 이 이미지를 기반으로 새로운 가상 서버 인스턴스가 만들어진다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2064&quot; data-origin-height=&quot;968&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/O2ECg/dJMcacBX7Qo/pKKFYz7v1U8KkEK3kbvL20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/O2ECg/dJMcacBX7Qo/pKKFYz7v1U8KkEK3kbvL20/img.png&quot; data-alt=&quot;NCP 서버 이미지 선택&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/O2ECg/dJMcacBX7Qo/pKKFYz7v1U8KkEK3kbvL20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FO2ECg%2FdJMcacBX7Qo%2FpKKFYz7v1U8KkEK3kbvL20%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;734&quot; height=&quot;344&quot; data-origin-width=&quot;2064&quot; data-origin-height=&quot;968&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;NCP 서버 이미지 선택&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NCP에서는 다양한 이미지를 제공한다. 여기에서 순수한 OS만 설치된 &lt;b&gt;Ubuntu 24.04&lt;/b&gt;를 선택했다. Ubuntu를 선택한 이유는 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Node.js, npm, PM2, Nginx, Git 등 주요 도구들이 Ubuntu에 최적화되어 있다.&lt;/li&gt;
&lt;li&gt;많은 배포 가이드와 CI/CD 도구들이 Ubuntu를 기본으로 지원한다. (GitHub Actions 등)&lt;/li&gt;
&lt;li&gt;가볍고 안정적이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.2 서버 설정 (VPC, Subnet, 스펙)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 설정에서는 VPC, Subnet, 서버 스펙, 요금제, 네트워크 인터페이스, 공인 IP 등을 설정한다. 아래 문서에서 가이드를 확인할 수 있다.&lt;/p&gt;
&lt;figure id=&quot;og_1770013419849&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;신규 콘솔 화면으로 Server 생성&quot; data-og-description=&quot; &quot; data-og-host=&quot;guide.ncloud-docs.com&quot; data-og-source-url=&quot;https://guide.ncloud-docs.com/docs/server-create-vpc#2-%EC%84%9C%EB%B2%84-%EC%84%A4%EC%A0%95&quot; data-og-url=&quot;https://guide.ncloud-docs.com/docs/server-create-vpc#2-%EC%84%9C%EB%B2%84-%EC%84%A4%EC%A0%95&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://guide.ncloud-docs.com/docs/server-create-vpc#2-%EC%84%9C%EB%B2%84-%EC%84%A4%EC%A0%95&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://guide.ncloud-docs.com/docs/server-create-vpc#2-%EC%84%9C%EB%B2%84-%EC%84%A4%EC%A0%95&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;신규 콘솔 화면으로 Server 생성&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;guide.ncloud-docs.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2064&quot; data-origin-height=&quot;1382&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kLUZ2/dJMcaa5dmTA/PFGijpONxwUjIXRR7xtkt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kLUZ2/dJMcaa5dmTA/PFGijpONxwUjIXRR7xtkt1/img.png&quot; data-alt=&quot;NCP 서버 설정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kLUZ2/dJMcaa5dmTA/PFGijpONxwUjIXRR7xtkt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkLUZ2%2FdJMcaa5dmTA%2FPFGijpONxwUjIXRR7xtkt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;750&quot; height=&quot;502&quot; data-origin-width=&quot;2064&quot; data-origin-height=&quot;1382&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;NCP 서버 설정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;2.2.1. VPC (Virtual Private Cloud)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VPC는 클라우드 안에 만드는 독립된 네트워크 공간이다. 서버들이 들어갈 폐쇄된 네트워크 구역이라고 생각하면 된다. 백엔드에서 이미 VPC를 만들어놨기 때문에 같은 VPC를 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2.2.2 &lt;span data-token-index=&quot;0&quot;&gt;Subnet&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Subnet은 VPC 안에서 IP 대역을 나누는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1426&quot; data-origin-height=&quot;1498&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUi5XY/dJMcacPvXOv/yw8uL4e64RzYcgpnLKKEE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUi5XY/dJMcacPvXOv/yw8uL4e64RzYcgpnLKKEE0/img.png&quot; data-alt=&quot;NCP Subnet 생성&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUi5XY/dJMcacPvXOv/yw8uL4e64RzYcgpnLKKEE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUi5XY%2FdJMcacPvXOv%2Fyw8uL4e64RzYcgpnLKKEE0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;519&quot; height=&quot;545&quot; data-origin-width=&quot;1426&quot; data-origin-height=&quot;1498&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;NCP Subnet 생성&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 서버가 10.0.1.0/24를 사용하고 있어 10.0.2.0/24로 설정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js 서버를 외부에서 접속해야 하므로 Internet Gateway 전용 여부는 &amp;lsquo;Y&amp;rsquo;로 설정하고, 간단한 프로젝트이기 때문에 용도는 &amp;lsquo;일반&amp;rsquo;으로 설정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2.2.3 &lt;span data-token-index=&quot;0&quot;&gt;서버 스펙&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;984&quot; data-origin-height=&quot;82&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pUieD/dJMcahQN16J/3GYZ5XDipQC2amzUR9DxR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pUieD/dJMcahQN16J/3GYZ5XDipQC2amzUR9DxR0/img.png&quot; data-alt=&quot;NCP 서버 스펙 선택&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pUieD/dJMcahQN16J/3GYZ5XDipQC2amzUR9DxR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpUieD%2FdJMcahQN16J%2F3GYZ5XDipQC2amzUR9DxR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;430&quot; height=&quot;36&quot; data-origin-width=&quot;984&quot; data-origin-height=&quot;82&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;NCP 서버 스펙 선택&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본으로 제시되는 스펙은 Standard s2-g3a (vCPU 2EA, Memory 8GB)로 월 82,240원이다. 공식 가이드에 따르면 일반 웹 용도로는 Standard가 권장되지만, 비용을 고려해 High-CPU c2-g3a (vCPU 2EA, Memory 4GB)로 선택했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2.2.4 &lt;span data-token-index=&quot;0&quot;&gt;요금제 및 기타 설정&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;요금제&lt;/b&gt;: 월 요금제 (계속 켜둬야 하므로)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;서버 개수&lt;/b&gt;: 1개 (서버 생성시 공인 IP를 함께 생성하려면 서버 개수는 1개여야 한다.)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Network Interface&lt;/b&gt;: 기본값 (IP 자동 할당)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;공인 IP&lt;/b&gt;: 할당 (외부에서 접속을 위해 필수)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2.2.5 &lt;span data-token-index=&quot;0&quot;&gt;스토리지&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스토리지는 서버의 하드디스크 용량을 설정하는 것이다. 운영체제, 프로젝트 코드, 빌드 파일 등이 저장되는 공간이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2064&quot; data-origin-height=&quot;558&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c50AnQ/dJMcagj3LDU/cK9CUtDF0lBsMTVoqKd4vk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c50AnQ/dJMcagj3LDU/cK9CUtDF0lBsMTVoqKd4vk/img.png&quot; data-alt=&quot;NCP 스토리지 설정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c50AnQ/dJMcagj3LDU/cK9CUtDF0lBsMTVoqKd4vk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc50AnQ%2FdJMcagj3LDU%2FcK9CUtDF0lBsMTVoqKd4vk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;750&quot; height=&quot;203&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2064&quot; data-origin-height=&quot;558&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;NCP 스토리지 설정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;크기&lt;/b&gt;: 30GB (기본 10GB는 부족할 수 있어서 여유롭게 설정했다. 나중에 확장이 가능하다.)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;타입&lt;/b&gt;: CB1 (일반 SSD)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.3 인증키 및 ACG 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2.3.1. &lt;span data-token-index=&quot;0&quot;&gt;인증키 설정&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증키는 SSH로 서버에 접속할 때 사용하는 보안 키이다. 비밀번호 대신 &lt;code&gt;.pem&lt;/code&gt; 파일로 인증한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2064&quot; data-origin-height=&quot;498&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rpn4J/dJMcahDfxUR/RvRlZg8iMoNXQmiBQJpkP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rpn4J/dJMcahDfxUR/RvRlZg8iMoNXQmiBQJpkP0/img.png&quot; data-alt=&quot;NCP 인증키 설정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rpn4J/dJMcahDfxUR/RvRlZg8iMoNXQmiBQJpkP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Frpn4J%2FdJMcahDfxUR%2FRvRlZg8iMoNXQmiBQJpkP0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;750&quot; height=&quot;181&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2064&quot; data-origin-height=&quot;498&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;NCP 인증키 설정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 서버와 프론트엔드 서버를 구분하기 위해 새로운 인증키를 생성했다. [새로운 인증키 생성]을 선택하면 &lt;code&gt;.pem&lt;/code&gt; 파일을 다운로드받을 수 있다. 이 파일은 안전한 곳에 보관해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2.3.2 &lt;span data-token-index=&quot;0&quot;&gt;ACG (Access Control Group) 설정&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2064&quot; data-origin-height=&quot;698&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnpHCh/dJMcafrUPgp/RYmEvig7W6mCAkpl8YuUUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnpHCh/dJMcafrUPgp/RYmEvig7W6mCAkpl8YuUUk/img.png&quot; data-alt=&quot;NCP ACG 설정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnpHCh/dJMcafrUPgp/RYmEvig7W6mCAkpl8YuUUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnpHCh%2FdJMcafrUPgp%2FRYmEvig7W6mCAkpl8YuUUk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;750&quot; height=&quot;254&quot; data-origin-width=&quot;2064&quot; data-origin-height=&quot;698&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;NCP ACG 설정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 백엔드 개발자가 만들어둔 ACG를 사용했다. 나중에 필요한 포트는 추가로 열 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 서버 환경 구축&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.1 SSH 접속하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 생성이 완료되면 SSH를 통해 서버에 접속할 수 있다. macOS는 터미널에서 기본적으로 SSH 접속이 가능하다. Windows는 PowerShell이나 PuTTY를 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3.1.1 &lt;span data-token-index=&quot;0&quot;&gt;접속 명령어&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 명령어로 접속할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;ssh -i ~/Documents/Key/my-key.pem root@서버IP&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;-i&lt;/code&gt;: 인증키 파일 경로 (인증키 설정에서 받은 파일 주소)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;root&lt;/code&gt;: 사용자 이름 (NCP 기본값은 root)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;서버IP&lt;/code&gt;: NCP 콘솔의 Server에서 생성한 서버를 클릭하면 공인 IP를 확인할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3.1.2 &lt;span data-token-index=&quot;0&quot;&gt;권한 오류 해결&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 접속 시 SSH가 처음 접속하는 서버가 맞는지 확인하는 메시지가 뜨는데, &lt;code&gt;yes&lt;/code&gt;를 입력하면 된다. 그리고 접속을 시도하면 이런 오류가 발생할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;Warning: Permanently added '서버IP' (ED25519) to the list of known hosts. Permissions 0644 for '~/Documents/Key/my-key.pem' are too open.&lt;/i&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;키 파일의 권한이 너무 열려있다는 뜻이다. 다음 명령어로 권한을 변경해준다.&lt;/p&gt;
&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;chmod 400 ~/Documents/Key/my-key.pem
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;3.1.3 관리자 비밀번호 확인&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 다시 SSH 접속을 시도해보면 비밀번호를 요구한다. 비밀번호는 [NCP 콘솔] - [서버 관리 및 설정 변경] - [관리자 비밀번호 확인]에서 인증 키를 첨부하면 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;568&quot; data-origin-height=&quot;226&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/t9Ggi/dJMcahQN2e0/1hc45VKiahNJLkrOaObEK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/t9Ggi/dJMcahQN2e0/1hc45VKiahNJLkrOaObEK0/img.png&quot; data-alt=&quot;NCP Server 관리 메뉴&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/t9Ggi/dJMcahQN2e0/1hc45VKiahNJLkrOaObEK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ft9Ggi%2FdJMcahQN2e0%2F1hc45VKiahNJLkrOaObEK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;277&quot; height=&quot;110&quot; data-origin-width=&quot;568&quot; data-origin-height=&quot;226&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;NCP Server 관리 메뉴&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비밀번호를 입력하면 서버에 잘 접속된다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1140&quot; data-origin-height=&quot;424&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kwAZg/dJMcaaxofKq/NfNSjlhdqiik9sCHAWSOok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kwAZg/dJMcaaxofKq/NfNSjlhdqiik9sCHAWSOok/img.png&quot; data-alt=&quot;SSH 서버 접속&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kwAZg/dJMcaaxofKq/NfNSjlhdqiik9sCHAWSOok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkwAZg%2FdJMcaaxofKq%2FNfNSjlhdqiik9sCHAWSOok%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;462&quot; height=&quot;172&quot; data-origin-width=&quot;1140&quot; data-origin-height=&quot;424&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;SSH 서버 접속&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;root&lt;/code&gt; 계정은 시스템의 모든 권한을 가지고 있어 위험이 크기 때문에 실제 서비스 운영 시에는 보안을 위해 &lt;code&gt;root&lt;/code&gt; 대신 별도 사용자 계정을 생성해서 사용하는 것이 권장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.2 개발 환경 구축 (Node.js 및 Git 설치)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3.2.1 &lt;span data-token-index=&quot;0&quot;&gt;Node.js 및 npm 설치&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에 Node.js, npm, Git을 설치한다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;sudo apt install nodejs
node -v # 설치 확인

sudo apt install npm
npm -v # 설치 확인&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 Node.js를 설치하면 npm이 포함되어 있을 거라고 생각해서 npm을 따로 설치하지 않았는데, &lt;code&gt;npm install&lt;/code&gt; 명령어가 작동하지 않았다. 일부 리눅스 배포판에서는 Node.js와 npm이 별도 패키지로 분리되어 있다고 한다. &lt;b&gt;npm도 반드시 따로 설치해주어야 한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 이렇게 Node.js를&amp;nbsp;&lt;code&gt;apt&lt;/code&gt;로 설치하니 약간 번거로운 점이 있었다. 관련 내용은 &lt;a href=&quot;https://sohxxny.tistory.com/8&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이 글&lt;/a&gt;에서 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3.2.2 &lt;span data-token-index=&quot;0&quot;&gt;Git 설치&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;sudo apt install git
git -v # 설치 확인&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.3 프로젝트 클론 및 빌드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3.3.1 프로젝트 클론&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# 프로젝트 클론
git clone https://github.com/my-repo/project.git

# 프로젝트로 이동
cd project&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;.env&lt;/code&gt; 파일은 따로 만들어서 환경 변수를 추가해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3.3.2 의존성 설치 및 빌드/실행&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# 의존성 설치
npm install

# 빌드 및 실행
npm run build
npm start&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드가 완료되고 서버가 실행되면 &lt;code&gt;http://서버IP:3000&lt;/code&gt;으로 접속할 수 있다. 하지만 아직 방화벽(ACG)에서 3000번 포트를 열어주지 않았기 때문에 접속이 안 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3.3.4 &lt;span data-token-index=&quot;0&quot;&gt;포트 열기 (ACG 설정)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js는 기본적으로 3000번 포트를 사용한다. 외부에서 접속하려면 ACG 설정에서 3000번 포트를 열어주어야 한다. [NCP 콘솔] - [Server] - [ACG 규칙] - [규칙 설정]에 들어가면 인바운드에서 새로운 포트를 추가할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1948&quot; data-origin-height=&quot;786&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ms1V5/dJMcaiIT7of/jqXfGhRpqKWUQ86e78VhG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ms1V5/dJMcaiIT7of/jqXfGhRpqKWUQ86e78VhG0/img.png&quot; data-alt=&quot;NCP ACG 인바운드 설정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ms1V5/dJMcaiIT7of/jqXfGhRpqKWUQ86e78VhG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fms1V5%2FdJMcaiIT7of%2FjqXfGhRpqKWUQ86e78VhG0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;750&quot; height=&quot;303&quot; data-origin-width=&quot;1948&quot; data-origin-height=&quot;786&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;NCP ACG 인바운드 설정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 인바운드 규칙(들어오는 트래픽)을 추가한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;프로토콜&lt;/b&gt;: TCP&lt;/li&gt;
&lt;li&gt;&lt;b&gt;접근 소스&lt;/b&gt;: 0.0.0.0/0 (누구나 접속 가능)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;허용 포트&lt;/b&gt;: 3000&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정을 저장하고 &lt;code&gt;http://서버IP:3000&lt;/code&gt;으로 접속하면 Next.js 앱이 잘 뜬다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. Next.js 실행하기&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.1 PM2 설치 및 실행&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지는 &lt;code&gt;npm start&lt;/code&gt;로 서버를 실행했다. 하지만 이 방식에는 큰 문제가 있다. 바로 SSH 접속을 종료하면 서버도 같이 꺼진다는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위해 &lt;b&gt;PM2&lt;/b&gt;라는 도구를 사용할 수 있다. PM2는 &lt;b&gt;Node.js 프로세스를 백그라운드에서 관리&lt;/b&gt;해준다. PM2를 사용하면 SSH를 종료해도 서버를 계속 실행할 수 있고, 서버가 에러로 죽거나 재부팅할 때도 자동으로 다시 시작하도록 할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4.1.1 &lt;span data-token-index=&quot;0&quot;&gt;PM2 설치&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 실행 중인 &lt;code&gt;npm start&lt;/code&gt;는 종료 후 설치 및 실행한다.&lt;/p&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;npm install -g pm2
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4.1.2 &lt;span data-token-index=&quot;0&quot;&gt;PM2로 실행&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;--name&lt;/code&gt;: PM2에서 관리할 프로세스 이름 지정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-- start&lt;/code&gt;: npm start 명령어 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;cd project
pm2 start npm --name &quot;project&quot; -- start
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;pm2 list&lt;/code&gt;로 현재 실행중인 프로세스를 확인하면 정상적으로 표시되어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4.1.3 &lt;span data-token-index=&quot;0&quot;&gt;상태 저장 및 자동 시작 설정&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;# 현재 상태 저장
pm2 save

# 서버 재부팅 시 자동 실행되게 설정
pm2 startup
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;pm2 startup&lt;/code&gt; 입력 시 아래와 같은 명령어가 출력되는데, 이 명령어를 복사해서 실행해야 한다.&lt;/p&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u root --hp /root
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 SSH 접속을 종료해도 서버가 계속 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.2 포트 분리 (prod/dev)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영 배포만 따로 두어도 되지만, 운영 환경은 배포 주기가 길다. 개발자 팀원들은 로컬 환경에서 바로 테스트할 수 있지만, 비개발자 팀원들은 실제 서버에 배포되기까지 오래 기다려야 할 수밖에 없다. 빠른 피드백과 협업을 위해 개발 환경도 별도로 배포하기로 했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;main 브랜치 &amp;rarr; 3000번 포트 (프로덕션)&lt;/li&gt;
&lt;li&gt;develop 브랜치 &amp;rarr; 3001번 포트 (개발)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4.2.1 &lt;span data-token-index=&quot;0&quot;&gt;develop 브랜치 클론&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 프로젝트는 main 브랜치였으므로 &lt;code&gt;project-prod&lt;/code&gt;와 같이 폴더 이름을 변경하고, develop 브랜치를 별도로 클론했다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# 프로젝트 클론
git clone -b develop https://github.com/my-repo.git project-dev

# 의존성 설치 및 빌드
cd project-dev
npm install
npm run build&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4.2.2 &lt;span data-token-index=&quot;0&quot;&gt;3001번 포트 열기&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 환경을 동시에 실행하려면 각각 다른 포트가 필요하므로 ACG 설정에서 3000번 포트를 추가했던 것과 똑같이 3001번 포트도 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4.2.3 &lt;span data-token-index=&quot;0&quot;&gt;각각 다른 포트로 PM2 실행&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pm2 설정 파일 &lt;code&gt;ecosystem.config.js&lt;/code&gt;를 사용하는 방법도 있다.&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;# main 브랜치 (3000번 포트)
cd ~/project-prod
PORT=3000 pm2 start npm --name &quot;prod&quot; -- start

# develop 브랜치 (3001번 포트)
cd ~/project-dev
PORT=3001 pm2 start npm --name &quot;dev&quot; -- start
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;pm2 list&lt;/code&gt;로 확인하면 두 개의 프로세스가 실행 중인 걸 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1114&quot; data-origin-height=&quot;164&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/x4wzU/dJMcabbYfaK/AUFL5jxNfNQBkwT0289Od1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/x4wzU/dJMcabbYfaK/AUFL5jxNfNQBkwT0289Od1/img.png&quot; data-alt=&quot;PM2 프로세스 목록&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/x4wzU/dJMcabbYfaK/AUFL5jxNfNQBkwT0289Od1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fx4wzU%2FdJMcabbYfaK%2FAUFL5jxNfNQBkwT0289Od1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;450&quot; height=&quot;66&quot; data-origin-width=&quot;1114&quot; data-origin-height=&quot;164&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;PM2 프로세스 목록&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4.2.4 &lt;span data-token-index=&quot;0&quot;&gt;상태 저장&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;pm2 save
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.3 코드 수정 후 재배포&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나중에 코드를 수정하고 재배포할 때는 다음 명령어만 실행하면 된다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# SSH 접속하여 프로젝트 디렉토리로 이동 후 업데이트
git pull
npm install
npm run build
pm2 restart prod # 또는 dev&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. 마무리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 단일 서버에만 배포했지만 추후에는 CDN, Object Storage를 활용해서 정적 리소스와 서버를 분리하는 구조도 만들어보고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sohxxny.tistory.com/6&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;다음 글&lt;/a&gt;에서는 &lt;b&gt;Nginx를 이용한 리버스 프록시 설정, HTTPS 적용, 도메인 연결, 간단한 자동 배포 설정 과정&lt;/b&gt;을 정리해볼 예정이다!&lt;/p&gt;</description>
      <category>DevOps</category>
      <category>NCP</category>
      <category>next.js</category>
      <category>pm2</category>
      <category>네이버 클라우드</category>
      <category>배포</category>
      <author>카페모카 소믈리에</author>
      <guid isPermaLink="true">https://sohxxny.tistory.com/5</guid>
      <comments>https://sohxxny.tistory.com/5#entry5comment</comments>
      <pubDate>Mon, 2 Feb 2026 16:06:50 +0900</pubDate>
    </item>
  </channel>
</rss>