# 운영서버 셋팅 가이드 > 작성일: 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) ```bash # 시스템 업데이트 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`): ```ini [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 및 사용자:** ```sql -- 데이터베이스 (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) ```bash # 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) ```bash # 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`): ```nginx 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) ```bash # 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개):** ```ini # /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 ``` ```ini # /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 ``` ```ini # /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 ``` ```ini # /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 ``` ```bash # 기본 pool 제거, 분리된 pool 사용 sudo rm /etc/php/8.4/fpm/pool.d/www.conf sudo systemctl restart php8.4-fpm ``` ### ⑥ Supervisor ✅ (2026-02-24) ```bash 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 ```bash # 디렉토리 구조 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):** ```nginx # /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) ```bash # 디렉토리 (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):** ```nginx # /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) ```bash 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):** ```nginx # /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) ```bash # 디렉토리 (레거시이므로 단순 구조) sudo mkdir -p /home/webservice/sales sudo chown -R hskwon:webservice /home/webservice/sales # 코드 배포 cd /home/webservice git clone 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):** ```nginx # /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) ```bash sudo mkdir -p /home/webservice/landing sudo chown -R hskwon:webservice /home/webservice/landing # 간단한 index.html 배치 ``` **Nginx 설정 (codebridge-x.com):** ```nginx # /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) ```bash # 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 설정:** ```javascript // /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' } } ] }; ``` ```bash # 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):** ```nginx # /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):** ```nginx # /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) ```bash # 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) ```bash # 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), 전체 서비스 자동 복구 확인 ```bash # 서비스 상태 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) ```bash # 개발서버에서 덤프 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) ```bash 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 원격 셋팅 ### 완료 항목 ✅ - [x] SSH 키 인증 (sam-prod, sam-cicd, sam-dev) - [x] sudo NOPASSWD (3개 서버 모두) - [x] ~/.ssh/config 등록 (sam-prod, sam-cicd, sam-dev) - [x] root SSH 차단 + 비밀번호 로그인 차단 (sam-prod, sam-cicd) ### Claude Code 실행 방식 ```bash # 단일 명령 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. 보안 체크리스트 - [x] SSH 키 인증만 허용 (비밀번호 로그인 비활성화) - [x] root SSH 로그인 비활성화 - [x] UFW 방화벽 활성화 - [x] MySQL root 원격 접근 차단 (auth_socket) - [x] MySQL 앱 사용자 최소 권한 (codebridge) - [x] .env 파일 권한 600 (api, admin, sales 모두) - [x] storage/ 디렉토리 권한 775 - [x] Nginx security.conf 스니펫 적용 - [x] PHP display_errors = Off (모든 pool) - [x] Laravel APP_DEBUG=false, APP_ENV=production - [x] Sales uploads/ PHP 실행 차단 - [x] Certbot 자동 갱신 확인 (7/7 dry-run success) - [x] fail2ban 설치 (SSH 브루트포스 방지) - [x] Redis bind 127.0.0.1 (외부 접근 차단) - [x] 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 이전 검토