- 운영서버(211.117.60.189) 전체 설치 완료 문서화 - OS, MySQL 8.4.8, Redis 7.0.15, Nginx 1.24.0, PHP 8.4.18 - 7개 도메인 SSL (develop@codebridge-x.com), PM2 cluster - Supervisor queue worker, node_exporter, 보안 설정 - CI/CD 서버(110.10.147.46) 셋팅 가이드 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
34 KiB
운영서버 셋팅 가이드
작성일: 2026-02-23 | 최종 수정: 2026-02-24 상태: ✅ 완료 (커널 6.8.0-100, 전체 서비스 가동 중)
1. 서버 구성 개요
인프라 구조
┌──────────────────────────────────────────────────────────┐
│ 운영서버 (2 vCPU / 8GB) │
│ Ubuntu 24.04 / IDC 클라우드 │
│ IP: 211.117.60.189 │
│ Kernel: 6.8.0-100-generic │
│ │
│ ┌──────────┐ ┌───────────┐ ┌───────────────────────┐ │
│ │ Nginx │ │ Certbot │ │ UFW (22,80,443,9100) │ │
│ │ (Proxy) │ │ (SSL) │ │ │ │
│ └────┬─────┘ └───────────┘ └───────────────────────┘ │
│ │ │
│ ┌────┴───────────────────────────────────────────────┐ │
│ │ Virtual Hosts (운영) │ │
│ │ │ │
│ │ sam.it.kr ──────────→ Next.js (PM2 cluster, :3000)│ │
│ │ api.sam.it.kr ──────→ PHP-FPM (api pool) │ │
│ │ admin.codebridge-x.com → PHP-FPM (admin pool) │ │
│ │ sales.codebridge-x.com → PHP-FPM (sales pool) │ │
│ │ codebridge-x.com ──→ 정적 랜딩페이지 │ │
│ │ │ │
│ │ Virtual Hosts (Stage) │ │
│ │ │ │
│ │ stage.sam.it.kr ────→ Next.js (PM2 fork, :3100) │ │
│ │ stage-api.sam.it.kr → PHP-FPM (api-stage pool) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────┐ ┌────────────┐ ┌─────────────────┐ │
│ │ MySQL 8.4 │ │ Redis │ │ Supervisor │ │
│ │ (Master) │ │ (캐시/큐) │ │ (Queue Worker) │ │
│ └────────────┘ └────────────┘ └─────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ node_exporter (:9100) → CI/CD Prometheus │ │
│ └─────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────┐
│ CI/CD서버 (2 vCPU / 8GB) │
│ Ubuntu 24.04 / IDC 클라우드 │
│ IP: 110.10.147.46 │
│ │
│ ┌──────────┐ ┌───────────┐ ┌────────────────────┐ │
│ │ Jenkins │ │ Gitea │ │ MySQL 8.4 │ │
│ │ │ │ │ │ (백업/슬레이브) │ │
│ └──────────┘ └───────────┘ └────────────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Prometheus │ │ Grafana │ │
│ │ (수집) │ │ (시각화) │ │
│ └──────────────┘ └──────────────┘ │
└───────────────────────────────────────────────────────────┘
도메인 매핑
| 도메인 | 서비스 | 앱 | SSL |
|---|---|---|---|
| sam.it.kr | Next.js 15 (PM2 cluster) | react/ | Let's Encrypt |
| api.sam.it.kr | Laravel 12 API | api/ | Let's Encrypt |
| admin.codebridge-x.com | Laravel 12 Admin | mng/ | Let's Encrypt |
| sales.codebridge-x.com | Plain PHP (레거시) | sales/ | Let's Encrypt |
| codebridge-x.com (+ www) | 정적 랜딩페이지 | landing/ | Let's Encrypt |
| stage.sam.it.kr | Next.js 15 (PM2 fork) | react/ (stage) | Let's Encrypt |
| stage-api.sam.it.kr | Laravel 12 API (stage) | api/ (stage) | Let's Encrypt |
도메인 환경 분리
| 서비스 | 운영 | Stage | 개발 |
|---|---|---|---|
| Front | sam.it.kr | stage.sam.it.kr | dev.codebridge-x.com |
| API | api.sam.it.kr | stage-api.sam.it.kr | api.codebridge-x.com |
| Admin | admin.codebridge-x.com | - | mng.codebridge-x.com |
| Sales | sales.codebridge-x.com | - | salesdev.codebridge-x.com |
| Landing | codebridge-x.com | - | - |
SSH 접속 정보
| 별칭 | IP | 용도 | 설정완료 |
|---|---|---|---|
| sam-prod | 211.117.60.189 | 운영서버 | ✅ |
| sam-cicd | 110.10.147.46 | CI/CD서버 | ✅ |
| sam-dev | 114.203.209.83 | 개발서버 | ✅ |
보안: root SSH 차단, 비밀번호 로그인 차단, hskwon 키 인증 + sudo NOPASSWD
2. 메모리 배분 계획 (8GB)
| 서비스 | 할당 | 설정 | 비고 |
|---|---|---|---|
| MySQL 8.4 | ~2GB | innodb_buffer_pool_size=2G | 메인 DB |
| Redis | ~0.5GB | maxmemory 512mb | 캐시/세션/큐 |
| PHP-FPM (API) | ~0.8GB | pm.max_children=10 | 트래픽 주력 |
| PHP-FPM (Admin) | ~0.3GB | pm.max_children=5 | 관리자용 |
| PHP-FPM (Sales) | ~0.2GB | pm.max_children=3 | 영업자용 |
| PHP-FPM (API-Stage) | ~0.2GB | pm.max_children=3 | Stage |
| Next.js 운영 (PM2 cluster×2) | ~0.6GB | max-old-space-size=256 | 무중단 배포 |
| Next.js Stage (PM2 fork×1) | ~0.15GB | max-old-space-size=128 | 트래픽 적음 |
| Supervisor (Queue Worker) | ~0.1GB | numprocs=2 | Laravel 큐 |
| Nginx | ~0.1GB | worker_connections 1024 | 리버스 프록시 |
| node_exporter | ~0.01GB | - | 모니터링 |
| OS + 여유 | ~2.9GB | 스왑 4GB | 안전 마진 |
| 합계 | ~8GB | 스왑 4GB 백업 |
3. 설치 순서
① OS 기본 셋팅 ✅ (2026-02-23)
# 시스템 업데이트
sudo apt update && sudo apt upgrade -y
# 기본 패키지
sudo apt install -y curl wget git unzip vim htop net-tools
# 타임존
sudo timedatectl set-timezone Asia/Seoul
# 스왑 4GB 설정
sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
# 스왑 최적화
echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
# UFW 방화벽
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
sudo ufw allow from 110.10.147.46 to any port 9100 # node_exporter (CI/CD만)
sudo ufw allow from 110.10.147.46 to any port 3306 # MySQL (CI/CD 백업용)
sudo ufw enable
# webservice 사용자 그룹 생성
sudo groupadd -f webservice
sudo usermod -aG webservice hskwon
sudo usermod -aG webservice www-data
sudo mkdir -p /home/webservice
sudo chown hskwon:webservice /home/webservice
sudo chmod 2775 /home/webservice # setgid
② MySQL 8.4 ✅ (2026-02-23)
설치 완료: MySQL 8.4.8 (GPG 키 만료 이슈로 Ubuntu keyserver 경유 설치)
성능 튜닝 (/etc/mysql/mysql.conf.d/sam-tuning.cnf):
[mysqld]
innodb_buffer_pool_size = 2048M
innodb_log_file_size = 512M
innodb_flush_log_at_trx_commit = 2
max_connections = 100
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2
validate_password.policy = LOW
DB 및 사용자:
-- 데이터베이스 (4개)
CREATE DATABASE sam CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE DATABASE sam_stage CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE DATABASE sam_stat CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE DATABASE codebridge CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 앱 사용자 (codebridge)
CREATE USER 'codebridge'@'localhost' IDENTIFIED BY '<비밀번호>';
GRANT ALL PRIVILEGES ON sam.* TO 'codebridge'@'localhost';
GRANT ALL PRIVILEGES ON sam_stage.* TO 'codebridge'@'localhost';
GRANT ALL PRIVILEGES ON sam_stat.* TO 'codebridge'@'localhost';
GRANT ALL PRIVILEGES ON codebridge.* TO 'codebridge'@'localhost';
-- 관리자 (hskwon) - auth_socket + 패스워드 인증
CREATE USER 'hskwon'@'localhost' IDENTIFIED WITH auth_socket;
GRANT ALL PRIVILEGES ON *.* TO 'hskwon'@'localhost' WITH GRANT OPTION;
-- CI/CD 서버 백업용
CREATE USER 'sam_backup'@'110.10.147.46' IDENTIFIED BY '<백업용_비밀번호>';
GRANT SELECT, LOCK TABLES, SHOW VIEW, EVENT, TRIGGER ON sam.* TO 'sam_backup'@'110.10.147.46';
GRANT SELECT, LOCK TABLES, SHOW VIEW, EVENT, TRIGGER ON sam_stat.* TO 'sam_backup'@'110.10.147.46';
FLUSH PRIVILEGES;
참고: root는 auth_socket 인증 (비밀번호 없이
sudo mysql로 접근) MySQL 업그레이드 전략: 운영(8.4) 안정화 확인 후 → 개발서버(8.0→8.4) 업그레이드
③ Redis 7.0.15 ✅ (2026-02-24)
# Redis 설치
sudo apt install -y redis-server
# 설정 (/etc/redis/redis.conf)
# bind 127.0.0.1 ::1
# maxmemory 512mb
# maxmemory-policy allkeys-lru
# supervised systemd
sudo systemctl enable redis-server
sudo systemctl restart redis-server
# 연결 테스트
redis-cli ping # → PONG
Laravel .env 설정:
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
QUEUE_CONNECTION=redis
CACHE_STORE=redis
SESSION_DRIVER=redis
④ Nginx 1.24.0 + Certbot 2.9.0 ✅ (2026-02-24)
# Nginx 설치
sudo apt install -y nginx
# Certbot 설치
sudo apt install -y certbot python3-certbot-nginx
# 보안 스니펫 생성 (개발서버와 동일)
sudo tee /etc/nginx/snippets/security.conf > /dev/null << 'SECEOF'
# 숨김 파일 차단 (.env, .git 등)
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
# 환경설정/백업/로그 파일 차단
location ~* \.(env|ini|log|conf|bak|sql)$ {
deny all;
access_log off;
log_not_found off;
}
# Git 폴더 차단
location ~ ^/\.git {
deny all;
access_log off;
log_not_found off;
}
# Composer, Node 패키지 등 민감 파일 차단
location ~* /(composer\.(json|lock)|package\.json|yarn\.lock|phpunit\.xml|artisan|server\.php)$ {
deny all;
access_log off;
log_not_found off;
}
SECEOF
기본 설정 최적화 (/etc/nginx/nginx.conf):
worker_processes auto;
events {
worker_connections 1024;
}
http {
keepalive_timeout 65;
client_max_body_size 50M;
gzip on;
gzip_types text/plain application/json application/javascript text/css;
}
⑤ PHP 8.4.18 + Composer 2.9.5 ✅ (2026-02-24)
# PHP 8.4 PPA
sudo add-apt-repository ppa:ondrej/php -y
sudo apt update
# PHP 8.4 + 확장
sudo apt install -y \
php8.4-fpm \
php8.4-mysql \
php8.4-mbstring \
php8.4-xml \
php8.4-curl \
php8.4-zip \
php8.4-gd \
php8.4-bcmath \
php8.4-intl \
php8.4-redis \
php8.4-opcache
# Composer 설치
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
PHP-FPM Pool 분리 (4개):
# /etc/php/8.4/fpm/pool.d/api.conf
[api]
user = www-data
group = webservice
listen = /run/php/php8.4-fpm-api.sock
listen.owner = www-data
listen.group = www-data
pm = dynamic
pm.max_children = 10
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6
pm.max_requests = 500
php_admin_value[memory_limit] = 128M
php_admin_value[upload_max_filesize] = 50M
php_admin_value[post_max_size] = 50M
php_admin_value[display_errors] = Off
# /etc/php/8.4/fpm/pool.d/admin.conf
[admin]
user = www-data
group = webservice
listen = /run/php/php8.4-fpm-admin.sock
listen.owner = www-data
listen.group = www-data
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
pm.max_requests = 500
php_admin_value[memory_limit] = 128M
php_admin_value[upload_max_filesize] = 50M
php_admin_value[post_max_size] = 50M
php_admin_value[display_errors] = Off
# /etc/php/8.4/fpm/pool.d/sales.conf
[sales]
user = www-data
group = webservice
listen = /run/php/php8.4-fpm-sales.sock
listen.owner = www-data
listen.group = www-data
pm = dynamic
pm.max_children = 3
pm.start_servers = 1
pm.min_spare_servers = 1
pm.max_spare_servers = 2
pm.max_requests = 500
php_admin_value[memory_limit] = 128M
php_admin_value[upload_max_filesize] = 50M
php_admin_value[post_max_size] = 50M
php_admin_value[display_errors] = Off
# /etc/php/8.4/fpm/pool.d/api-stage.conf
[api-stage]
user = www-data
group = webservice
listen = /run/php/php8.4-fpm-api-stage.sock
listen.owner = www-data
listen.group = www-data
pm = dynamic
pm.max_children = 3
pm.start_servers = 1
pm.min_spare_servers = 1
pm.max_spare_servers = 2
pm.max_requests = 500
php_admin_value[memory_limit] = 128M
php_admin_value[upload_max_filesize] = 50M
php_admin_value[post_max_size] = 50M
php_admin_value[display_errors] = Off
# 기본 pool 제거, 분리된 pool 사용
sudo rm /etc/php/8.4/fpm/pool.d/www.conf
sudo systemctl restart php8.4-fpm
⑥ Supervisor ✅ (2026-02-24)
sudo apt install -y supervisor
# Laravel Queue Worker 설정
sudo tee /etc/supervisor/conf.d/sam-queue.conf > /dev/null << 'EOF'
[program:sam-queue-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /home/webservice/api/current/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/home/webservice/api/shared/storage/logs/queue-worker.log
stopwaitsecs=3600
EOF
sudo supervisorctl reread
sudo supervisorctl update
⑦ Laravel API 배포 (api.sam.it.kr) ✅ (2026-02-24)
초기 배포: 개발서버에서 tar pipe로 전체 소스 전송 (이미지/업로드 파일 포함) 이후 배포: CI/CD Jenkins → Git pull + composer install
# 디렉토리 구조
sudo mkdir -p /home/webservice/api/{releases,shared/storage/{app/public,framework/{cache,sessions,views},logs}}
sudo chown -R hskwon:webservice /home/webservice/api
sudo chmod -R 2775 /home/webservice/api
# 초기 배포 (개발서버 → 운영서버, tar pipe)
ssh sam-dev "cd /home/webservice && tar cf - --exclude='vendor' --exclude='node_modules' --exclude='.env' api/" | \
ssh sam-prod "cd /home/webservice && tar xf -"
# 이후: CI/CD에서 git clone + releases 구조 사용
ln -sfn /home/webservice/api/releases/20260223 /home/webservice/api/current
# shared 심링크
ln -sfn /home/webservice/api/shared/storage /home/webservice/api/current/storage
ln -sfn /home/webservice/api/shared/.env /home/webservice/api/current/.env
# 의존성 설치 + 최적화
cd /home/webservice/api/current
composer install --no-dev --optimize-autoloader
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan migrate --force
Nginx 설정 (api.sam.it.kr):
# /etc/nginx/sites-available/api.sam.it.kr
server {
listen 80;
server_name api.sam.it.kr;
root /home/webservice/api/current/public;
index index.php;
access_log /var/log/nginx/api.sam.it.kr.access.log;
error_log /var/log/nginx/api.sam.it.kr.error.log;
include /etc/nginx/snippets/security.conf;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.4-fpm-api.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_read_timeout 60;
}
client_max_body_size 50M;
}
⑦-stage Laravel API Stage (stage-api.sam.it.kr) ✅ (2026-02-24)
# 디렉토리 (API와 동일 구조)
sudo mkdir -p /home/webservice/api-stage/{releases,shared/storage/{app/public,framework/{cache,sessions,views},logs}}
sudo chown -R hskwon:webservice /home/webservice/api-stage
sudo chmod -R 2775 /home/webservice/api-stage
# 배포 절차는 API와 동일, DB는 sam_stage 사용
Nginx 설정 (stage-api.sam.it.kr):
# /etc/nginx/sites-available/stage-api.sam.it.kr
server {
listen 80;
server_name stage-api.sam.it.kr;
root /home/webservice/api-stage/current/public;
index index.php;
access_log /var/log/nginx/stage-api.sam.it.kr.access.log;
error_log /var/log/nginx/stage-api.sam.it.kr.error.log;
include /etc/nginx/snippets/security.conf;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.4-fpm-api-stage.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_read_timeout 60;
}
client_max_body_size 50M;
}
⑧ Laravel Admin 배포 (admin.codebridge-x.com) ✅ (2026-02-24)
sudo mkdir -p /home/webservice/mng/{releases,shared/storage/{app/public,framework/{cache,sessions,views},logs}}
sudo chown -R hskwon:webservice /home/webservice/mng
sudo chmod -R 2775 /home/webservice/mng
# 배포 절차는 API와 동일 (mng/ 저장소 기준)
Nginx 설정 (admin.codebridge-x.com):
# /etc/nginx/sites-available/admin.codebridge-x.com
server {
listen 80;
server_name admin.codebridge-x.com;
root /home/webservice/mng/current/public;
index index.php;
access_log /var/log/nginx/admin.codebridge-x.com.access.log;
error_log /var/log/nginx/admin.codebridge-x.com.error.log;
include /etc/nginx/snippets/security.conf;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.4-fpm-admin.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_read_timeout 60;
}
client_max_body_size 50M;
}
⑨ Sales 배포 (sales.codebridge-x.com) ✅ (2026-02-24)
# 디렉토리 (레거시이므로 단순 구조)
sudo mkdir -p /home/webservice/sales
sudo chown -R hskwon:webservice /home/webservice/sales
# 코드 배포
cd /home/webservice
git clone <repo_url> sales
# .env 설정
cp /home/webservice/sales/.env.example /home/webservice/sales/.env
# .env: DB_HOST=localhost, DB_NAME=codebridge, DB_USER=codebridge, DB_PASS=<비밀번호>
chmod 600 /home/webservice/sales/.env
chmod 775 /home/webservice/sales/uploads
참고: sales는 순수 PHP 앱. session.php → .env 로드 → lib/mydb.php DB 연결.
Nginx 설정 (sales.codebridge-x.com):
# /etc/nginx/sites-available/sales.codebridge-x.com
server {
listen 80;
server_name sales.codebridge-x.com;
root /home/webservice/sales;
index index.php index.html;
access_log /var/log/nginx/sales.codebridge-x.com.access.log;
error_log /var/log/nginx/sales.codebridge-x.com.error.log;
include /etc/nginx/snippets/security.conf;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.4-fpm-sales.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_read_timeout 60;
}
# uploads PHP 실행 차단 (보안)
location ~* /uploads/.*\.php$ {
deny all;
}
client_max_body_size 50M;
}
⑩ 랜딩페이지 (codebridge-x.com) ✅ (2026-02-24)
sudo mkdir -p /home/webservice/landing
sudo chown -R hskwon:webservice /home/webservice/landing
# 간단한 index.html 배치
Nginx 설정 (codebridge-x.com):
# /etc/nginx/sites-available/codebridge-x.com
server {
listen 80;
server_name codebridge-x.com www.codebridge-x.com;
root /home/webservice/landing;
index index.html;
access_log /var/log/nginx/codebridge-x.com.access.log;
error_log /var/log/nginx/codebridge-x.com.error.log;
location / {
try_files $uri $uri/ /index.html;
}
}
⑪ Node.js 22.17.1 + Next.js 15.5.12 + PM2 6.0.14 ✅ (2026-02-24)
# Node.js 22 LTS
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs
# PM2 설치
sudo npm install -g pm2
# 운영 디렉토리
sudo mkdir -p /home/webservice/react/{releases,shared}
sudo chown -R hskwon:webservice /home/webservice/react
# Stage 디렉토리
sudo mkdir -p /home/webservice/react-stage/{releases,shared}
sudo chown -R hskwon:webservice /home/webservice/react-stage
# 초기 배포 (개발서버에서 빌드된 소스 tar pipe 전송)
ssh sam-dev "cd /home/webservice && tar cf - --exclude='node_modules' react/" | \
ssh sam-prod "cd /home/webservice && tar xf -"
cd /home/webservice/react/current
npm install # 의존성 설치 (빌드 결과물은 이미 포함)
# ⚠️ 빌드는 CI/CD 서버(Jenkins)에서 수행 → 운영서버로 rsync
# 운영서버에서 직접 npm run build 하지 않음
PM2 설정:
// /home/webservice/ecosystem.config.js
module.exports = {
apps: [
{
// 운영: cluster 모드 (무중단 배포 지원)
name: 'sam-front',
cwd: '/home/webservice/react/current',
script: 'node_modules/next/dist/bin/next',
args: 'start -p 3000',
instances: 2,
exec_mode: 'cluster',
max_memory_restart: '300M',
env: {
NODE_ENV: 'production',
NODE_OPTIONS: '--max-old-space-size=256'
}
},
{
// Stage: fork 모드 (리소스 절약)
name: 'sam-front-stage',
cwd: '/home/webservice/react-stage/current',
script: 'node_modules/next/dist/bin/next',
args: 'start -p 3100',
instances: 1,
exec_mode: 'fork',
max_memory_restart: '200M',
env: {
NODE_ENV: 'production',
NODE_OPTIONS: '--max-old-space-size=128'
}
}
]
};
# PM2 시작 + 부팅 시 자동시작
cd /home/webservice
pm2 start ecosystem.config.js
pm2 save
pm2 startup
# 무중단 배포 (운영)
pm2 reload sam-front # cluster 모드에서 zero-downtime reload
Nginx 설정 (sam.it.kr):
# /etc/nginx/sites-available/sam.it.kr
upstream nextjs_prod {
server 127.0.0.1:3000;
keepalive 32;
}
server {
listen 80;
server_name sam.it.kr;
access_log /var/log/nginx/sam.it.kr.access.log;
error_log /var/log/nginx/sam.it.kr.error.log;
location /_next/static/ {
alias /home/webservice/react/current/.next/static/;
expires 365d;
add_header Cache-Control "public, immutable";
}
location / {
proxy_pass http://nextjs_prod;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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;
proxy_cache_bypass $http_upgrade;
}
}
Nginx 설정 (stage.sam.it.kr):
# /etc/nginx/sites-available/stage.sam.it.kr
upstream nextjs_stage {
server 127.0.0.1:3100;
}
server {
listen 80;
server_name stage.sam.it.kr;
access_log /var/log/nginx/stage.sam.it.kr.access.log;
error_log /var/log/nginx/stage.sam.it.kr.error.log;
location / {
proxy_pass http://nextjs_stage;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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;
proxy_cache_bypass $http_upgrade;
}
}
⑫ SSL 인증서 (Let's Encrypt) ✅ (2026-02-24)
# Nginx 사이트 활성화
sudo ln -s /etc/nginx/sites-available/sam.it.kr /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/api.sam.it.kr /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/admin.codebridge-x.com /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/sales.codebridge-x.com /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/codebridge-x.com /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/stage.sam.it.kr /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/stage-api.sam.it.kr /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
# SSL 인증서 발급 (전체 develop@codebridge-x.com)
sudo certbot --nginx -d sam.it.kr --email develop@codebridge-x.com
sudo certbot --nginx -d api.sam.it.kr --email develop@codebridge-x.com
sudo certbot --nginx -d stage.sam.it.kr --email develop@codebridge-x.com
sudo certbot --nginx -d stage-api.sam.it.kr --email develop@codebridge-x.com
sudo certbot --nginx -d admin.codebridge-x.com --email develop@codebridge-x.com
sudo certbot --nginx -d sales.codebridge-x.com --email develop@codebridge-x.com
sudo certbot --nginx -d codebridge-x.com -d www.codebridge-x.com --email develop@codebridge-x.com
# 자동 갱신 확인
sudo certbot renew --dry-run # 7/7 success
sudo systemctl status certbot.timer
인증서 현황 (만료일: 2026-05-24, 알림: develop@codebridge-x.com):
| 도메인 | 상태 |
|---|---|
| sam.it.kr | ✅ |
| api.sam.it.kr | ✅ |
| stage.sam.it.kr | ✅ |
| stage-api.sam.it.kr | ✅ |
| admin.codebridge-x.com | ✅ |
| sales.codebridge-x.com | ✅ |
| codebridge-x.com + www | ✅ |
인증서 현황 (만료일: 2026-05-24):
| 도메인 | 알림 이메일 |
|---|---|
| sam.it.kr | admin@codebridge-x.com |
| api.sam.it.kr | admin@codebridge-x.com |
| stage.sam.it.kr | admin@codebridge-x.com |
| stage-api.sam.it.kr | admin@codebridge-x.com |
| admin.codebridge-x.com | develop@codebridge-x.com |
| sales.codebridge-x.com | develop@codebridge-x.com |
| codebridge-x.com + www | develop@codebridge-x.com |
⑬ 모니터링 에이전트 (node_exporter 1.8.2) ✅ (2026-02-24)
# node_exporter 설치 (Prometheus 메트릭 수집)
cd /tmp
wget https://github.com/prometheus/node_exporter/releases/download/v1.8.2/node_exporter-1.8.2.linux-amd64.tar.gz
tar xzf node_exporter-1.8.2.linux-amd64.tar.gz
sudo mv node_exporter-1.8.2.linux-amd64/node_exporter /usr/local/bin/
rm -rf node_exporter-1.8.2*
# systemd 서비스 등록
sudo tee /etc/systemd/system/node_exporter.service > /dev/null << 'EOF'
[Unit]
Description=Node Exporter
After=network.target
[Service]
User=nobody
ExecStart=/usr/local/bin/node_exporter
Restart=always
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable node_exporter
sudo systemctl start node_exporter
# 확인: http://localhost:9100/metrics
curl -s localhost:9100/metrics | head -5
CI/CD 서버의 Prometheus가
211.117.60.189:9100을 스크래핑. UFW에서 CI/CD IP만 9100 포트 허용.
⑭ 최종 점검 ✅ (2026-02-24)
커널 리부트 완료 (6.8.0-41 → 6.8.0-100), 전체 서비스 자동 복구 확인
# 서비스 상태
sudo systemctl status nginx php8.4-fpm mysql redis-server supervisor node_exporter
pm2 status
# 방화벽
sudo ufw status verbose
# 메모리
free -h
# 디스크
df -h
# 포트
sudo ss -tlnp
# Redis
redis-cli ping
# SSL 인증서
sudo certbot certificates
4. 디렉토리 구조
/home/webservice/
├── api/ # Laravel API (운영)
│ ├── current → releases/20260223/
│ ├── releases/
│ │ └── 20260223/
│ └── shared/
│ ├── .env
│ └── storage/
│ ├── app/public/
│ ├── framework/{cache,sessions,views}/
│ └── logs/
│
├── api-stage/ # Laravel API (Stage)
│ ├── current → releases/...
│ ├── releases/
│ └── shared/ (.env, storage/)
│
├── mng/ # Laravel Admin
│ ├── current → releases/...
│ ├── releases/
│ └── shared/ (.env, storage/)
│
├── sales/ # Plain PHP (레거시)
│ ├── .env
│ ├── index.php
│ ├── lib/
│ ├── uploads/
│ └── ...
│
├── react/ # Next.js (운영)
│ ├── current → releases/...
│ ├── releases/
│ └── shared/ (.env.local)
│
├── react-stage/ # Next.js (Stage)
│ ├── current → releases/...
│ ├── releases/
│ └── shared/ (.env.local)
│
├── landing/ # 정적 랜딩페이지
│ └── index.html
│
└── ecosystem.config.js # PM2 설정 (운영 + Stage)
5. DB 마이그레이션 ✅ (2026-02-24)
SAM 메인 DB (sam)
# 개발서버에서 덤프
ssh sam-dev "mysqldump -u codebridge -p sam > /tmp/sam_dump.sql"
scp sam-dev:/tmp/sam_dump.sql /tmp/
# 운영서버로 전송 + 임포트
scp /tmp/sam_dump.sql sam-prod:/tmp/
ssh sam-prod "sudo mysql sam < /tmp/sam_dump.sql"
ssh sam-prod "rm /tmp/sam_dump.sql"
통계 DB (sam_stat)
ssh sam-dev "mysqldump -u codebridge -p sam_stat > /tmp/sam_stat_dump.sql"
scp sam-dev:/tmp/sam_stat_dump.sql /tmp/
scp /tmp/sam_stat_dump.sql sam-prod:/tmp/
ssh sam-prod "sudo mysql sam_stat < /tmp/sam_stat_dump.sql"
ssh sam-prod "rm /tmp/sam_stat_dump.sql"
참고: chandj DB는 운영서버로 이관하지 않음. Sales는 codebridge DB 사용.
6. 개발서버 현황 (참고)
| 항목 | 개발서버 | 운영서버 |
|---|---|---|
| OS | Ubuntu 24.04.2 | Ubuntu 24.04 (kernel 6.8.0-100) |
| CPU/RAM | 2C / 3.8GB (스왑 없음) | 2C / 8GB + 스왑 4GB |
| PHP | 8.4.15 (+ 5.6, 7.3) | 8.4.18 |
| MySQL | 8.0.45 | 8.4.8 |
| Node.js | 22.17.1 | 22.17.1 |
| Nginx | 1.24.0 | 1.24.0 |
| Redis | - | 7.0.15 (로컬, 512mb) |
| Composer | - | 2.9.5 |
| PHP-FPM | 단일 www pool | 4개 분리 (api/admin/sales/stage) |
| PM2 | fork ×1 (:3001) | 6.0.14 cluster ×2 (:3000) |
| Supervisor | - | queue worker ×2 (redis) |
| UFW | 비활성 | 활성 (22/80/443/9100/3306) |
| fail2ban | - | ✅ |
| 디렉토리 | /home/webservice/ | /home/webservice/ |
7. Claude Code SSH 원격 셋팅
완료 항목 ✅
- SSH 키 인증 (sam-prod, sam-cicd, sam-dev)
- sudo NOPASSWD (3개 서버 모두)
- ~/.ssh/config 등록 (sam-prod, sam-cicd, sam-dev)
- root SSH 차단 + 비밀번호 로그인 차단 (sam-prod, sam-cicd)
Claude Code 실행 방식
# 단일 명령
ssh sam-prod "sudo apt update"
# 파일 전송
scp local_file sam-prod:/remote/path/
# 설정 파일 생성 (tee + heredoc)
ssh sam-prod "sudo tee /etc/nginx/sites-available/sam.it.kr > /dev/null << 'EOF'
...설정 내용...
EOF"
제약사항
- 인터랙티브 명령 불가 (vim, mysql_secure_installation 등)
- 명령 타임아웃: 최대 10분
DEBIAN_FRONTEND=noninteractive사용
8. 보안 체크리스트
- SSH 키 인증만 허용 (비밀번호 로그인 비활성화)
- root SSH 로그인 비활성화
- UFW 방화벽 활성화
- MySQL root 원격 접근 차단 (auth_socket)
- MySQL 앱 사용자 최소 권한 (codebridge)
- .env 파일 권한 600 (api, admin, sales 모두)
- storage/ 디렉토리 권한 775
- Nginx security.conf 스니펫 적용
- PHP display_errors = Off (모든 pool)
- Laravel APP_DEBUG=false, APP_ENV=production
- Sales uploads/ PHP 실행 차단
- Certbot 자동 갱신 확인 (7/7 dry-run success)
- fail2ban 설치 (SSH 브루트포스 방지)
- Redis bind 127.0.0.1 (외부 접근 차단)
- node_exporter CI/CD IP만 허용 (UFW)
9. 모니터링 아키텍처
운영서버 CI/CD서버
┌─────────────────┐ ┌────────────────────┐
│ node_exporter │──(:9100)──→ │ Prometheus │
│ (CPU/RAM/Disk) │ │ (수집 + 알림) │
└─────────────────┘ └────────┬───────────┘
│
┌────────▼───────────┐
│ Grafana │
│ (대시보드 시각화) │
└────────────────────┘
| 서버 | 프로그램 | 역할 | 메모리 |
|---|---|---|---|
| 운영 | node_exporter | 메트릭 수집 (CPU/RAM/디스크/네트워크) | ~10MB |
| CI/CD | Prometheus | 메트릭 저장 + 알림 규칙 | ~200MB |
| CI/CD | Grafana | 대시보드 시각화 | ~100MB |
10. 향후 계획
- CI/CD 서버 셋팅 (Jenkins + Gitea + Prometheus + Grafana)
- 자동 배포 파이프라인 (Jenkins → 운영서버)
- Stage React 배포 (react-stage, PM2 fork :3100)
- API CORS/Sanctum 설정 (운영 도메인 반영)
- DB 복제 (Master → Slave)
- 개발서버 MySQL 8.0 → 8.4 업그레이드 (운영 안정화 후)
- Docker 기반 전환 검토
- 백업 자동화 (DB 일일 백업 → CI/CD 서버)
- React → Vercel 이전 검토