서버 한 대로 사이트 14개 굴리기 — nginx · PM2 · certbot 구성
지금 서버 한 대에서 사이트 14개가 돌아가고 있다. 정적 사이트도 있고 Node 앱도 있고, 도메인은 .io, .co.kr, 서브도메인까지 섞여 있다. 특별한 기술은 없고, 규칙을 하나 정해두고 그것만 지킨 것이 전부다.
구성
사양은 4 vCPU / 15GB RAM / 969GB. 지금 디스크 사용률은 2%다. 개인 사이트 수십 개는 이 정도면 남는다.
- nginx — 모든 요청의 입구. 정적 파일은 여기서 바로 주고, 동적 요청만 뒤로 넘긴다
- PM2 — Node 앱 프로세스 관리. 죽으면 살리고, 재부팅해도 복구
- MySQL — 앱별로 DB와 계정을 따로 판다
- certbot — Let's Encrypt 인증서 발급과 자동 갱신
규칙 1 — 사이트 하나에 설정 파일 하나
/etc/nginx/sites-available/도메인으로 파일을 만들고 sites-enabled에 심볼릭 링크를 건다. 한 파일에 여러 도메인을 몰아넣지 않는다. 사이트를 내릴 때 링크만 지우면 되고, 실수로 다른 사이트를 건드릴 일이 없다.
공통 보안 헤더처럼 반복되는 건 snippets/에 빼두고 include한다.
server {
server_name node9.io www.node9.io;
root /var/www/node9.io;
include snippets/common-headers.conf;
location ~* \.(svg|png|jpg|webp|ico|woff2)$ {
expires 30d;
add_header Cache-Control "public, max-age=2592000";
}
location / {
try_files $uri $uri.html $uri/index.html =404;
}
}
try_files $uri $uri.html를 넣어두면 /about.html을 /about으로도 열 수 있다. 정적 사이트에서 주소를 깔끔하게 유지하는 가장 싼 방법이다.
규칙 2 — Node 앱은 포트를 겹치지 않게 미리 나눠둔다
앱마다 포트를 하나씩 정하고 127.0.0.1에만 바인딩한다. 외부에서 포트로 직접 접근할 수 없게 하고, 무조건 nginx를 거치게 한다.
app.listen(PORT, '127.0.0.1');
새 앱을 올릴 때는 먼저 빈 포트를 확인한다.
sudo ss -ltn | grep -oE '127.0.0.1:[0-9]+' | sort -u
nginx 쪽은 이렇게 넘긴다. Host와 X-Forwarded-Proto를 빼먹으면 앱이 자기 주소를 http://localhost로 착각해서 리다이렉트가 깨진다.
location / {
proxy_pass http://127.0.0.1:4040;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
Express에서는 app.set('trust proxy', 1)을 켜야 req.ip와 secure 쿠키가 제대로 동작한다.
규칙 3 — PM2는 반드시 save까지
PM2 문서의 startup 항목에 나와 있듯, 앱을 띄우는 것까지는 다들 하는데, pm2 save와 pm2 startup을 빼먹으면 서버가 재부팅되는 순간 전부 내려간다. 이건 한 번 당해봐야 안 잊는다.
pm2 start ecosystem.config.js
pm2 save
pm2 startup # 출력되는 명령을 sudo로 한 번 실행
환경변수는 코드에 넣지 않고 .env에 두고 권한을 600으로 잠근다. ecosystem 파일에서 읽어서 넘긴다.
규칙 4 — 인증서는 발급하고 잊는다
certbot을 쓰면 도메인을 추가할 때마다 한 줄이면 끝난다. certbot이 nginx 설정까지 알아서 고쳐주고, 갱신 타이머도 등록해둔다.
sudo certbot --nginx -d blog.example.com --redirect
와일드카드 DNS(*.example.com)를 미리 걸어두면 서브도메인을 새로 팔 때 DNS 전파를 기다릴 필요가 없다. 도메인 등록기관 설정 한 번으로 이후가 편해진다.
안 하는 것
- Docker를 쓰지 않는다. 앱이 전부 Node 한 가지라 런타임을 격리할 이유가 없었다. 메모리와 운영 복잡도만 늘었을 것이다
- 포트를 외부에 열지 않는다. 22, 80, 443 외에는 방화벽에서 막는다
- DB 계정을 공유하지 않는다. 앱마다 계정을 파고
SELECT, INSERT, UPDATE, DELETE정도만 준다. 하나가 뚫려도 옆으로 못 번진다
지금 상태
이 구성으로 정적 사이트 8개, Node 앱 6개가 돌고 있고 메모리는 절반쯤 쓴다. 사이트를 하나 더 올리는 데 걸리는 시간은 설정 파일 하나 쓰고 certbot 한 줄, 5분이면 된다. 규칙을 단순하게 잡아두면 늘어나도 관리 비용이 거의 안 는다.