-
AWS 로드밸런서 리스너 및 규칙의 리스너에 443 포트추가 & ssl 인증서 발행AWS 2026. 4. 15. 14:36
AWS의 로드밸런서 리스너 및 규칙의 리스너에 443 포트를 추가하려고 하는데 ssl 인증서 발행을 해야 하나요?
443 포트(HTTPS) 리스너를 추가하려면 SSL 인증서가 필요합니다.
SSL 인증서 발급 방법
방법 1: AWS Certificate Manager (ACM) - 권장
가장 간단하고 무료입니다.
발급 절차:
- AWS Console → Certificate Manager → 인증서 요청
- 퍼블릭 인증서 선택
- 도메인 이름 입력 (예: example.com, *.example.com)
- 검증 방법 선택:
- DNS 검증 (권장) - Route 53 사용 시 자동 처리 가능
- 이메일 검증 - 도메인 관리자 이메일로 승인
- 검증 완료 후 ACM에서 인증서 ARN 발급
ALB 리스너에 연결:
- 443 리스너 추가 시 → Default SSL/TLS certificate 항목에서 ACM 인증서 선택
방법 2: 외부 인증서 가져오기 (Import)
Let's Encrypt 등 외부에서 발급한 인증서를 ACM에 import하는 방식입니다.
- 단, 자동 갱신이 안 되므로 수동 관리 필요
도메인이 Route 53에 있다면?
DNS 검증이 매우 간단합니다.
ACM 인증서 요청 → DNS 검증 선택 → "Route 53에서 레코드 생성" 버튼 클릭 (자동 처리) → 수 분 내 검증 완료
현재 상황에 따른 체크
상황해결책도메인 있음 + Route 53 ACM 발급 → DNS 자동 검증 도메인 있음 + 외부 DNS ACM 발급 → DNS CNAME 레코드 수동 추가 도메인 없음 (IP만) ACM 불가 → 자체 서명 인증서 (ALB는 미지원, NLB만 가능) 도메인 상황이 어떻게 되시나요?
구글애드센스에 판매자 정보 공개 상태에 비즈니스 도메인이 옛날 도메인으로 되어 있는데 현재 사용하고 있는 도메인으로 수정할 수 있을까?
2월 19일네, 수정 가능합니다.
Google AdSense → 계정 → 판매자 정보 에서 변경할 수 있습니다.
다만 몇 가지 주의사항이 있습니다.
도메인 소유권 인증 필요 — 새 도메인으로 변경 시 해당 도메인의 소유권을 구글에 인증해야 할 수 있습니다. Google Search Console에 이미 도메인이 등록되어 있다면 비교적 간단하게 처리됩니다.
변경 후 검토 시간 — 즉시 반영되지 않고 구글 검토 후 적용되는 경우가 있습니다.
ads.txt 확인 — 도메인 변경 후 새 도메인의 루트에 ads.txt 파일이 올바르게 설정되어 있는지 확인하세요. AdSense 퍼블리셔 ID가 포함되어 있어야 광고 수익에 문제가 없습니다.
혹시 변경 메뉴에서 수정이 안 되거나 잠겨 있는 상태인가요?
greever.io 도메인이 AWS 의 arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:loadbalancer/app/ALB-Audigo-API/51ddca8620e1a3f9 이 로드밸런서를 가리키고 있는지 확인하고 싶어. bash 터미널에서 상세한 절차 부탁해.
2월 19일터미널에서 단계별로 확인하는 방법입니다.
1. 도메인 DNS 확인
bash# greever.io 가 어떤 IP/CNAME 을 가리키는지 확인 nslookup greever.io # 또는 dig greever.io
2. ALB DNS 이름 확인
bashaws elbv2 describe-load-balancers \ --load-balancer-arns "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:loadbalancer/app/ALB-Audigo-API/51ddca8620e1a3f9" \ --region ap-northeast-2 \ --query "LoadBalancers[0].DNSName" \ --output text→ 출력 예시: ALB-Audigo-API-xxxxxxx.ap-northeast-2.elb.amazonaws.com
3. ALB DNS 이름의 IP 확인
bash# 위에서 나온 ALB DNS 이름으로 조회 nslookup ALB-Audigo-API-xxxxxxx.ap-northeast-2.elb.amazonaws.com
4. 비교
bash# greever.io IP dig +short greever.io # ALB DNS IP dig +short ALB-Audigo-API-xxxxxxx.ap-northeast-2.elb.amazonaws.comIP가 일치하면 → greever.io 가 해당 ALB를 가리키는 것
CNAME으로 연결된 경우 직접 확인:
bash# CNAME 체인 추적 dig greever.io CNAME # 결과에 elb.amazonaws.com 이 포함되어 있으면 정상
5. Route 53 사용 중이라면 (Alias 레코드)
ALB를 Route 53 Alias로 연결한 경우 IP가 아닌 CNAME 형태로 표시됩니다.
bashaws route53 list-hosted-zones --query "HostedZones[*].[Name,Id]" --output table # greever.io 호스팅 영역 ID 확인 후 aws route53 list-resource-record-sets \ --hosted-zone-id "Z이이이이이이이이이이" \ --query "ResourceRecordSets[?Name=='greever.io.']" \ --output jsonAlias 레코드의 DNSName 값이 ALB DNS 이름과 일치하면 연결된 것입니다.
ALB와 백엔드 사이는 보통 HTTP로 통신한다고 하는데 이유가 뭐지?
2월 23일좋은 질문입니다. 이유는 보안 경계(Security Boundary) 개념 때문입니다.
핵심 개념: SSL Termination (SSL 종료)
ALB가 클라이언트로부터 받은 HTTPS 요청을 복호화(decrypt) 한 후, 백엔드로는 평문 HTTP로 전달하는 방식입니다.
클라이언트 → [HTTPS/443] → ALB → [HTTP/8080] → EC2/컨테이너 암호화 통신 복호화 후 전달
HTTP 사용 이유
1. 성능 SSL 암호화/복호화는 CPU 비용이 높습니다. ALB에서 한 번만 처리하고 내부는 HTTP로 통신하면 백엔드 서버의 부담이 줄어듭니다.
2. 내부 네트워크는 신뢰 가능 ALB와 백엔드 EC2는 같은 VPC 내부 프라이빗 서브넷에 있습니다. 외부에서 직접 접근이 불가능하기 때문에 내부 통신은 굳이 암호화할 필요가 없다는 판단입니다.
3. 인증서 관리 단순화 HTTPS를 ALB에서만 종료하면 SSL 인증서를 ALB 한 곳에서만 관리하면 됩니다. 백엔드 서버마다 인증서를 설치/갱신할 필요가 없습니다.
4. 로드밸런싱 효율 ALB가 HTTP 헤더, 경로, 쿠키 등을 평문으로 읽어야 라우팅 규칙을 적용할 수 있습니다. 암호화된 상태에서는 내용을 볼 수 없어 세밀한 라우팅이 불가능합니다.
그럼 보안은 괜찮은가?
VPC Security Group으로 백엔드 EC2의 인바운드를 ALB에서 오는 트래픽만 허용하도록 설정하면 충분합니다.
EC2 Security Group 인바운드 규칙: - 포트 8080 (또는 3000) - 소스: ALB의 Security Group ID ← 이게 핵심이렇게 하면 ALB를 통하지 않은 직접 접근은 원천 차단됩니다.
예외: End-to-End 암호화가 필요한 경우
금융, 의료 등 컴플라이언스 요구사항이 있을 경우 ALB → 백엔드도 HTTPS로 구성하기도 합니다. 이 경우 백엔드 서버에도 인증서를 설치해야 합니다. 일반적인 서비스에서는 과도한 설정입니다.
aws route53을 통해 integree.org 도메인을 구매하고 ssl 인증서를 발급하고 arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:loadbalancer/app/ALB-Audigo-API/51ddca8620e1a3f9 로드밸런서에 연결했어. 이제 https://integree.org/api-docs/ 이 주소를 브라우저에 입력하면 swagger 사이트가 떠야 하는데 502 Bad Gateway 란 메시지가 브라우저에 나와. 뭐가 잘못됐지?
3월 3일502 Bad Gateway는 ALB는 요청을 받았지만 백엔드 서버가 응답을 못 하는 상태입니다. 단계별로 원인을 찾아봅시다.
1. ALB 타겟 그룹 상태 확인 (가장 먼저)
bash# 타겟 그룹 목록 확인 aws elbv2 describe-target-groups \ --load-balancer-arn "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:loadbalancer/app/ALB-Audigo-API/51ddca8620e1a3f9" \ --region ap-northeast-2 \ --query "TargetGroups[*].[TargetGroupName,TargetGroupArn,HealthCheckPath,Port]" \ --output tablebash# 타겟 헬스 상태 확인 (TargetGroupArn 은 위에서 나온 값으로 교체) aws elbv2 describe-target-health \ --target-group-arn "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:targetgroup/XXXX/XXXX" \ --region ap-northeast-2여기서 상태값 해석:
상태의미healthy 정상 → 다른 원인 탐색 unhealthy 백엔드 서버 문제 unused 타겟 그룹에 서버가 등록 안 됨 draining 서버가 제거 중
2. 타겟이 unhealthy 라면 — EC2 서버 점검
bash# EC2 서버에 직접 접속 후 앱 실행 상태 확인 pm2 status pm2 logs # 포트 리스닝 확인 (예: 3000, 8080) ss -tlnp | grep LISTENbash# 로컬에서 직접 curl 테스트 curl -v http://localhost:3000/api-docs/
3. ALB 리스너 규칙 확인
bash# 443 리스너 확인 aws elbv2 describe-listeners \ --load-balancer-arn "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:loadbalancer/app/ALB-Audigo-API/51ddca8620e1a3f9" \ --region ap-northeast-2 \ --query "Listeners[*].[ListenerArn,Port,Protocol]" \ --output tablebash# 리스너 규칙 확인 (ListenerArn 교체) aws elbv2 describe-rules \ --listener-arn "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:listener/app/ALB-Audigo-API/XXXX/XXXX" \ --region ap-northeast-2/api-docs/ 경로가 올바른 타겟 그룹으로 라우팅되는지 확인
4. Security Group 확인
bash# ALB Security Group 확인 aws elbv2 describe-load-balancers \ --load-balancer-arns "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:loadbalancer/app/ALB-Audigo-API/51ddca8620e1a3f9" \ --region ap-northeast-2 \ --query "LoadBalancers[0].SecurityGroups" \ --output textEC2 Security Group 인바운드에 ALB SG가 허용되어 있는지 콘솔에서 확인
- EC2 → 보안 그룹 → 인바운드 규칙
- 앱 포트(3000 or 8080)에 대해 ALB Security Group이 소스로 등록되어 있어야 함
5. ALB Access Log 확인 (빠른 원인 파악)
AWS Console → EC2 → Load Balancers → ALB-Audigo-API → Monitoring 탭 → Target response codes 확인
- 5xx 많으면 백엔드 앱 문제
- 0 응답이면 연결 자체가 안 되는 것 (SG 또는 포트 문제)
체크리스트 요약
□ 타겟 그룹 헬스체크 → healthy? □ EC2에서 앱이 실행 중인가? (pm2 status) □ 앱이 올바른 포트에서 리스닝 중인가? □ EC2 SG 인바운드에 ALB SG 허용되어 있는가? □ ALB 리스너 규칙에 /api-docs/ 경로 라우팅이 올바른가? □ 헬스체크 경로가 실제 존재하는 경로인가?타겟 헬스 상태 결과가 어떻게 나오는지 알려주시면 더 정확히 찾을 수 있습니다!
Files hidden in shared chats
이 코드를 어디에 입력하라는 거지?
3월 3일로컬 PC의 터미널(Terminal) 에 입력하면 됩니다.
사전 조건
AWS CLI가 설치되어 있고 인증이 설정되어 있어야 합니다.
bash# 설치 확인 aws --version # 인증 확인 (본인 계정 정보가 나오면 OK) aws sts get-caller-identity
터미널 여는 방법
Windows:
- Win + R → cmd 또는 powershell 입력
- 또는 WSL (Ubuntu 터미널)
Mac:
- Cmd + Space → Terminal 검색
AWS CLI 없다면 대안
AWS Console에서 직접 확인:
- AWS Console → EC2 → 좌측 메뉴 대상 그룹 (Target Groups)
- 해당 타겟 그룹 클릭
- Targets 탭 → 각 서버의 Health status 확인
콘솔이 더 직관적으로 볼 수 있으니 CLI가 번거로우시면 콘솔에서 확인하셔도 됩니다.
aws elbv2 describe-target-health \ --target-group-arn "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:targetgroup/dev-server-target-group/1c229fa408bbdc6e" \ --region ap-northeast-2 이렇게 입력하면 타겟이 unhealthy 로 나와. 하지만 pm2 status 로 확인하면 | id │ name │ mode │ ↺ │ status │ cpu │ memory │ ├────┼────────────────────┼──────────┼──────┼───────────┼──────────┼──────────┤ │ 4 │ audigo-back-ads-d… │ cluster │ 12 │ online │ 0% │ 155.9mb │ │ 0 │ audigo-back-dev │ cluster │ 34 │ online │ 0% │ 252.4mb │ │ 2 │ audigo-observer │ fork │ 33 │ online │ 0% │ 120.5mb │ │ 1 │ audigo-scheduler │ fork │ 33 │ online │ 0% │ 141.4mb | 이렇게 4개의 프로세스가 살아있어.
3월 3일앱은 살아있는데 unhealthy면 헬스체크 경로 문제일 가능성이 높습니다.
1. 헬스체크 경로 확인
bashaws elbv2 describe-target-groups \ --target-group-arns "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:targetgroup/dev-server-target-group/1c229fa408bbdc6e" \ --region ap-northeast-2 \ --query "TargetGroups[0].[HealthCheckPath,HealthCheckPort,HealthCheckProtocol,Matcher]" \ --output table
2. EC2에서 직접 헬스체크 경로 curl 테스트
헬스체크 경로가 예를 들어 /health 라면:
bash# EC2 서버 안에서 실행 curl -v http://localhost:3000/health # 또는 앱 포트에 맞게 curl -v http://localhost:8080/health200 응답이 와야 ALB가 healthy로 판단합니다.
3. 앱 포트 확인
bashss -tlnp | grep LISTENaudigo-back-dev 가 실제로 어떤 포트에서 리스닝하는지 확인 후, 타겟 그룹의 포트와 일치하는지 비교해보세요.
가장 흔한 원인 3가지
원인확인 방법헬스체크 경로가 404 반환 curl localhost:포트/헬스체크경로 타겟 그룹 포트 ≠ 앱 포트 ss -tlnp vs 타겟 그룹 포트 EC2 Security Group이 ALB를 막음 인바운드에 ALB SG 허용 여부 1번 curl 결과가 어떻게 나오는지 알려주시면 바로 원인 찾을 수 있습니다!
더 보기aws elbv2 describe-target-groups \ --target-group-arns "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:targetgroup/dev-server-target-group/1c229fa408bbdc6e" \ --region ap-northeast-2 \ --query "TargetGroups[0].[HealthCheckPath,HealthCheckPort,HealthCheckProtocol,Matcher]" \ --output table 이렇게 입력하면 ---------------------- |DescribeTargetGroups| +--------------------+ | /ping | | traffic-port | | HTTP | | HttpCode | +--------------------+ 이렇게 나와서 curl -v http://localhost:3003/ping EC2에서 직접 헬스체크하니 * Host localhost:3003 was resolved. * IPv6: ::1 * IPv4: 127.0.0.1 * Trying [::1]:3003... * Connected to localhost (::1) port 3003 > GET /ping HTTP/1.1 > Host: localhost:3003 > User-Agent: curl/8.5.0 > Accept: */* > < HTTP/1.1 403 Forbidden < Access-Control-Allow-Origin: * < RateLimit-Policy: 20;w=10 < RateLimit-Limit: 20 < RateLimit-Remaining: 19 < RateLimit-Reset: 10 < Access-Control-Allow-Credentials: true < Access-Control-Allow-Methods: GET, POST, PUT, DELETE < Access-Control-Allow-Headers: Content-Type, Authorization < Content-Type: text/plain; charset=utf-8 < Content-Length: 9 < ETag: W/"9-PatfYBLj4Um1qTm5zrukoLhNyPU" < Date: Tue, 03 Mar 2026 08:28:11 GMT < Connection: keep-alive < Keep-Alive: timeout=65
- Connection #0 to host localhost left intact 이런 결과가 나와.
3월 3일원인 찾았습니다. /ping 엔드포인트가 403 Forbidden을 반환하고 있어서 ALB가 unhealthy로 판단하는 겁니다.
ALB 헬스체크는 200 응답만 healthy로 인식합니다.
해결 방법 2가지
방법 1: ALB 헬스체크 허용 상태코드 변경 (빠름)
bashaws elbv2 modify-target-group \ --target-group-arn "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:targetgroup/dev-server-target-group/1c229fa408bbdc6e" \ --matcher HttpCode=200,403 \ --region ap-northeast-2403도 healthy로 인식하도록 허용합니다. 임시방편이지만 가장 빠릅니다.
방법 2: /ping 엔드포인트가 200 반환하도록 코드 수정 (권장)
/ping에 403이 발생하는 이유는 Rate Limiting 미들웨어가 헬스체크 경로에도 적용되고 있기 때문입니다.
Node.js 코드에서 /ping 경로를 rate limit 적용 전에 처리하도록 수정:
typescript// rate limit 미들웨어 등록 전에 /ping 먼저 처리 app.get('/ping', (req, res) => { res.status(200).send('pong'); }); // 이후 rate limit 미들웨어 등록 app.use(rateLimitMiddleware);
권장 순서
지금 당장은 방법 1로 빠르게 해결하고, 이후 방법 2로 코드를 수정한 뒤 방법 1을 원래대로 되돌리는 게 좋습니다.
방법 1 적용 후 헬스체크가 healthy로 바뀌는지 확인해보세요!
Parameter validation failed: Invalid type for parameter Matcher.HttpCode, value: ['200', '403'], type: <class 'list'>, valid types: <class 'str'> 란 메시지가 발생해.
3월 3일값을 문자열로 넘겨야 합니다.
bashaws elbv2 modify-target-group \ --target-group-arn "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:targetgroup/dev-server-target-group/1c229fa408bbdc6e" \ --matcher '{"HttpCode":"200,403"}' \ --region ap-northeast-2이렇게 실행 후 헬스체크 상태가 바뀌는지 확인:
bashaws elbv2 describe-target-health \ --target-group-arn "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:targetgroup/dev-server-target-group/1c229fa408bbdc6e" \ --region ap-northeast-2healthy로 바뀌면 브라우저에서 https://integree.org/api-docs/ 접속해보세요!
더 보기{ "TargetHealthDescriptions": [ { "Target": { "Id": "i-09fe7a516d412b0bd", "Port": 80 }, "HealthCheckPort": "80", "TargetHealth": { "State": "unhealthy", "Reason": "Target.Timeout", "Description": "Request timed out" }, "AdministrativeOverride": { "State": "no_override", "Reason": "AdministrativeOverride.NoOverride", "Description": "No override is currently active on target" } } ] } 헬스체크 메시지야. 왜 unhealthy 지? 참고로 로드밸런서는 ipv4, ipv6 듀얼타입이야.
3월 3일포트 80으로 헬스체크하는데 앱은 3003에서 실행 중이라서 타임아웃이 발생하는 겁니다.
원인 정리
ALB 헬스체크 → EC2:80 으로 요청 앱은 실제로 → 3003 포트에서 실행 결과 → 80 포트 아무도 안 받음 → Timeout → unhealthy
해결: 타겟 그룹 포트를 3003으로 수정
타겟 그룹의 포트는 등록된 타겟을 재등록해야 변경됩니다.
1. 기존 타겟 제거
bashaws elbv2 deregister-targets \ --target-group-arn "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:targetgroup/dev-server-target-group/1c229fa408bbdc6e" \ --targets Id=i-09fe7a516d412b0bd \ --region ap-northeast-22. 3003 포트로 재등록
bashaws elbv2 register-targets \ --target-group-arn "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:targetgroup/dev-server-target-group/1c229fa408bbdc6e" \ --targets Id=i-09fe7a516d412b0bd,Port=3003 \ --region ap-northeast-23. 헬스체크 포트도 수정
bashaws elbv2 modify-target-group \ --target-group-arn "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:targetgroup/dev-server-target-group/1c229fa408bbdc6e" \ --health-check-port "3003" \ --matcher '{"HttpCode":"200,403"}' \ --region ap-northeast-2
추가 확인: EC2 Security Group
혹시 EC2 Security Group 인바운드에 3003 포트가 열려 있는지도 확인하세요.
bashaws ec2 describe-security-groups \ --filters "Name=tag:Name,Values=*" \ --region ap-northeast-2 \ --query "SecurityGroups[*].[GroupId,GroupName,IpPermissions[*].[FromPort,ToPort]]" \ --output tableALB Security Group에서 EC2 3003 포트로 들어오는 트래픽이 허용되어 있어야 합니다.
고마워. https://integree.org/api-docs/ 여기 접속은 잘돼! 이번엔 3005 번 포트에 있는 was 에도 브라우저에서 접속할 수 있도록 만들고 싶어. 어떻게 하지?
3월 3일3005 포트 서버를 ALB에 연결하려면 도메인을 어떻게 구분할지 먼저 결정해야 합니다.
질문: 3005 서버 접근 방식을 어떻게 하실 건가요? 답변: 경로 분리 (예: integree.org/ads/)
3월 3일경로 분리 방식으로 진행하겠습니다. 절차는 다음과 같습니다.
전체 흐름
integree.org/api-docs/ → 타겟그룹 (3003) ← 기존 integree.org/ads/ → 새 타겟그룹 (3005) ← 추가할 것
1. 새 타겟 그룹 생성 (3005용)
bashaws elbv2 create-target-group \ --name ads-server-target-group \ --protocol HTTP \ --port 3005 \ --vpc-id vpc-XXXXXXXX \ --target-type instance \ --health-check-path /ping \ --health-check-protocol HTTP \ --matcher '{"HttpCode":"200,403"}' \ --region ap-northeast-2vpc-id는 아래로 확인:
bashaws ec2 describe-vpcs --region ap-northeast-2 \ --query "Vpcs[*].[VpcId,Tags[?Key=='Name'].Value]" \ --output table
2. EC2 타겟 등록
bashaws elbv2 register-targets \ --target-group-arn "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:targetgroup/ads-server-target-group/XXXX" \ --targets Id=i-09fe7a516d412b0bd,Port=3005 \ --region ap-northeast-2
3. ALB 443 리스너에 경로 규칙 추가
먼저 리스너 ARN 확인:
bashaws elbv2 describe-listeners \ --load-balancer-arn "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:loadbalancer/app/ALB-Audigo-API/51ddca8620e1a3f9" \ --region ap-northeast-2 \ --query "Listeners[?Port==\`443\`].ListenerArn" \ --output text규칙 추가:
bashaws elbv2 create-rule \ --listener-arn "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:listener/app/ALB-Audigo-API/XXXX/XXXX" \ --conditions '[{"Field":"path-pattern","Values":["/ads/*"]}]' \ --actions '[{"Type":"forward","TargetGroupArn":"arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:targetgroup/ads-server-target-group/XXXX"}]' \ --priority 10 \ --region ap-northeast-2
4. EC2 Security Group에 3005 포트 오픈
bash# EC2의 Security Group ID 확인 aws ec2 describe-instances \ --instance-ids i-09fe7a516d412b0bd \ --region ap-northeast-2 \ --query "Reservations[0].Instances[0].SecurityGroups" \ --output table # 3005 포트 인바운드 추가 (sg-XXXX는 EC2 SG ID, sg-YYYY는 ALB SG ID) aws ec2 authorize-security-group-ingress \ --group-id sg-XXXX \ --protocol tcp \ --port 3005 \ --source-group sg-YYYY \ --region ap-northeast-2
순서 요약
1. VPC ID 확인 2. 타겟 그룹 생성 3. EC2 타겟 등록 4. 리스너 ARN 확인 5. 리스너 규칙 추가 6. EC2 SG에 3005 포트 오픈1번 VPC ID부터 확인해서 순서대로 진행해보세요!
더 보기aws elbv2 create-target-group \ --name dev-ads-server-target-group \ --protocol HTTP \ --port 3005 \ --vpc-id vpc-008e6f096d2852ef5 \ --target-type instance \ --health-check-path /ping \ --health-check-protocol HTTP \ --matcher '{"HttpCode":"200,403"}' \ --region ap-northeast-2 An error occurred (ValidationError) when calling the CreateTargetGroup operation: Health check path 'C:/Program Files/Git/ping' must begin with a '/' character and can only contain printable ASCII characters, without spaces. 이 메시지가 나와.
3월 4일Windows Git Bash 환경에서 발생하는 경로 변환 문제입니다. /ping을 Git Bash가 Windows 경로로 변환해버리는 현상입니다.
해결 방법
방법 1: MSYS_NO_PATHCONV 환경변수 설정
bashMSYS_NO_PATHCONV=1 aws elbv2 create-target-group \ --name dev-ads-server-target-group \ --protocol HTTP \ --port 3005 \ --vpc-id vpc-008e6f096d2852ef5 \ --target-type instance \ --health-check-path /ping \ --health-check-protocol HTTP \ --matcher '{"HttpCode":"200,403"}' \ --region ap-northeast-2방법 2: PowerShell에서 실행
Git Bash 대신 PowerShell을 열어서 동일한 명령어 실행하면 경로 변환 문제가 없습니다.
방법 3: 경로를 따옴표로 감싸기
bashaws elbv2 create-target-group \ --name dev-ads-server-target-group \ --protocol HTTP \ --port 3005 \ --vpc-id vpc-008e6f096d2852ef5 \ --target-type instance \ --health-check-path "//ping" \ --health-check-protocol HTTP \ --matcher '{"HttpCode":"200,403"}' \ --region ap-northeast-2//ping 으로 입력하면 Git Bash가 경로 변환을 건너뜁니다.
방법 1이 가장 깔끔하니 먼저 시도해보세요!
Files hidden in shared chats
여기서 targets Id=i-09fe7a516d412b0bd 이 값은 도대체 어디서 가져온거지?
3월 4일EC2 인스턴스 ID입니다. 이전 대화에서 헬스체크 결과에 나왔던 값입니다.
json"Target": { "Id": "i-09fe7a516d412b0bd", ← 여기서 가져온 것 "Port": 80 }직접 확인하려면:
bash# EC2 인스턴스 목록 확인 aws ec2 describe-instances \ --region ap-northeast-2 \ --query "Reservations[*].Instances[*].[InstanceId,Tags[?Key=='Name'].Value|[0],State.Name]" \ --output table또는 AWS Console → EC2 → Instances 에서 인스턴스 ID 컬럼에서 확인할 수 있습니다.
aws ec2 describe-instances \ --instance-ids i-09fe7a516d412b0bd \ --region ap-northeast-2 \ --query "Reservations[0].Instances[0].SecurityGroups" \ --output table 이건 EC2 SG ID 를 확인한거라면 ALB SG ID 를 확인하는 명령어는 뭐지?
3월 4일bashaws elbv2 describe-load-balancers \ --load-balancer-arns "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:loadbalancer/app/ALB-Audigo-API/51ddca8620e1a3f9" \ --region ap-northeast-2 \ --query "LoadBalancers[0].SecurityGroups" \ --output tablesg-XXXX는 EC2 SG ID, sg-YYYY는 ALB SG ID 라고 했는데 이 2개의 값이 동일해!
3월 4일정상입니다. EC2와 ALB가 같은 Security Group을 공유하고 있는 설정이기 때문입니다.
이 경우 --source-group에 동일한 SG ID를 넣으면 됩니다.
bashaws ec2 authorize-security-group-ingress \ --group-id sg-XXXX \ --protocol tcp \ --port 3005 \ --source-group sg-XXXX \ --region ap-northeast-2다만 EC2와 ALB가 같은 SG를 쓰는 것은 보안상 권장하지 않습니다. ALB용 SG와 EC2용 SG를 분리하면 EC2로의 직접 접근을 차단하고 ALB를 통한 트래픽만 허용할 수 있습니다. 지금 당장은 위 명령어로 진행하셔도 되고, 나중에 SG 분리를 고려해보세요.
^(/ads/ping|/ads/api-docs(/.*)?|/api/ads/(common)(/.*)?|/test/(func(/.*)?|encrypt.*|decrypt.*|fcm(/.*)?|fcm/silent.*))$ 이 정규식에서 'fcm/silent.*' 이 부분을 제거하고 싶어. 어떻게 수정하지?
3월 4일^(/ads/ping|/ads/api-docs(/.*)?|/api/ads/(common)(/.*)?|/test/(func(/.*)?|encrypt.*|decrypt.*|fcm(/.*)?)$변경 내용:
- |fcm/silent.* 제거
- 마지막 ) 닫는 괄호 위치 조정
원래 구조와 비교:
before: |fcm(/.*)?|fcm/silent.*))$ after: |fcm(/.*)?)$fcm 도 제거할거야!
3월 4일^(/ads/ping|/ads/api-docs(/.*)?|/api/ads/(common)(/.*)?|/test/(func(/.*)?|encrypt.*|decrypt.*)$변경 내용:
- |fcm(/.*)? 제거
- |fcm/silent.* 제거
원래 구조와 비교:
before: |encrypt.*|decrypt.*|fcm(/.*)?|fcm/silent.*))$ after: |encrypt.*|decrypt.*)$정규식에서 or 부분이 어디지? fcm(/.*)?|fcm/silent.*) 이 부분에서 마지막 괄호의 의미는 뭐지?
3월 4일OR 부분
정규식에서 | 가 OR 역할을 합니다.
func(/.*)? encrypt.* decrypt.* fcm(/.*)? ← OR fcm/silent.* ← OR전체 구조로 보면:
/test/(func(/.*)?|encrypt.*|decrypt.*|fcm(/.*)?|fcm/silent.*) ↑ ↑ 이 괄호가 /test/ 뒤의 선택지 전체를 묶는 그룹
마지막 괄호의 의미
/test/(func(/.*)?|encrypt.*|decrypt.*|fcm(/.*)?|fcm/silent.*) ↑ /test/( 를 닫는 괄호즉 /test/ 뒤에 올 수 있는 모든 선택지를 하나의 그룹으로 묶는 닫는 괄호입니다.
그래서 fcm을 제거한 결과:
^(/ads/ping|/ads/api-docs(/.*)?|/api/ads/(common)(/.*)?|/test/(func(/.*)?|encrypt.*|decrypt.*)$/test/( 를 열었으니 decrypt.* 뒤에 ) 로 닫아줘야 올바른 정규식입니다.
그럼 왜 제일 처음엔 마지막에 괄호가 2개 였지? 틀린 표현식인가?
3월 4일아니요, 틀리지 않았습니다. 원래 정규식을 다시 보면:
^(/ads/ping|/ads/api-docs(/.*)?|/api/ads/(common)(/.*)?|/test/(func(/.*)?|encrypt.*|decrypt.*|fcm(/.*)?|fcm/silent.*))$닫는 괄호가 2개인 이유:
^( ← 1번 괄호 열기 - 전체를 묶는 그룹 /ads/ping |/ads/api-docs(/.*)? |/api/ads/(common)(/.*)? |/test/( ← 2번 괄호 열기 - /test/ 선택지 묶는 그룹 func(/.*)? |encrypt.* |decrypt.* |fcm(/.*)? |fcm/silent.* ) ← 2번 닫기 ) ← 1번 닫기 $즉 전체를 감싸는 바깥 괄호가 하나 더 있었던 겁니다. 그래서 ))가 맞는 표현이었습니다.
따라서 fcm 제거 후 올바른 정규식은:
^(/ads/ping|/ads/api-docs(/.*)?|/api/ads/(common)(/.*)?|/test/(func(/.*)?|encrypt.*|decrypt.*))$마지막 ))$ 로 끝나야 합니다. 제가 이전 답변에서 ) 를 하나 빠뜨렸네요, 죄송합니다!
? 의 의미는 뭐지?
3월 4일정규식에서 ?는 "앞의 것이 0개 또는 1개" 를 의미합니다. 즉 있어도 되고 없어도 된다는 뜻입니다.
예시로 설명
/ads/api-docs(/.*)?URL매칭 여부/ads/api-docs ✅ (뒤에 아무것도 없어도 됨) /ads/api-docs/swagger ✅ (뒤에 경로 있어도 됨) (/.*)? 의 의미:
- (.*) → 슬래시/ 로 시작하는 아무 문자열
- ? → 이 그룹 자체가 없어도 됨
현재 정규식에서 ? 위치들
/ads/api-docs(/.*)? → /ads/api-docs 또는 /ads/api-docs/뭐든 (common)(/.*)? → /common 또는 /common/뭐든 func(/.*)? → /func 또는 /func/뭐든 fcm(/.*)? → /fcm 또는 /fcm/뭐든모두 해당 경로 자체만 오거나, 하위 경로까지 허용할 때 사용하는 패턴입니다.
^(/ads/ping|/ads/api-docs(/.*)?|/api/ads/(common)(/.*)?|/test/(func(/.*)?|encrypt.*|decrypt.*))$ 이 정규식의 의미를 그럼 상세히 설명해줘.
3월 4일전체 구조
이 정규식은 허용할 URL 경로 목록을 정의한 것입니다.
^ → 문자열 시작 $ → 문자열 끝즉 URL 전체가 이 패턴과 완전히 일치해야 합니다.
각 경로별 설명
1. /ads/ping
/ads/ping- 정확히 /ads/ping 만 허용
- /ads/ping/anything 은 ❌
2. /ads/api-docs(/.*)?
/ads/api-docs /ads/api-docs/ /ads/api-docs/swagger /ads/api-docs/swagger/index.html- /ads/api-docs 자체 또는 하위 경로 모두 허용
3. /api/ads/(common)(/.*)?
/api/ads/common /api/ads/common/ /api/ads/common/list /api/ads/common/detail/123- /api/ads/common 자체 또는 하위 경로 모두 허용
4. /test/(func(/.*)?|encrypt.*|decrypt.*)
/test/ 뒤에 올 수 있는 선택지가 3가지입니다.
4-1. func(/.*)?
/test/func /test/func/ /test/func/anything4-2. encrypt.*
/test/encrypt /test/encrypt123 /test/encrypt/data /test/encryptABC- encrypt 로 시작하는 모든 것 허용
4-3. decrypt.*
/test/decrypt /test/decrypt123 /test/decrypt/data- decrypt 로 시작하는 모든 것 허용
한눈에 요약
경로설명/ads/ping 헬스체크 /ads/api-docs/** 광고 서버 Swagger /api/ads/common/** 광고 공통 API /test/func/** 테스트 - 기능 /test/encrypt* 테스트 - 암호화 /test/decrypt* 테스트 - 복호화 AWS EC2에 설치한 postgresql 에 접속시 FATAL: no pg_hba.conf entry for host "119.198.249.156", user "postgres", database "postgres", SSL encryption 이란 메시지가 나오는 이유는?
3월 5일원인
pg_hba.conf 파일에 해당 IP(119.198.249.156)에서의 접속 허용 규칙이 없기 때문입니다.
PostgreSQL은 접속 요청이 오면 pg_hba.conf 파일을 위에서부터 순서대로 읽어서 일치하는 규칙이 없으면 무조건 거부합니다.
pg_hba.conf 구조
# TYPE DATABASE USER ADDRESS METHOD host all all 127.0.0.1/32 md5 ← 로컬만 허용현재 119.198.249.156 에서 오는 접속을 허용하는 규칙이 없는 상태입니다.
해결 방법
EC2에 접속 후 pg_hba.conf 수정:
bash# pg_hba.conf 위치 확인 sudo -u postgres psql -c "SHOW hba_file;" # 파일 편집 sudo vi /etc/postgresql/17/main/pg_hba.conf아래 규칙 추가:
bash# 특정 IP만 허용 (권장) host postgres postgres 119.198.249.156/32 md5 # 또는 모든 IP 허용 (개발환경에서만) host all all 0.0.0.0/0 md5설정 적용:
bashsudo systemctl reload postgresql # 또는 sudo -u postgres psql -c "SELECT pg_reload_conf();"
추가 확인: EC2 Security Group
pg_hba.conf 수정과 함께 EC2 Security Group 인바운드에 5432 포트도 열려 있어야 합니다.
bashaws ec2 authorize-security-group-ingress \ --group-id sg-XXXX \ --protocol tcp \ --port 5432 \ --cidr 119.198.249.156/32 \ --region ap-northeast-2보안 주의 — 5432 포트를 외부에 직접 여는 것은 위험합니다. 운영환경이라면 SSH 터널링이나 AWS Systems Manager Session Manager를 통한 접속을 권장합니다.
elasticloadbalancing:DeregisterTargetselasticloadbalancing:RegisterTargetselasticloadbalancing:DescribeTargetHealth 이 권한을 arn:aws:iam::508153279153:user/hwimin 계정에 추가하고 싶어.
3월 9일bashaws iam put-user-policy \ --user-name hwimin \ --policy-name elb-target-management \ --policy-document '{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "elasticloadbalancing:DeregisterTargets", "elasticloadbalancing:RegisterTargets", "elasticloadbalancing:DescribeTargetHealth" ], "Resource": "*" } ] }' \ --region ap-northeast-2적용 확인:
bashaws iam get-user-policy \ --user-name hwimin \ --policy-name elb-target-management
참고 — put-user-policy는 인라인 정책으로 추가됩니다. 여러 사용자에게 동일한 권한을 부여할 일이 있다면 관리형 정책(Managed Policy) 으로 만들어서 attach하는 방식이 더 관리하기 편합니다.
ssm role: i-0399060e28e3fa091 ec2 세션매니저 role 이 현재 어떤 사용자나 ec2에 연결되어 있는지 확인하고 싶어.
3월 9일bash# EC2 인스턴스에 연결된 IAM 역할 확인 aws ec2 describe-instances \ --instance-ids i-0399060e28e3fa091 \ --region ap-northeast-2 \ --query "Reservations[0].Instances[0].IamInstanceProfile" \ --output json위 결과로 나온 Instance Profile ARN으로 실제 Role 확인:
bash# Instance Profile에 연결된 Role 확인 aws iam get-instance-profile \ --instance-profile-name 프로파일이름 \ --query "InstanceProfile.Roles[*].[RoleName,Arn]" \ --output table
해당 Role이 다른 EC2에도 연결되어 있는지 확인
bash# 모든 EC2의 Instance Profile 목록 확인 aws ec2 describe-instances \ --region ap-northeast-2 \ --query "Reservations[*].Instances[*].[InstanceId,Tags[?Key=='Name'].Value|[0],IamInstanceProfile.Arn]" \ --output table
Role이 IAM 사용자에게도 연결되어 있는지 확인
bash# Role 이름 확인 후 (위에서 나온 RoleName으로 교체) aws iam list-entities-for-policy \ --policy-arn arn:aws:iam::508153279153:policy/정책이름 \ --query "[PolicyGroups,PolicyUsers,PolicyRoles]" \ --output table첫 번째 명령어 결과의 Instance Profile 이름을 알려주시면 이어서 진행할 수 있습니다!
Files hidden in shared chats
프로파일 이름에 뭐 넣지?
3월 9일첫 번째 명령어 실행 결과에서 가져오면 됩니다.
bashaws ec2 describe-instances \ --instance-ids i-0399060e28e3fa091 \ --region ap-northeast-2 \ --query "Reservations[0].Instances[0].IamInstanceProfile" \ --output json결과가 이런 형태로 나옵니다:
json{ "Arn": "arn:aws:iam::508153279153:instance-profile/SSM-Role-EC2", "Id": "AIPAXXX..." }ARN에서 마지막 / 뒤의 값이 프로파일 이름입니다.
arn:aws:iam::508153279153:instance-profile/SSM-Role-EC2 ↑ SSM-Role-EC2 ← 이게 프로파일 이름먼저 위 명령어 실행 결과를 알려주시면 바로 다음 명령어를 완성해드릴게요!
Files hidden in shared chats
여기에는 어떤 값들이 파라미터에 들어가줘야 하지?
3월 9일이 명령어는 특정 값을 넣을 필요 없이 그대로 실행하면 됩니다.
bashaws ec2 describe-instances \ --region ap-northeast-2 \ --query "Reservations[*].Instances[*].[InstanceId,Tags[?Key=='Name'].Value|[0],IamInstanceProfile.Arn]" \ --output table--region ap-northeast-2 만 본인 리전에 맞으면 되고, 나머지는 모든 EC2 인스턴스를 조회하는 명령어라 별도 파라미터가 없습니다.
실행하면 이런 형태로 나옵니다:
---------------------------------------------------------------------------------- | DescribeInstances | +----------------------+------------------+--------------------------------------+ | i-09fe7a516d412b0bd | audigo-dev | arn:aws:iam::508153279153:instance-profile/SSM-Role-EC2 | | i-0399060e28e3fa091 | audigo-prod | arn:aws:iam::508153279153:instance-profile/SSM-Role-EC2 | +----------------------+------------------+--------------------------------------+여기서 각 EC2에 어떤 Instance Profile(Role)이 연결되어 있는지 한눈에 확인할 수 있습니다.
aws iam list-entities-for-policy \ --policy-arn arn:aws:iam::508153279153:policy/EC2-Session-Manager-Role \ --query "[PolicyGroups,PolicyUsers,PolicyRoles]" \ --output table 이라고 하면 An error occurred (NoSuchEntity) when calling the ListEntitiesForPolicy operation: Policy arn:aws:iam::508153279153:policy/EC2-Session-Manager-Role does not exist or is not attachable. 라고 나와.
3월 9일정책 이름이 다를 수 있습니다. 먼저 실제 존재하는 정책 목록을 확인해보세요.
bash# 계정의 모든 커스텀 정책 목록 확인 aws iam list-policies \ --scope Local \ --query "Policies[*].[PolicyName,Arn]" \ --output table또는 Role 이름으로 직접 접근하는 방법:
bash# EC2에 연결된 Role 이름 확인 aws iam get-instance-profile \ --instance-profile-name EC2-Session-Manager-Role \ --query "InstanceProfile.Roles[*].[RoleName,Arn]" \ --output tablebash# Role에 연결된 정책 확인 aws iam list-attached-role-policies \ --role-name 위에서나온RoleName \ --output tablebash# 해당 Role이 어떤 EC2에 연결되어 있는지 확인 aws ec2 describe-instances \ --region ap-northeast-2 \ --query "Reservations[*].Instances[*].[InstanceId,Tags[?Key=='Name'].Value|[0],IamInstanceProfile.Arn]" \ --output tablelist-policies 결과에서 Session Manager 관련 정책 이름을 찾아서 알려주시면 이어서 진행할게요!
해당 Role이 어떤 EC2에 연결되어 있는지 확인은 했고 어떤 IAM 유저에게 있는지 확인하고 싶어.
3월 9일Role은 IAM 유저에게 직접 연결되지 않고 assume 방식으로 사용됩니다. 하지만 어떤 유저가 이 Role을 사용할 수 있는지 확인하려면:
bash# Role의 Trust Policy 확인 (누가 이 Role을 assume할 수 있는지) aws iam get-role \ --role-name EC2-Session-Manager-Role \ --query "Role.AssumeRolePolicyDocument" \ --output jsonbash# Role에 연결된 모든 정책 확인 aws iam list-attached-role-policies \ --role-name EC2-Session-Manager-Role \ --output tablebash# 인라인 정책도 확인 aws iam list-role-policies \ --role-name EC2-Session-Manager-Role \ --output table
IAM 유저별로 어떤 Role을 assume할 수 있는지 확인
bash# 전체 유저 목록 aws iam list-users \ --query "Users[*].UserName" \ --output table # 특정 유저의 권한 확인 aws iam list-attached-user-policies \ --user-name 유저이름 \ --output table aws iam list-user-policies \ --user-name 유저이름 \ --output tableEC2 Session Manager Role의 경우 일반적으로 IAM 유저가 아닌 EC2 서비스 자체에만 연결되는 Role이라 유저에게는 없을 가능성이 높습니다. get-role 결과를 보여주시면 정확히 확인할 수 있습니다!
Files hidden in shared chats
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "ec2.amazonaws.com" }, "Action": "sts:AssumeRole" } ] } 결과가 이렇게 나오면 EC2만 사용권한이 있다는 말이야?
3월 9일네, 맞습니다.
"Principal": { "Service": "ec2.amazonaws.com" } 의 의미는 EC2 서비스만 이 Role을 assume할 수 있다는 뜻입니다.
정리
Principal 값의미ec2.amazonaws.com EC2 인스턴스만 사용 가능 lambda.amazonaws.com Lambda만 사용 가능 arn:aws:iam::508153279153:user/hwimin 특정 IAM 유저가 사용 가능 arn:aws:iam::508153279153:root 해당 계정 전체 사용 가능
현재 상황은 EC2-Session-Manager-Role 은 EC2 인스턴스에만 부여된 Role이고, IAM 유저는 이 Role을 직접 사용할 수 없습니다.
특정 IAM 유저도 Session Manager로 EC2에 접속하게 하려면 유저에게 별도로 ssm:StartSession 권한을 부여해야 합니다.
EC2-Session-Manager-Role 에서 elasticloadbalancing:DeregisterTargets elasticloadbalancing:RegisterTargets elasticloadbalancing:DescribeTargetHealth 이 권한들을 사용할 수 있도록 수정하고 싶어.
3월 9일bashaws iam attach-role-policy \ --role-name EC2-Session-Manager-Role \ --policy-arn arn:aws:iam::508153279153:policy/elb-target-management \ --region ap-northeast-2앞서 hwimin 유저에게 만들었던 elb-target-management 인라인 정책과 동일한 권한이라 해당 정책을 재사용하면 됩니다.
단, elb-target-management가 인라인 정책으로 만들어졌다면 attach가 안 됩니다. 이 경우 관리형 정책으로 새로 생성:
bash# 관리형 정책 생성 aws iam create-policy \ --policy-name elb-target-management \ --policy-document '{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "elasticloadbalancing:DeregisterTargets", "elasticloadbalancing:RegisterTargets", "elasticloadbalancing:DescribeTargetHealth" ], "Resource": "*" } ] }' # Role에 연결 aws iam attach-role-policy \ --role-name EC2-Session-Manager-Role \ --policy-arn arn:aws:iam::508153279153:policy/elb-target-management적용 확인:
bashaws iam list-attached-role-policies \ --role-name EC2-Session-Manager-Role \ --output tableFiles hidden in shared chats
이걸 어떻게 확인할 수 있지? 그리고 왜 attach 가 안되는지 상세한 설명이 필요해.
3월 9일인라인 정책 vs 관리형 정책 확인
bash# hwimin 유저의 인라인 정책 확인 aws iam list-user-policies \ --user-name hwimin \ --output table # hwimin 유저의 관리형 정책 확인 aws iam list-attached-user-policies \ --user-name hwimin \ --output table
왜 인라인 정책은 attach가 안 되는가?
AWS IAM 정책에는 2가지 종류가 있습니다.
관리형 정책 (Managed Policy)
IAM → Policies 메뉴에 독립적으로 존재 ↓ attach/detach 가능 유저A ←─┤ 유저B ←─┤ 여러 대상에 재사용 가능 Role ←─┘- ARN이 존재 (arn:aws:iam::508153279153:policy/elb-target-management)
- 여러 유저/Role에 attach 가능
- aws iam attach-role-policy 명령어 사용 가능
인라인 정책 (Inline Policy)
유저A 안에 정책이 내장됨 → 유저A 전용, 다른 곳에 재사용 불가 유저B 안에 정책이 내장됨 → 유저B 전용 Role 안에 정책이 내장됨 → 해당 Role 전용- ARN이 없음 (독립적으로 존재하지 않음)
- 특정 대상 안에 종속되어 있어서 다른 곳에 attach 불가
- aws iam put-user-policy 명령어로 생성된 것
앞서 hwimin 유저에게 권한 추가할 때 put-user-policy 를 사용했기 때문에 인라인 정책으로 만들어진 상태입니다. 따라서 Role에 바로 attach는 안 되고 관리형 정책을 새로 생성해서 attach해야 합니다.
arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:loadbalancer/app/audigo-server-ALB/b8c642e2308258f7 이 로드밸런서를 사용중인데 현재 80, 443 포트에 리스너가 있어. was 서버가 스스로 127.0.0.1/ping 이 주소로 헬스체크를 하려면 어느 부분이 수정되어야 하지?
3월 9일WAS 서버가 스스로 헬스체크를 한다는 것은 ALB와 무관하게 앱 내부에서 자체 헬스체크를 하겠다는 의미입니다.
수정이 필요한 부분
1. Node.js 앱 코드 (헬스체크 엔드포인트 확인)
/ping 엔드포인트가 존재하고 200 을 반환하는지 확인:
typescript// 예시 app.get('/ping', (req, res) => { res.status(200).send('pong'); });
2. 자체 헬스체크 로직 추가
앱 내부에서 주기적으로 자기 자신에게 요청을 보내는 코드:
typescriptimport axios from 'axios'; const selfHealthCheck = async () => { try { const res = await axios.get('http://127.0.0.1:3005/ping'); if (res.status === 200) { console.log('Health check OK'); } } catch (err) { console.error('Health check FAILED', err.message); // 실패 시 처리 로직 (알림, 재시작 등) } }; // 30초마다 헬스체크 setInterval(selfHealthCheck, 30000);
3. PM2 헬스체크 활용 (권장)
코드 수정 없이 ecosystem.config.js 에서 설정 가능:
javascriptmodule.exports = { apps: [{ name: 'audigo-back-dev', script: './dist/main.js', // 헬스체크 URL 설정 health_check_url: 'http://127.0.0.1:3005/ping', health_check_grace_period: 3000, // 시작 후 3초 후부터 체크 }] };
ALB 헬스체크와의 차이
ALB 헬스체크자체 헬스체크주체 ALB가 EC2에 요청 앱이 자기 자신에게 요청 목적 트래픽 라우팅 결정 앱 내부 상태 모니터링 실패 시 ALB가 해당 EC2로 트래픽 차단 앱이 직접 대응 (재시작, 알림 등) 어떤 목적으로 자체 헬스체크를 하려는 건지 알려주시면 더 구체적으로 도움드릴 수 있습니다!
arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:loadbalancer/app/audigo-server-ALB/b8c642e2308258f7 로드밸런서에서 리소스맵 확인 시 Health checks failed 란 메시지를 보는데 health check 를 정상으로 만들고 싶어.
3월 9일단계별로 원인을 찾아보겠습니다.
1. 타겟 그룹 및 헬스체크 상태 확인
bash# 타겟 그룹 목록 확인 aws elbv2 describe-target-groups \ --load-balancer-arn "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:loadbalancer/app/audigo-server-ALB/b8c642e2308258f7" \ --region ap-northeast-2 \ --query "TargetGroups[*].[TargetGroupName,TargetGroupArn,HealthCheckPath,HealthCheckPort,Matcher.HttpCode]" \ --output tablebash# 타겟 헬스 상태 확인 (위에서 나온 ARN으로 교체) aws elbv2 describe-target-health \ --target-group-arn "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:targetgroup/XXXX/XXXX" \ --region ap-northeast-2
결과에서 State 값을 확인 후 알려주시면 정확한 원인을 찾을 수 있습니다.
State의미Target.Timeout 포트 불일치 또는 SG 차단 Target.FailedHealthChecks 앱이 헬스체크 경로에 200 미반환 Target.NotRegistered 타겟 그룹에 EC2 미등록 Elb.InternalError ALB 내부 문제 더 보기{ "TargetHealthDescriptions": [ { "Target": { "Id": "i-0cc3535cc3a2b2849", "Port": 3003 }, "HealthCheckPort": "3003", "TargetHealth": { "State": "unhealthy", "Reason": "Target.FailedHealthChecks", "Description": "Health checks failed" }, "AdministrativeOverride": { "State": "no_override", "Reason": "AdministrativeOverride.NoOverride", "Description": "No override is currently active on target" } } ] }
3월 9일Target.FailedHealthChecks 는 포트는 연결되는데 헬스체크 경로가 200을 반환하지 않는 상태입니다.
EC2에서 직접 확인
bash# 헬스체크 경로 확인 curl -v http://127.0.0.1:3003/ping결과를 보여주시면 바로 원인을 찾을 수 있습니다.
예상 가능한 원인:
응답 코드원인403 Rate limit 미들웨어가 /ping 에 적용됨 404 /ping 엔드포인트가 없음 500 앱 내부 에러 연결 자체 안됨 앱이 3003 포트에서 실행 안 됨 더 보기- Trying 127.0.0.1:3003...
- Connected to 127.0.0.1 (127.0.0.1) port 3003 > GET /ping HTTP/1.1 > Host: 127.0.0.1:3003 > User-Agent: curl/8.5.0 > Accept: */* > < HTTP/1.1 200 OK < Access-Control-Allow-Origin: * < Content-Security-Policy: default-src 'self';base-uri 'self';font-src 'self' https: data:;form-action 'self';frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests < Cross-Origin-Opener-Policy: same-origin < Cross-Origin-Resource-Policy: same-origin < Origin-Agent-Cluster: ?1 < Referrer-Policy: no-referrer < Strict-Transport-Security: max-age=31536000; includeSubDomains < X-Content-Type-Options: nosniff < X-DNS-Prefetch-Control: off < X-Download-Options: noopen < X-Frame-Options: SAMEORIGIN < X-Permitted-Cross-Domain-Policies: none < X-XSS-Protection: 0 < RateLimit-Policy: 20;w=10 < RateLimit-Limit: 20 < RateLimit-Remaining: 19 < RateLimit-Reset: 10 < Access-Control-Allow-Credentials: true < Access-Control-Allow-Methods: GET, POST, PUT, DELETE < Access-Control-Allow-Headers: Content-Type, Authorization < Content-Type: application/json; charset=utf-8 < Content-Length: 16 < ETag: W/"10-MxB4y4MLcx6QDsp8b8vgp7iFMFo" < Date: Mon, 09 Mar 2026 05:55:32 GMT < Connection: keep-alive < Keep-Alive: timeout=65
- Connection #0 to host 127.0.0.1 left intact
3월 9일로컬에서는 200 OK가 정상적으로 나오고 있습니다. 그렇다면 ALB가 헬스체크할 때는 다른 경로나 설정으로 요청하고 있을 가능성이 높습니다.
타겟 그룹 헬스체크 설정 상세 확인
bashaws elbv2 describe-target-groups \ --load-balancer-arn "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:loadbalancer/app/audigo-server-ALB/b8c642e2308258f7" \ --region ap-northeast-2 \ --query "TargetGroups[*].[TargetGroupName,HealthCheckPath,HealthCheckPort,HealthCheckProtocol,Matcher.HttpCode,HealthCheckIntervalSeconds,HealthCheckTimeoutSeconds,HealthyThresholdCount,UnhealthyThresholdCount]" \ --output table
확인 포인트:
항목정상값HealthCheckPath /ping HealthCheckPort 3003 또는 traffic-port HealthCheckProtocol HTTP Matcher.HttpCode 200 또는 200,403 결과 보여주시면 바로 원인 찾겠습니다!
---------------------------------------------------------------------------------------------- | DescribeTargetGroups | +-----------------------------+--------+---------------+--------+------+-----+----+----+-----+ | audigo-server-target-group | /ping | traffic-port | HTTPS | 200 | 30 | 5 | 5 | 2 | +-----------------------------+--------+---------------+--------+------+-----+----+----+-----+
3월 9일원인 찾았습니다. 헬스체크 프로토콜이 HTTPS 로 설정되어 있는데 앱은 HTTP로 응답하고 있어서 실패하는 겁니다.
해결: 헬스체크 프로토콜을 HTTP로 변경
bashaws elbv2 modify-target-group \ --target-group-arn "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:targetgroup/audigo-server-target-group/XXXX" \ --health-check-protocol HTTP \ --health-check-path /ping \ --matcher '{"HttpCode":"200"}' \ --region ap-northeast-2타겟 그룹 ARN 확인:
bashaws elbv2 describe-target-groups \ --load-balancer-arn "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:loadbalancer/app/audigo-server-ALB/b8c642e2308258f7" \ --region ap-northeast-2 \ --query "TargetGroups[*].[TargetGroupName,TargetGroupArn]" \ --output tableARN 확인 후 modify 명령어 실행하고 헬스체크 상태 재확인:
bashaws elbv2 describe-target-health \ --target-group-arn "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:targetgroup/audigo-server-target-group/XXXX" \ --region ap-northeast-2healthy 로 바뀌면 해결된 겁니다!
Files hidden in shared chats
https 를 사용하면 안되는 거야?
3월 9일HTTPS 헬스체크를 사용할 수 있습니다. 단 조건이 있습니다.
HTTPS 헬스체크가 동작하려면
백엔드 앱 자체가 HTTPS를 처리할 수 있어야 합니다.
즉 EC2의 Node.js 앱이 SSL 인증서를 가지고 있고 HTTPS로 서비스해야 합니다.
현재 일반적인 구조
클라이언트 → HTTPS → ALB → HTTP → EC2 Node.js 앱 ↑ 여기서 SSL 종료 (SSL Termination)ALB가 HTTPS를 처리하고 백엔드로는 HTTP로 전달하는 구조입니다. 이 경우 헬스체크도 HTTP로 해야 합니다.
HTTPS 헬스체크를 쓰는 구조
클라이언트 → HTTPS → ALB → HTTPS → EC2 Node.js 앱 ↑ 앱에 SSL 인증서 설치 필요 (End-to-End 암호화)이 경우 Node.js 앱에 인증서를 직접 설치하고 HTTPS 서버로 실행해야 합니다.
결론
구조헬스체크 프로토콜ALB에서 SSL 종료 (일반적) HTTP 앱까지 HTTPS (End-to-End) HTTPS 현재 Node.js 앱이 HTTP로 실행 중이므로 HTTP로 변경하는 것이 맞습니다.
Files hidden in shared chats
An error occurred (ValidationError) when calling the ModifyTargetGroup operation: Health check path 'C:/Program Files/Git/ping' must begin with a '/' character and can only contain printable ASCII characters, without spaces. 란 오류가 발생해.
3월 9일아까와 동일한 Git Bash 경로 변환 문제입니다. MSYS_NO_PATHCONV=1 을 앞에 붙여서 실행하세요.
bashMSYS_NO_PATHCONV=1 aws elbv2 modify-target-group \ --target-group-arn "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:targetgroup/audigo-server-target-group/XXXX" \ --health-check-protocol HTTP \ --health-check-path /ping \ --matcher '{"HttpCode":"200"}' \ --region ap-northeast-2Files hidden in shared chats
더 보기MSYS_NO_PATHCONV=1 aws elbv2 modify-target-group \ --target-group-arn "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:targetgroup/audigo-server-target-group/0e5e4fd868d78566" \ --health-check-protocol HTTP \ --health-check-path /ping \ --matcher '{"HttpCode":"200"}' \ --region ap-northeast-2 입력하면 { "TargetGroups": [ { "TargetGroupArn": "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:targetgroup/audigo-server-target-group/0e5e4fd868d78566", "TargetGroupName": "audigo-server-target-group", "Protocol": "HTTP", "Port": 3003, "VpcId": "vpc-07111d75408fe8006", "HealthCheckProtocol": "HTTP", "HealthCheckPort": "traffic-port", "HealthCheckEnabled": true, "HealthCheckIntervalSeconds": 30, "HealthCheckTimeoutSeconds": 5, "HealthyThresholdCount": 5, "UnhealthyThresholdCount": 2, "HealthCheckPath": "/ping", "Matcher": { "HttpCode": "200" }, "LoadBalancerArns": [ "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:loadbalancer/app/audigo-server-ALB/b8c642e2308258f7" ], "TargetType": "instance", "ProtocolVersion": "HTTP1", "IpAddressType": "ipv4" } ] } 이런 결과가 나오는데 상태확인을 하면 아직 unhealthy 로 나와. 시간이 좀 걸리는 거야?
3월 9일네, 시간이 걸립니다. 현재 설정상 최대 2분 30초 소요됩니다.
HealthCheckIntervalSeconds: 30초마다 체크 HealthyThresholdCount: 5회 연속 성공해야 healthy → 30초 × 5회 = 최대 150초 (2분 30초)잠시 기다린 후 아래 명령어로 재확인하세요:
bashaws elbv2 describe-target-health \ --target-group-arn "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:targetgroup/audigo-server-target-group/0e5e4fd868d78566" \ --region ap-northeast-2빠르게 확인하고 싶다면 헬스체크 간격을 줄일 수 있습니다:
bashaws elbv2 modify-target-group \ --target-group-arn "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:targetgroup/audigo-server-target-group/0e5e4fd868d78566" \ --health-check-interval-seconds 10 \ --healthy-threshold-count 2 \ --region ap-northeast-2이렇게 하면 20초 안에 결과를 확인할 수 있습니다. 확인 후 다시 원래 값으로 되돌리셔도 됩니다.
aws 계정 중 sts:AssumeRoleWithWebIdentity 이 role 을 가진 계정의 arn을 찾고 싶어.
3월 11일bash# 모든 Role 목록 조회 후 AssumeRolePolicyDocument 에서 sts:AssumeRoleWithWebIdentity 찾기 aws iam list-roles \ --query "Roles[?AssumeRolePolicyDocument.Statement[?Action=='sts:AssumeRoleWithWebIdentity']].{RoleName:RoleName,Arn:Arn}" \ --output table결과가 안 나오면 아래 방법으로 전체 확인:
bash# 모든 Role의 Trust Policy 를 JSON으로 출력 후 grep aws iam list-roles \ --query "Roles[*].[RoleName,AssumeRolePolicyDocument]" \ --output json | grep -B5 "AssumeRoleWithWebIdentity"또는 Role 이름까지 함께 보고 싶다면:
bashaws iam list-roles --output json | \ python3 -c " import json, sys roles = json.load(sys.stdin)['Roles'] for r in roles: for s in r['AssumeRolePolicyDocument']['Statement']: if 'AssumeRoleWithWebIdentity' in str(s.get('Action','')): print(r['RoleName'], r['Arn']) "결과에서 나오는 ARN이 sts:AssumeRoleWithWebIdentity 를 허용하는 Role들입니다.
Files hidden in shared chats
{ "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::508153279153:oidc-provider/token.actions.githubusercontent.com" }, "Action": "sts:AssumeRoleWithWebIdentity", 이렇게 나오면 누가 가진 role 인지 어떻게 알지?
3월 11일grep 결과에 Role 이름이 안 보이니 앞쪽 컨텍스트를 더 출력해야 합니다.
bashaws iam list-roles --output json | \ grep -B20 "AssumeRoleWithWebIdentity" | grep "RoleName"또는 python으로 Role 이름과 ARN까지 한번에:
bashaws iam list-roles --output json | \ python3 -c " import json, sys roles = json.load(sys.stdin)['Roles'] for r in roles: for s in r['AssumeRolePolicyDocument']['Statement']: if 'AssumeRoleWithWebIdentity' in str(s.get('Action','')): print('RoleName:', r['RoleName']) print('RoleArn :', r['Arn']) print('Principal:', s.get('Principal')) print() "결과에서 RoleName 을 확인한 후 해당 Role에 어떤 정책이 붙어있는지 확인:
bashaws iam list-attached-role-policies \ --role-name 위에서나온RoleName \ --output tableoidc-provider/token.actions.githubusercontent.com 이 Federated Principal인걸 보면 GitHub Actions CI/CD 파이프라인에서 사용하는 Role일 가능성이 높습니다.
Files hidden in shared chats
"RoleName": "GithubAction", 이라고 나와
3월 11일GithubAction Role이 GitHub Actions CI/CD에서 AWS 리소스에 접근할 때 사용하는 Role입니다.
어떤 권한을 가지고 있는지 확인해보세요:
bash# 연결된 관리형 정책 확인 aws iam list-attached-role-policies \ --role-name GithubAction \ --output table # 인라인 정책 확인 aws iam list-role-policies \ --role-name GithubAction \ --output tablebash# Trust Policy 상세 확인 (어떤 GitHub 레포/브랜치에서 사용 가능한지) aws iam get-role \ --role-name GithubAction \ --query "Role.AssumeRolePolicyDocument" \ --output jsonTrust Policy 결과에서 Condition 부분을 보면 어떤 GitHub 레포지토리에서 이 Role을 사용할 수 있는지 확인할 수 있습니다. 예를 들어:
json"Condition": { "StringLike": { "token.actions.githubusercontent.com:sub": "repo:greever-io/audigo:*" } }이런 식으로 특정 레포로 제한되어 있을 겁니다.
특정 레포로 제한되어 있는 곳에 특정 레포를 추가하고 싶어.
3월 11일먼저 현재 Trust Policy를 확인하고 수정합니다.
1. 현재 Trust Policy 확인
bashaws iam get-role \ --role-name GithubAction \ --query "Role.AssumeRolePolicyDocument" \ --output json결과를 보여주시면 현재 어떤 레포가 등록되어 있는지 확인 후 추가할 레포를 넣은 명령어를 만들어드리겠습니다.
2. 수정 방법 (결과 확인 후)
현재 Trust Policy가 이런 형태라면:
json"Condition": { "StringLike": { "token.actions.githubusercontent.com:sub": "repo:greever-io/audigo:*" } }추가할 레포를 배열로 확장:
bashaws iam update-assume-role-policy \ --role-name GithubAction \ --policy-document '{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::508153279153:oidc-provider/token.actions.githubusercontent.com" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringLike": { "token.actions.githubusercontent.com:sub": [ "repo:greever-io/audigo:*", "repo:greever-io/새레포이름:*" ] } } } ] }'현재 Trust Policy 결과를 먼저 보여주시면 정확한 명령어를 완성해드리겠습니다!
Files hidden in shared chats
더 보기{ "Version": "2012-10-17", "Statement": [ --output json { "Version": "2012-10-17", "Statement": [ "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::508153279153:oidc-provider/token.actions.githubusercontent.com" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" }, "StringLike": { "token.actions.githubusercontent.com:sub": [ "repo:integree-com/*", "repo:integree-com/*", "repo:integree-dev/audigo-back-node-prod:ref:refs/heads/main" ] } } } ] }
3월 11일현재 등록된 레포 목록을 보니 추가할 레포 이름만 알려주시면 됩니다. 어떤 레포를 추가할 건가요?
확인된 현재 목록:
- repo:integree-com/* (중복 등록되어 있음)
- repo:integree-dev/audigo-back-node-prod:ref:refs/heads/main
추가할 레포 이름을 알려주시면 아래 명령어를 완성해드리겠습니다:
bashaws iam update-assume-role-policy \ --role-name GithubAction \ --policy-document '{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::508153279153:oidc-provider/token.actions.githubusercontent.com" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" }, "StringLike": { "token.actions.githubusercontent.com:sub": [ "repo:integree-com/*", "repo:integree-dev/audigo-back-node-prod:ref:refs/heads/main", "repo:여기에추가할레포:*" ] } } } ] }'중복된 repo:integree-com/* 도 이번에 같이 정리해드릴 수 있습니다!
repo:integree-dev/audigo-back-ads-prod:ref:refs/heads/main
3월 11일bashaws iam update-assume-role-policy \ --role-name GithubAction \ --policy-document '{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::508153279153:oidc-provider/token.actions.githubusercontent.com" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" }, "StringLike": { "token.actions.githubusercontent.com:sub": [ "repo:integree-com/*", "repo:integree-dev/audigo-back-node-prod:ref:refs/heads/main", "repo:integree-dev/audigo-back-ads-prod:ref:refs/heads/main" ] } } } ] }'중복된 repo:integree-com/* 도 하나 제거해서 정리했습니다.
적용 확인:
bashaws iam get-role \ --role-name GithubAction \ --query "Role.AssumeRolePolicyDocument" \ --output json서버에 백엔드 api 를 배포했고 https://integree.io/ads/api-docs 이 주소로 접근할 수 있도록 AWS 도메인과 연결하고 있어. Nginx 는 사용하지 않고 로드밸런서에 바로 연결하는 구조야. 하지만 지금 404 에러가 뜨는데 어느 부분을 확인하면 좋을까?
3월 18일단계별로 확인해보겠습니다.
1. ALB 리스너 규칙 확인
/ads/* 경로가 올바른 타겟 그룹으로 라우팅되는지 확인:
bash# 443 리스너 ARN 확인 aws elbv2 describe-listeners \ --load-balancer-arn "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:loadbalancer/app/audigo-server-ALB/b8c642e2308258f7" \ --region ap-northeast-2 \ --query "Listeners[?Port==\`443\`].ListenerArn" \ --output text # 리스너 규칙 확인 aws elbv2 describe-rules \ --listener-arn "위에서나온ListenerArn" \ --region ap-northeast-2 \ --output json
2. Route 53 확인
integree.io 가 올바른 ALB를 가리키는지 확인:
bashaws route53 list-hosted-zones \ --query "HostedZones[?Name=='integree.io.'].[Id,Name]" \ --output tablebash# 위에서 나온 호스팅 영역 ID로 교체 aws route53 list-resource-record-sets \ --hosted-zone-id "ZXXXXXXXXX" \ --query "ResourceRecordSets[?Name=='integree.io.']" \ --output json
3. EC2에서 직접 확인
bashcurl -v http://localhost:3005/ads/api-docs
404 원인 가능성
원인확인 방법ALB 리스너 규칙에 /ads/* 경로 없음 1번 확인 타겟 그룹이 잘못된 EC2/포트를 가리킴 타겟 헬스 확인 Route 53이 잘못된 ALB를 가리킴 2번 확인 앱 자체에 /ads/api-docs 경로 없음 3번 확인 1번 리스너 규칙 결과를 보여주시면 바로 원인을 찾을 수 있습니다!
더 보기$ aws elbv2 describe-rules --listener-arn "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:listener/app/audigo-server-ALB/b8c642e2308258f7/ef745eb0697d4f48" --region ap-northeast-2 --output json 라고 입력하면 t-2 --output json { "Rules": [ { "RuleArn": "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:listener-rule/app/audigo-server-ALB/b8c642e2308258f7/ef745eb0697d4f48/71830e67b5c8f576", "Priority": "1", "Conditions": [ { "Field": "path-pattern", "PathPatternConfig": { "RegexValues": [ "^(/ping|/api-docs(/.*)?|/api/(adm|mbr|common|admob)(/.*)?|/test/(func(/.*)?|encrypt.*|decrypt.*|fcm(/.*)?|fcm/silent.*))$" ] }, "RegexValues": [ "^(/ping|/api-docs(/.*)?|/api/(adm|mbr|common|admob)(/.*)?|/test/(func(/.*)?|encrypt.*|decrypt.*|fcm(/.*)?|fcm/silent.*))$" ] } ], "Actions": [ { "Type": "forward", "Order": 1, "ForwardConfig": { "TargetGroups": [ { "TargetGroupArn": "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:targetgroup/audigo-server-target-group/0e5e4fd868d78566", "Weight": 1 }, { "TargetGroupArn": "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:targetgroup/audigo-ads-server-target-group/cbfbaa9c4432e06b", "Weight": 1 } ], "TargetGroupStickinessConfig": { "Enabled": false, "DurationSeconds": 3600 } } } ], "IsDefault": false, "Transforms": [] }, { "RuleArn": "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:listener-rule/app/audigo-server-ALB/b8c642e2308258f7/ef745eb0697d4f48/b7320a239ce6097f", "Priority": "default", "Conditions": [], "Actions": [ { "Type": "fixed-response", "Order": 1, "FixedResponseConfig": { "MessageBody": "Not Found", "StatusCode": "404", "ContentType": "text/plain" } } ], "IsDefault": true, "Transforms": [] } ] } 이런 결과가 나와. 현재 연결된 TargetGroups 은 2개고 arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:targetgroup/audigo-server-target-group/0e5e4fd868d78566 이 타겟그룹은 3003 포트이고 arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:targetgroup/audigo-ads-server-target-group/cbfbaa9c4432e06b 이 타겟그룹은 3005 포트야. PathPatternConfig 을 2개 사이트에 접속가능하도록 수정할 수 있을까? 3005 포트용 리스너 규칙을 별도로 만들어 우선순위를 조정하는 것이 좋을까?
3월 18일좋은 질문입니다. 두 방법의 차이를 먼저 설명드립니다.
방법 비교
방법 1: 현재처럼 규칙 1개에 2개 타겟그룹 (현재 구조)
/ads/api-docs → audigo-server-target-group(3003) + audigo-ads-server-target-group(3005) 로 Weight 분산문제점: 두 타겟그룹으로 랜덤하게 분산되므로 /ads/* 요청이 3003으로 가면 404가 납니다.
방법 2: 규칙을 분리 (권장)
규칙 1 (Priority 1): /ads/* 경로 → audigo-ads-server-target-group (3005) 규칙 2 (Priority 2): 나머지 경로 → audigo-server-target-group (3003)경로별로 명확하게 분리되므로 혼선이 없습니다.
권장: 규칙 분리 방법
1. 기존 규칙 수정 (3003용 - /ads 경로 제외)
bashaws elbv2 modify-rule \ --rule-arn "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:listener-rule/app/audigo-server-ALB/b8c642e2308258f7/ef745eb0697d4f48/71830e67b5c8f576" \ --conditions '[{ "Field": "path-pattern", "PathPatternConfig": { "RegexValues": ["^(/ping|/api-docs(/.*)?|/api/(adm|mbr|common|admob)(/.*)?|/test/(func(/.*)?|encrypt.*|decrypt.*))$"] } }]' \ --actions '[{ "Type": "forward", "ForwardConfig": { "TargetGroups": [{ "TargetGroupArn": "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:targetgroup/audigo-server-target-group/0e5e4fd868d78566", "Weight": 1 }] } }]' \ --region ap-northeast-22. 새 규칙 추가 (3005용 - /ads 경로 전용)
bashaws elbv2 create-rule \ --listener-arn "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:listener/app/audigo-server-ALB/b8c642e2308258f7/ef745eb0697d4f48" \ --conditions '[{ "Field": "path-pattern", "PathPatternConfig": { "RegexValues": ["^(/ads/ping|/ads/api-docs(/.*)?|/api/ads/(common)(/.*)?)$"] } }]' \ --actions '[{ "Type": "forward", "ForwardConfig": { "TargetGroups": [{ "TargetGroupArn": "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:targetgroup/audigo-ads-server-target-group/cbfbaa9c4432e06b", "Weight": 1 }] } }]' \ --priority 2 \ --region ap-northeast-2
최종 구조
Priority 1: ^(/ping|/api-docs|/api/(adm|mbr|common|admob)|/test/...)$ → 3003 Priority 2: ^(/ads/ping|/ads/api-docs|/api/ads/common/...)$ → 3005 Default: 404 반환3005 포트 서버에서 /ads/api-docs 경로가 실제로 존재하는지도 확인해주세요:
bashcurl -v http://localhost:3005/ads/api-docsFiles hidden in shared chats
우선순위가 높고 낮으면 어떤 일이 일어나지? 상세히 설명 부탁해~
3월 18일ALB 리스너 규칙 우선순위 동작 방식
ALB는 요청이 들어오면 Priority 숫자가 낮은 규칙부터 순서대로 검사합니다. 처음으로 일치하는 규칙을 적용하고 나머지 규칙은 무시합니다.
요청 → Priority 1 검사 → 일치? → 즉시 적용 (끝) → 불일치? → Priority 2 검사 → 일치? → 즉시 적용 (끝) → 불일치? → Priority 3 검사 ... → 모두 불일치 → Default 규칙 적용
현재 상황에 대입
Priority 1: ^(/ping|/api-docs|/api/(adm|mbr|common|admob)|/test/...)$ → 3003 Priority 2: ^(/ads/ping|/ads/api-docs|/api/ads/common/...)$ → 3005 Default: 404 반환요청: https://integree.io/ads/api-docs
Priority 1 검사 → /ads/api-docs 는 패턴 불일치 → 다음으로 Priority 2 검사 → /ads/api-docs 패턴 일치! → 3005로 전달 ✅요청: https://integree.io/api-docs
Priority 1 검사 → /api-docs 패턴 일치! → 3003으로 전달 ✅ Priority 2 는 검사 자체를 하지 않음요청: https://integree.io/unknown
Priority 1 검사 → 불일치 Priority 2 검사 → 불일치 Default 규칙 → 404 반환
우선순위가 잘못되면 생기는 문제
만약 Priority 1에 /* (모든 경로) 규칙이 있다면:
Priority 1: /* → 3003 (모든 경로 일치!) Priority 2: /ads/* → 3005 (절대 실행 안 됨 ❌)더 구체적인 경로일수록 낮은 숫자(높은 우선순위) 를 부여해야 합니다.
우선순위 설계 원칙
우선순위경로이유낮은 숫자 (1, 2...) 구체적인 경로 (/ads/api-docs) 먼저 잡아야 함 높은 숫자 (...8, 9) 넓은 경로 (/*) 나중에 처리 Default (마지막) 아무것도 안 맞을 때 404 반환 더 보기curl -v http://localhost:3005/ads/api-docs * Host localhost:3005 was resolved. * IPv6: ::1 * IPv4: 127.0.0.1 * Trying [::1]:3005... * Connected to localhost (::1) port 3005 > GET /ads/api-docs HTTP/1.1 > Host: localhost:3005 > User-Agent: curl/8.5.0 > Accept: */* > < HTTP/1.1 301 Moved Permanently < Access-Control-Allow-Origin: * < Content-Security-Policy: default-src 'none' < Cross-Origin-Opener-Policy: same-origin < Cross-Origin-Resource-Policy: same-origin < Origin-Agent-Cluster: ?1 < Referrer-Policy: no-referrer < Strict-Transport-Security: max-age=31536000; includeSubDomains < X-Content-Type-Options: nosniff < X-DNS-Prefetch-Control: off < Content-Security-Policy: default-src 'none' < Cross-Origin-Opener-Policy: same-origin < Cross-Origin-Resource-Policy: same-origin < Origin-Agent-Cluster: ?1 < Referrer-Policy: no-referrer < Strict-Transport-Security: max-age=31536000; includeSubDomains < X-Content-Type-Options: nosniff < X-DNS-Prefetch-Control: off < Cross-Origin-Resource-Policy: same-origin < Origin-Agent-Cluster: ?1 < Referrer-Policy: no-referrer < Strict-Transport-Security: max-age=31536000; includeSubDomains < X-Content-Type-Options: nosniff < X-DNS-Prefetch-Control: off < Referrer-Policy: no-referrer < Strict-Transport-Security: max-age=31536000; includeSubDomains < X-Content-Type-Options: nosniff < X-DNS-Prefetch-Control: off < X-Content-Type-Options: nosniff < X-DNS-Prefetch-Control: off < X-Download-Options: noopen < X-Frame-Options: SAMEORIGIN < X-Permitted-Cross-Domain-Policies: none < X-XSS-Protection: 0 < X-DNS-Prefetch-Control: off < X-Download-Options: noopen < X-Frame-Options: SAMEORIGIN < X-Permitted-Cross-Domain-Policies: none < X-XSS-Protection: 0 < X-Download-Options: noopen < X-Frame-Options: SAMEORIGIN < X-Permitted-Cross-Domain-Policies: none < X-XSS-Protection: 0 < RateLimit-Policy: 20;w=10 < RateLimit-Limit: 20 < RateLimit-Policy: 20;w=10 < RateLimit-Limit: 20 < RateLimit-Remaining: 19 < RateLimit-Reset: 10 < Content-Type: text/html; charset=UTF-8 < Content-Length: 162 < Location: /ads/api-docs/ < Date: Wed, 18 Mar 2026 03:31:44 GMT < Connection: keep-alive < Keep-Alive: timeout=65
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Redirecting</title> </head> <body> <pre>Redirecting to /ads/api-docs/</pre> </body> </html> * Connection #0 to host localhost left intact 이라고 뜨면 3005 포트가 정상적으로 실행되고 있어?
3월 18일네, 3005 포트는 정상 실행 중입니다.
301 Moved Permanently 는 에러가 아니라 /ads/api-docs → /ads/api-docs/ 로 슬래시를 붙여서 리다이렉트하는 정상적인 Swagger 동작입니다.
확인
bash# 슬래시 포함해서 재요청 curl -v http://localhost:3005/ads/api-docs/200 OK가 나오면 앱은 완전히 정상입니다.
그렇다면 브라우저 404의 원인은 ALB 리스너 규칙
/ads/api-docs/ (슬래시 포함) 가 현재 규칙 패턴에 매칭되는지 확인이 필요합니다.
현재 기존 규칙의 패턴:
^(/ping|/api-docs(/.*)?|/api/(adm|mbr|common|admob)(/.*)?|/test/(...))$/ads/api-docs/ 는 이 패턴에 없으므로 Default 규칙인 404로 떨어지는 상황입니다.
앞서 안내드린 대로 3005용 규칙을 별도로 추가하면 해결됩니다. 진행할까요?
리스너 규칙을 이미 추가했고 경로(정규식) = ^(/ads/ping|/ads/api-docs(/.*)?|/api/ads(/.*)?|/test/(func(/.*)?|encrypt.*|decrypt.*))$ => 이렇게 추가했더니 504 Gateway Time-out 오류가 발생해.
3월 18일504는 ALB가 타겟그룹으로 요청을 보냈지만 응답이 없는 상태입니다. 타겟그룹 헬스체크 상태를 먼저 확인해보세요.
1. ads 타겟그룹 헬스체크 확인
bashaws elbv2 describe-target-health \ --target-group-arn "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:targetgroup/audigo-ads-server-target-group/cbfbaa9c4432e06b" \ --region ap-northeast-2
2. EC2에서 직접 확인
bash# 3005 포트 리스닝 확인 ss -tlnp | grep 3005 # 직접 요청 테스트 curl -v http://localhost:3005/ads/ping
504 주요 원인
원인확인 방법타겟그룹이 unhealthy 1번 확인 EC2 SG에 3005 포트 미오픈 SG 인바운드 확인 앱이 응답을 너무 늦게 반환 ALB 타임아웃 설정 확인 타겟그룹에 EC2가 미등록 타겟 헬스에 인스턴스 없음 타겟 헬스 결과를 보여주시면 바로 원인 찾겠습니다!
Files hidden in shared chats
더 보기{ "TargetHealthDescriptions": [ { "Target": { "Id": "i-0399060e28e3fa091", "Port": 3005 }, "HealthCheckPort": "3005", "TargetHealth": { "State": "unhealthy", "Reason": "Target.Timeout", "Description": "Request timed out" }, "AdministrativeOverride": { "State": "no_override", "Reason": "AdministrativeOverride.NoOverride", "Description": "No override is currently active on target" } }, { "Target": { "Id": "i-0cc3535cc3a2b2849", "Port": 3005 }, "HealthCheckPort": "3005", "TargetHealth": { "State": "unhealthy", "Reason": "Target.Timeout", "Description": "Request timed out" }, "AdministrativeOverride": { "State": "no_override", "Reason": "AdministrativeOverride.NoOverride", "Description": "No override is currently active on target" } } ] }
3월 18일Target.Timeout 이 2개 EC2 모두에서 발생하고 있습니다. ALB가 3005 포트로 접근하지 못하는 상황입니다. EC2 Security Group 문제일 가능성이 높습니다.
1. EC2 Security Group 인바운드 확인
bash# i-0399060e28e3fa091 의 SG 확인 aws ec2 describe-instances \ --instance-ids i-0399060e28e3fa091 i-0cc3535cc3a2b2849 \ --region ap-northeast-2 \ --query "Reservations[*].Instances[*].[InstanceId,SecurityGroups[*].GroupId]" \ --output tablebash# SG 인바운드 규칙에 3005 포트가 있는지 확인 (sg-XXXX는 위에서 나온 SG ID로 교체) aws ec2 describe-security-groups \ --group-ids sg-XXXX \ --region ap-northeast-2 \ --query "SecurityGroups[0].IpPermissions[?FromPort==\`3005\`]" \ --output json
2. 3005 포트 인바운드 규칙 추가
결과가 비어있다면 ALB SG에서 3005 포트 접근을 허용해야 합니다.
bash# ALB SG ID 확인 aws elbv2 describe-load-balancers \ --load-balancer-arns "arn:aws:elasticloadbalancing:ap-northeast-2:508153279153:loadbalancer/app/audigo-server-ALB/b8c642e2308258f7" \ --region ap-northeast-2 \ --query "LoadBalancers[0].SecurityGroups" \ --output textbash# EC2 SG에 3005 포트 오픈 (ALB SG에서만 허용) aws ec2 authorize-security-group-ingress \ --group-id sg-EC2의SG \ --protocol tcp \ --port 3005 \ --source-group sg-ALB의SG \ --region ap-northeast-2SG 확인 결과를 보여주시면 바로 진행하겠습니다!
S3와 CloudFront 의 역할 구분이 정확하게 어떻게 되지? S3는 파일을 올릴 수 있는 폴더란 것을 알겠는데 CloudFront 는 무슨 용도로 사용하는지 모르겠어.
3월 18일S3 vs CloudFront 역할
S3 (Simple Storage Service)
단순히 파일을 저장하는 공간입니다.
S3 버킷 ├── index.html ├── main.js ├── style.css └── images/ └── logo.png- 파일 저장/조회 가능
- 정적 웹사이트 호스팅 가능
- 단점: 서울 리전에 있으면 미국 사용자는 느림, HTTPS 커스텀 도메인 설정 복잡
CloudFront (CDN, Content Delivery Network)
전 세계에 파일을 빠르게 배포하는 중간 전달자입니다.
한국 사용자 → 서울 엣지 서버 → (캐시 있으면 바로 응답) 미국 사용자 → 미국 엣지 서버 → (캐시 있으면 바로 응답) ↑ 캐시 없으면 S3에서 가져와서 저장CloudFront는 전 세계 450개 이상의 엣지 서버에 파일을 캐싱해두고 사용자와 가장 가까운 서버에서 응답합니다.
실제 구조
사용자 브라우저 ↓ CloudFront (글로벌 CDN) - HTTPS 처리 - 캐싱 - 커스텀 도메인 (ads.integree.org) ↓ S3 버킷 (오리진) - 실제 파일 저장 - 직접 접근은 차단
CloudFront가 하는 일 요약
기능설명캐싱 S3 파일을 엣지 서버에 저장해 빠르게 응답 HTTPS SSL 인증서 적용 (ACM 연동) 커스텀 도메인 ads.integree.org 같은 도메인 연결 보안 S3 직접 접근 차단, WAF 연동 가능 압축 gzip/brotli 압축으로 전송 속도 향상 SPA 라우팅 React 앱의 404 처리 (index.html 로 fallback)
Audigo 프로젝트에서의 활용
ads.integree.org (React 광고주 대시보드) ↓ CloudFront ↓ S3 (빌드된 React 파일: index.html, main.js 등)S3만 쓰면 되는데 CloudFront를 쓰는 가장 큰 이유는 HTTPS 커스텀 도메인 + 캐싱 + 보안 때문입니다.
CloudFront에서 HTTPS 커스텀 도메인을 사용할 수 있는 이유는 뭐지?
3월 18일CloudFront에서 HTTPS 커스텀 도메인이 가능한 이유
기본 구조
CloudFront는 기본적으로 아래 도메인을 제공합니다.
https://d1234abcd.cloudfront.net ← CloudFront 기본 도메인여기에 커스텀 도메인을 연결하려면 2가지가 필요합니다.
1. ACM SSL 인증서
ACM(AWS Certificate Manager)에서 발급한 인증서를 CloudFront에 연결하면 커스텀 도메인으로 HTTPS 통신이 가능해집니다.
사용자 → https://ads.integree.org 요청 ↓ CloudFront가 ACM 인증서로 SSL 핸드셰이크 처리 ↓ HTTPS 통신 성립단, CloudFront용 ACM 인증서는 반드시 us-east-1 (버지니아) 리전에서 발급해야 합니다. CloudFront가 글로벌 서비스라 버지니아 리전 인증서만 인식합니다.
ap-northeast-2 (서울) → ALB용 인증서 us-east-1 (버지니아) → CloudFront용 인증서
2. Route 53 CNAME 또는 Alias 레코드
ACM 인증서만으로는 부족하고 DNS도 연결해야 합니다.
Route 53 ads.integree.org → CNAME → d1234abcd.cloudfront.net이렇게 하면:
사용자가 ads.integree.org 입력 ↓ Route 53이 d1234abcd.cloudfront.net 으로 안내 ↓ CloudFront가 ACM 인증서로 HTTPS 처리 ↓ S3에서 파일 응답
전체 흐름 정리
① ACM에서 ads.integree.org 인증서 발급 (us-east-1) ↓ ② CloudFront 배포에 인증서 + 커스텀 도메인 등록 ↓ ③ Route 53에서 ads.integree.org → CloudFront 연결 ↓ ④ 사용자가 https://ads.integree.org 접속 가능 ✅
ALB와 비교
ALBCloudFrontACM 인증서 리전 ap-northeast-2 us-east-1 용도 백엔드 API 프론트엔드 정적 파일 오리진 EC2 S3 CloudFront 에서 사용하는 Distribution ID secret 이름은 어떤 용도지?
3월 18일CloudFront Distribution ID는 GitHub Actions CI/CD 배포 자동화에서 사용합니다.
용도
React 앱을 빌드 후 S3에 업로드하면 CloudFront가 이전 파일을 캐싱하고 있어서 즉시 반영이 안 됩니다. 그래서 배포할 때마다 CloudFront 캐시를 강제로 삭제(Invalidation)해야 합니다.
yaml# GitHub Actions workflow 예시 - name: S3에 빌드 파일 업로드 run: aws s3 sync ./build s3://audigo-ads-bucket - name: CloudFront 캐시 삭제 run: | aws cloudfront create-invalidation \ --distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} \ --paths "/*"
흐름
GitHub main 브랜치 push ↓ GitHub Actions 실행 ↓ React 빌드 (npm run build) ↓ S3에 파일 업로드 ↓ CloudFront Invalidation (캐시 삭제) ← 여기서 Distribution ID 사용 ↓ 사용자가 새 버전 즉시 확인 가능 ✅
Secret으로 관리하는 이유
Distribution ID 자체는 민감한 정보는 아니지만 AWS 리소스 ID를 코드에 하드코딩하지 않기 위해 GitHub Secrets에 저장해서 사용합니다.
Audigo 프로젝트에서 ads.integree.org 와 adm.integree.org 각각 별도의 CloudFront 배포가 있다면 Distribution ID도 각각 다른 Secret으로 관리하게 됩니다.
Distribution ID secret 은 어떻게 생성하지?
3월 18일GitHub Repository의 Secrets에 등록하면 됩니다.
1. CloudFront Distribution ID 확인
bashaws cloudfront list-distributions \ --query "DistributionList.Items[*].[Id,DomainName,Aliases.Items]" \ --output table결과에서 ads.integree.org 에 해당하는 Id 값을 복사합니다.
2. GitHub Secrets 등록
GitHub 웹에서:
해당 Repository → Settings → Secrets and variables → Actions → New repository secret항목값Name CLOUDFRONT_DISTRIBUTION_ID Secret 위에서 복사한 Distribution ID (예: E1ABCDEF1234) → Add secret 클릭
3. GitHub Actions workflow에서 사용
yaml- name: CloudFront 캐시 삭제 run: | aws cloudfront create-invalidation \ --distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} \ --paths "/*"
배포 대상이 ads.integree.org, adm.integree.org 2개라면 Secret도 2개 만들면 됩니다.
Secret 이름값CLOUDFRONT_DISTRIBUTION_ID_ADS ads용 Distribution ID CLOUDFRONT_DISTRIBUTION_ID_ADM adm용 Distribution ID Files hidden in shared chats
Row should have 1 elements, instead it has 3 라는 메시지가 나와.
3월 18일--output table 이 배열을 제대로 표시 못하는 문제입니다. json 으로 바꿔서 실행하세요.
bashaws cloudfront list-distributions \ --query "DistributionList.Items[*].[Id,DomainName,Aliases.Items[0]]" \ --output table또는 json으로:
bashaws cloudfront list-distributions \ --query "DistributionList.Items[*].{Id:Id,Domain:DomainName,Alias:Aliases.Items[0]}" \ --output table결과에서 ads.integree.org 또는 adm.integree.org 에 해당하는 Id 값을 찾으면 됩니다.
| ListDistributions | +----------------+---------------------------------+----------------------+ | E2SNVOZBV4S04V| d3fhlvyphi73pb.cloudfront.net | admin.integree.net | | E3OO9RAWZLXH8W| d30ouvx4f589ki.cloudfront.net | None | | ELPWE9HQBSUM7 | d14s94ocdbfsu2.cloudfront.net | None | | E2Z2IVHPAA47LC| dw4mub98kp6fv.cloudfront.net | None | | EOPXMP7QH251I | d2m0s82tfelqqj.cloudfront.net | audigo.im 이라고 나오는데 난 ads.audigo.im 이 주소가 필요해.
3월 18일결과에 ads.audigo.im 이 없네요. 두 가지 가능성이 있습니다.
1. Alias가 등록되지 않은 Distribution 확인
None 으로 나오는 3개 중 하나일 수 있습니다. 각각 상세 확인:
bashaws cloudfront get-distribution \ --id E3OO9RAWZLXH8W \ --query "Distribution.DistributionConfig.{Aliases:Aliases,Origins:Origins.Items[0].DomainName}" \ --output json aws cloudfront get-distribution \ --id ELPWE9HQBSUM7 \ --query "Distribution.DistributionConfig.{Aliases:Aliases,Origins:Origins.Items[0].DomainName}" \ --output json aws cloudfront get-distribution \ --id E2Z2IVHPAA47LC \ --query "Distribution.DistributionConfig.{Aliases:Aliases,Origins:Origins.Items[0].DomainName}" \ --output json
2. ads.audigo.im 용 Distribution이 아직 없는 경우
새로 생성해야 합니다. 현재 ads.audigo.im 용 CloudFront 배포를 만든 적이 있으신가요?
만든 적이 없어. 어떻게 만들지?
3월 18일순서대로 진행하겠습니다.
전체 순서
1. S3 버킷 생성 2. ACM 인증서 발급 (us-east-1) 3. CloudFront Distribution 생성 4. Route 53 DNS 연결
1. S3 버킷 생성
bashaws s3api create-bucket \ --bucket ads-audigo-im \ --region ap-northeast-2 \ --create-bucket-configuration LocationConstraint=ap-northeast-2정적 웹사이트 호스팅 활성화:
bashaws s3 website s3://ads-audigo-im \ --index-document index.html \ --error-document index.html
2. ACM 인증서 발급 (반드시 us-east-1)
bashaws acm request-certificate \ --domain-name ads.audigo.im \ --validation-method DNS \ --region us-east-1발급된 인증서 ARN 확인:
bashaws acm list-certificates \ --region us-east-1 \ --query "CertificateSummaryList[?DomainName=='ads.audigo.im'].[CertificateArn,Status]" \ --output table
3. ACM DNS 검증
bash# 검증용 CNAME 레코드 확인 aws acm describe-certificate \ --certificate-arn "arn:aws:acm:us-east-1:508153279153:certificate/XXXX" \ --region us-east-1 \ --query "Certificate.DomainValidationOptions[0].ResourceRecord" \ --output json결과로 나온 CNAME을 Route 53에 등록:
bash# audigo.im 호스팅 영역 ID 확인 aws route53 list-hosted-zones \ --query "HostedZones[?Name=='audigo.im.'].Id" \ --output textbash# CNAME 레코드 추가 (Name, Value는 위 describe-certificate 결과값으로 교체) aws route53 change-resource-record-sets \ --hosted-zone-id "ZXXXXXXXXX" \ --change-batch '{ "Changes": [{ "Action": "CREATE", "ResourceRecordSet": { "Name": "_xxxx.ads.audigo.im", "Type": "CNAME", "TTL": 300, "ResourceRecords": [{"Value": "_yyyy.acm-validations.aws"}] } }] }'인증서 상태가 ISSUED 로 바뀔 때까지 대기 (보통 5분 이내):
bashaws acm describe-certificate \ --certificate-arn "arn:aws:acm:us-east-1:508153279153:certificate/XXXX" \ --region us-east-1 \ --query "Certificate.Status" \ --output text
4. CloudFront Distribution 생성
인증서가 ISSUED 상태가 되면:
bashaws cloudfront create-distribution \ --distribution-config '{ "CallerReference": "ads-audigo-im-001", "Aliases": { "Quantity": 1, "Items": ["ads.audigo.im"] }, "DefaultRootObject": "index.html", "Origins": { "Quantity": 1, "Items": [{ "Id": "S3-ads-audigo-im", "DomainName": "ads-audigo-im.s3.ap-northeast-2.amazonaws.com", "S3OriginConfig": { "OriginAccessIdentity": "" } }] }, "DefaultCacheBehavior": { "TargetOriginId": "S3-ads-audigo-im", "ViewerProtocolPolicy": "redirect-to-https", "CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6", "AllowedMethods": { "Quantity": 2, "Items": ["GET", "HEAD"] } }, "CustomErrorResponses": { "Quantity": 1, "Items": [{ "ErrorCode": 403, "ResponsePagePath": "/index.html", "ResponseCode": "200", "ErrorCachingMinTTL": 0 }] }, "ViewerCertificate": { "ACMCertificateArn": "arn:aws:acm:us-east-1:508153279153:certificate/XXXX", "SSLSupportMethod": "sni-only", "MinimumProtocolVersion": "TLSv1.2_2021" }, "Enabled": true, "Comment": "ads.audigo.im" }'
5. Route 53 DNS 연결
CloudFront Distribution 생성 후 나온 DomainName (예: dxxxx.cloudfront.net) 으로 Alias 레코드 추가:
bashaws route53 change-resource-record-sets \ --hosted-zone-id "ZXXXXXXXXX" \ --change-batch '{ "Changes": [{ "Action": "CREATE", "ResourceRecordSet": { "Name": "ads.audigo.im", "Type": "A", "AliasTarget": { "HostedZoneId": "Z2FDTNDATAQYW2", "DNSName": "dxxxx.cloudfront.net", "EvaluateTargetHealth": false } } }] }' \ --region ap-northeast-2
1번 S3 버킷 생성부터 순서대로 진행해보세요!
Files hidden in shared chats
audigo-web-react-ads(arn:aws:s3:::audigo-web-react-ads) 란 버킷을 생성했는데 정적 웹사이트 호스팅 활성화가 되어 있는지 확인하고 싶어.
3월 18일bashaws s3api get-bucket-website \ --bucket audigo-web-react-ads결과 해석:
결과의미IndexDocument, ErrorDocument 가 나옴 활성화 되어 있음 ✅ NoSuchWebsiteConfiguration 에러 활성화 안 되어 있음 ❌ 활성화 안 되어 있다면:
bashaws s3 website s3://audigo-web-react-ads \ --index-document index.html \ --error-document index.htmlFiles hidden in shared chats
issued 가 되면 AWS 어느 메뉴페이지에서 확인가능하지?
3월 18일AWS Console → Certificate Manager (ACM) 에서 확인할 수 있습니다.
경로
AWS Console → 우측 상단 리전을 us-east-1 (버지니아 북부) 로 변경 ← 중요! → Certificate Manager 검색 → 인증서 목록에서 ads.audigo.im 확인 → 상태 컬럼에 "발급됨 (Issued)" 표시
주의사항
리전을 반드시 us-east-1 (버지니아 북부) 로 변경해야 합니다. 서울 리전(ap-northeast-2)에서는 CloudFront용 인증서가 보이지 않습니다.
CLI로 빠르게 확인하려면:
bashaws acm list-certificates \ --region us-east-1 \ --query "CertificateSummaryList[?DomainName=='ads.audigo.im'].[DomainName,Status]" \ --output tableFiles hidden in shared chats
S3 버킷 이름을 audigo-web-react-ads 이라 생성했어. CallerReference 라든지 내가 바꿀 수 있는 항목에 대해서는 -를 넣고 changable 이란 단어를 넣어줄래?
3월 18일bashaws cloudfront create-distribution \ --distribution-config '{ "CallerReference": "ads-audigo-im-001-changable", "Aliases": { "Quantity": 1, "Items": ["ads.audigo.im"] }, "DefaultRootObject": "index.html", "Origins": { "Quantity": 1, "Items": [{ "Id": "S3-audigo-web-react-ads-changable", "DomainName": "audigo-web-react-ads.s3.ap-northeast-2.amazonaws.com", "S3OriginConfig": { "OriginAccessIdentity": "" } }] }, "DefaultCacheBehavior": { "TargetOriginId": "S3-audigo-web-react-ads-changable", "ViewerProtocolPolicy": "redirect-to-https", "CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6", "AllowedMethods": { "Quantity": 2, "Items": ["GET", "HEAD"] } }, "CustomErrorResponses": { "Quantity": 1, "Items": [{ "ErrorCode": 403, "ResponsePagePath": "/index.html", "ResponseCode": "200", "ErrorCachingMinTTL": 0 }] }, "ViewerCertificate": { "ACMCertificateArn": "arn:aws:acm:us-east-1:508153279153:certificate/XXXX-changable", "SSLSupportMethod": "sni-only", "MinimumProtocolVersion": "TLSv1.2_2021" }, "Enabled": true, "Comment": "ads.audigo.im-changable" }'
변경 항목 설명
항목변경 내용CallerReference 중복 방지용 고유값, 아무 문자열이나 가능 Id (Origin) S3 오리진 식별자, 자유롭게 지정 가능 TargetOriginId Origin Id와 반드시 동일하게 맞춰야 함 DomainName 실제 S3 버킷 이름 반영 ACMCertificateArn 발급된 실제 인증서 ARN으로 교체 필요 Comment 설명용 메모, 자유롭게 지정 가능 arn:aws:cloudfront::508153279153:distribution/E2SNVOZBV4S04V에 대해 aws cloudfront create-distribution 정보를 조회하는 방법을 알려줄래?
3월 18일bashaws cloudfront get-distribution-config \ --id E2SNVOZBV4S04V \ --output json기존 Distribution의 설정을 그대로 참고해서 새 Distribution 만들 때 활용하려는 거죠?
결과에서 DistributionConfig 부분이 create-distribution 의 --distribution-config 에 넣을 수 있는 형태로 나옵니다. 단 ETag 값은 create-distribution 에는 필요 없으니 제외하면 됩니다.
Files hidden in shared chats
hosted-zone-id "ZXXXXXXXXX" 여기에 넣는 값과 "HostedZoneId": "Z2FDTNDATAQYW2" 여기에 넣는 값은 같은 값이야? 같은 값이면 왜 한쪽은 ZXXXXXXXXX 라고 했지?
3월 18일다른 값입니다.
차이점
항목설명값--hosted-zone-id "ZXXXXXXXXX" 내 Route 53 의 audigo.im 호스팅 영역 ID 내 AWS 계정에서 조회 "HostedZoneId": "Z2FDTNDATAQYW2" CloudFront 서비스 의 고정 호스팅 영역 ID AWS가 정해놓은 고정값
쉽게 설명
--hosted-zone-id → 내가 만든 audigo.im DNS 영역 ID (계정마다 다름) HostedZoneId (Alias) → CloudFront 전용 고정값 (모든 계정 동일: Z2FDTNDATAQYW2)Z2FDTNDATAQYW2 는 전 세계 모든 AWS 계정에서 CloudFront Alias 레코드 생성 시 동일하게 사용하는 고정값입니다. 외울 필요 없이 항상 이 값을 그대로 쓰면 됩니다.
내 hosted-zone-id 확인
bashaws route53 list-hosted-zones \ --query "HostedZones[?Name=='audigo.im.'].[Id,Name]" \ --output table결과에서 /hostedzone/ZXXXXXXXXX 형태로 나오면 ZXXXXXXXXX 부분만 사용하면 됩니다.
AWS EC2를 2대 사용하고 각각에 백엔드 node was 를 배포했어. 로드밸런서가 접속자들의 요청을 받아 한 번은 a 서버, 한 번은 b 서버에 알아서 요청을 배분하는데 이 때 동일한 사용자의 요청이라는 것을 각각의 서버에서 어떻게 구분하지?
3월 24일좋은 질문입니다. 이것이 바로 세션 관리 문제입니다.
문제 상황
사용자 A 로그인 요청 → ALB → EC2-1 (세션 생성) 사용자 A 다음 요청 → ALB → EC2-2 (세션 없음! 누구?)EC2-1과 EC2-2가 각자 메모리에 세션을 저장하면 다른 서버로 요청이 가면 인증이 풀리는 문제가 발생합니다.
해결 방법 3가지
방법 1: JWT (Audigo에서 사용 중인 방식)
로그인 → 서버가 JWT 토큰 발급 → 클라이언트가 토큰 보관 다음 요청 → Authorization: Bearer {JWT} 헤더에 포함 EC2-1 또는 EC2-2 어디서든 → JWT 서명 검증만 하면 사용자 식별 가능서버가 상태를 저장하지 않아도 됩니다 (Stateless). 토큰 자체에 사용자 정보가 담겨있어서 어느 서버로 가든 검증 가능합니다.
방법 2: 중앙 세션 저장소 (Redis)
사용자 요청 → EC2-1 또는 EC2-2 ↓ Redis (공유 세션 저장소) session_id: {userId, role, ...}어느 서버로 가든 Redis에서 세션을 조회하므로 동일한 사용자 식별 가능합니다.
방법 3: ALB Sticky Session (고정 세션)
사용자 A → 항상 EC2-1로만 라우팅 사용자 B → 항상 EC2-2로만 라우팅ALB가 쿠키를 심어서 동일 사용자는 항상 같은 서버로 보냅니다. 단 특정 서버에 부하가 몰릴 수 있고 서버 장애 시 세션이 사라지는 단점이 있습니다.
방법 비교
방법장점단점JWT Stateless, 서버 확장 용이 토큰 탈취 시 무효화 어려움 Redis 세션 세션 즉시 무효화 가능 Redis 서버 추가 필요 Sticky Session 구현 단순 부하 불균형, 장애 취약
Audigo 현재 구조 추천
JWT + HTTP-only Cookie 방식을 이미 사용 중이시므로 어느 서버로 요청이 가든 JWT 서명 검증만으로 사용자 식별이 가능한 상태입니다. 별도 추가 작업 없이 2대 EC2 로드밸런싱이 정상 동작합니다.
Files hidden in shared chats
단점으로 지적한 부분을 암호화하여 전송하는 것은 어때?
3월 24일암호화 전송은 이미 HTTPS로 해결되어 있어서 탈취 무효화 문제와는 별개입니다.
문제의 본질
HTTPS 암호화 → 전송 중 탈취 방지 ✅ JWT 무효화 → 이미 탈취된 토큰을 막는 것 ❌ (별개 문제)토큰이 이미 탈취된 후에 해당 토큰을 무효화할 수 없다는 단점입니다.
구체적인 시나리오
1. 해커가 사용자 JWT 토큰을 탈취 2. 서버에 해당 토큰으로 요청 3. 서버는 서명만 검증 → 유효한 토큰으로 판단 → 허용 ❌ 4. 토큰 만료 전까지 막을 방법이 없음암호화 전송(HTTPS)은 1번 탈취 자체를 어렵게 하지만 이미 탈취된 경우는 막지 못합니다.
실질적인 해결책
1. 토큰 만료시간 짧게 설정
Access Token → 15분~1시간 (짧게) Refresh Token → 7~30일 (길게, HTTP-only Cookie)탈취되어도 짧은 시간 내에 만료됩니다.
2. Refresh Token Rotation
Refresh Token 사용 시 → 새 Refresh Token 발급 + 기존 것 무효화 탈취된 Refresh Token 사용 감지 시 → 모든 세션 강제 종료3. Redis 블랙리스트 (로그아웃 시)
로그아웃 → 해당 JWT를 Redis 블랙리스트에 등록 요청 시 → JWT 검증 + 블랙리스트 확인이렇게 하면 즉시 무효화가 가능합니다.
Audigo 권장 구조
Access Token 15분 만료 + HTTP-only Cookie Refresh Token 7일 만료 + HTTP-only Cookie + Rotation 로그아웃 시 Redis 블랙리스트 등록현재 JWT + HTTP-only Cookie를 쓰고 계시니 Refresh Token Rotation 을 추가하는 것이 보안상 가장 효과적입니다.
Files hidden in shared chats
이게 무엇인지 어떻게 사용하는지 비용은 어떻게 되는지 정리 부탁해.
3월 24일Redis란?
메모리 기반 Key-Value 저장소입니다. 일반 DB(PostgreSQL)가 디스크에 저장하는 것과 달리 RAM에 저장하기 때문에 매우 빠릅니다.
PostgreSQL → 디스크 저장 → 조회 속도 느림 (ms 단위) Redis → 메모리 저장 → 조회 속도 빠름 (μs 단위)
주요 사용 사례
용도설명세션 저장 로그인 세션을 중앙에서 관리 JWT 블랙리스트 로그아웃된 토큰 무효화 캐싱 DB 조회 결과를 임시 저장해 부하 감소 속도 제한 Rate Limiting 카운터 저장 실시간 데이터 순위표, 알림 등
Node.js에서 사용 방법
설치
bashnpm install ioredis기본 사용
typescriptimport Redis from 'ioredis'; const redis = new Redis({ host: 'your-redis-endpoint', port: 6379, }); // 저장 (TTL 3600초 = 1시간) await redis.set('session:userId123', JSON.stringify({ userId: 123, role: 'admin' }), 'EX', 3600); // 조회 const session = await redis.get('session:userId123'); // 삭제 (로그아웃) await redis.del('session:userId123');JWT 블랙리스트 활용
typescript// 로그아웃 시 await redis.set(`blacklist:${token}`, '1', 'EX', tokenExpiry); // 요청 검증 시 const isBlacklisted = await redis.get(`blacklist:${token}`); if (isBlacklisted) throw new UnauthorizedException();
AWS에서 사용하는 방법 (ElastiCache)
AWS에서는 ElastiCache for Redis 를 제공합니다.
EC2 (Node.js) → ElastiCache (Redis) → 세션/캐시 공유 EC2-1 ↗ EC2-2 ↗ 동일한 Redis에 접근ElastiCache 생성
bashaws elasticache create-cache-cluster \ --cache-cluster-id audigo-redis \ --cache-node-type cache.t3.micro \ --engine redis \ --num-cache-nodes 1 \ --region ap-northeast-2
비용
AWS ElastiCache 요금 (ap-northeast-2 기준)
인스턴스 타입메모리시간당 요금월 예상 비용cache.t3.micro 0.5GB $0.017 약 $12 cache.t3.small 1.37GB $0.034 약 $25 cache.t3.medium 3.09GB $0.068 약 $50 Audigo 초기 단계에서는 cache.t3.micro 로 충분합니다.
EC2에 직접 설치하는 방법 (비용 절감)
ElastiCache 대신 기존 EC2에 Redis를 직접 설치하면 추가 비용 없음:
bash# EC2에 Redis 설치 sudo apt-get install redis-server -y # 시작 sudo systemctl start redis sudo systemctl enable redis # 확인 redis-cli ping # PONG 이 나오면 정상단 EC2가 재시작되면 데이터가 날아가는 위험이 있고 EC2 장애 시 Redis도 같이 죽는 단점이 있습니다.
권장 구성
상황권장개발/초기 단계 EC2에 직접 설치 (무료) 운영 안정성 필요 ElastiCache cache.t3.micro ($12/월) 고가용성 필요 ElastiCache Multi-AZ 현재 Audigo 단계에서는 EC2에 직접 설치로 시작하고 트래픽이 늘어나면 ElastiCache로 전환하는 것을 권장드립니다.
Redis 가 일종의 was 인거야? host: 'your-redis-endpoint' 에 구체적으로 어떤 값이 들어가지? 도메인을 연결하는 것도 돼? 그리고 redis 를 start 하고 stop 하는 것은 어떻게 하지?
3월 24일Redis는 WAS인가?
아닙니다. 역할이 다릅니다.
구분역할WAS (Node.js) 비즈니스 로직 처리, API 응답 Redis 데이터 빠르게 저장/조회 (메모리 DB) PostgreSQL 데이터 영구 저장 (디스크 DB) 클라이언트 → ALB → EC2 Node.js (WAS) → Redis (캐시/세션) → PostgreSQL (영구저장)Redis는 WAS가 필요할 때 호출하는 저장소입니다.
host에 들어가는 값
EC2에 직접 설치한 경우
typescriptconst redis = new Redis({ host: '127.0.0.1', // 같은 EC2면 localhost port: 6379, });다른 EC2에 설치한 경우
typescriptconst redis = new Redis({ host: '10.0.1.25', // EC2 프라이빗 IP port: 6379, });AWS ElastiCache 사용 시
typescriptconst redis = new Redis({ host: 'audigo-redis.xxxxx.ng.0001.apn2.cache.amazonaws.com', // ElastiCache 엔드포인트 port: 6379, });
도메인 연결이 되나요?
기술적으로는 가능하지만 일반적으로 하지 않습니다.
이유:
- Redis는 브라우저가 접근하는 서비스가 아니라 서버끼리 내부 통신하는 저장소
- 외부에 노출할 필요가 없고 오히려 보안상 위험
- 프라이빗 IP나 ElastiCache 엔드포인트로 충분
❌ redis.audigo.im:6379 → 외부 노출, 보안 위험 ✅ 10.0.1.25:6379 → VPC 내부 통신, 안전
Redis 시작/중지
EC2에 직접 설치한 경우
bash# 시작 sudo systemctl start redis # 중지 sudo systemctl stop redis # 재시작 sudo systemctl restart redis # 상태 확인 sudo systemctl status redis # EC2 재부팅 시 자동 시작 등록 sudo systemctl enable redis # 자동 시작 해제 sudo systemctl disable redis동작 확인
bash# ping 테스트 redis-cli ping # PONG 이 나오면 정상 # 직접 접속 redis-cli # 저장된 키 목록 확인 redis-cli keys *PM2로 관리하는 방법 (선택사항)
bash# PM2로 Redis 실행 (systemctl 대신) pm2 start redis-server --name redis pm2 save현재 Audigo는 Node.js를 PM2로 관리하고 있으니 systemctl로 Redis를 별도 관리하는 것이 더 명확합니다.
Files hidden in shared chats
공유 세션 저장소라고 했는데 EC2 가 2대밖에 없으면 그 중 하나에 redis 를 생성하고 다른 EC2에 있는 was가 private IP 로 접속해서 데이터를 공유하는 거야?
3월 24일네, 정확합니다.
EC2-1 (Node.js WAS + Redis) - Redis: 127.0.0.1:6379 로 자기 자신에 접속 EC2-2 (Node.js WAS) - Redis: 10.0.1.xx:6379 (EC2-1의 프라이빗 IP) 로 접속두 서버 모두 EC2-1의 Redis에 접속해서 세션 데이터를 공유합니다.
주의사항
1. Redis 외부 접속 허용 설정
EC2-1의 Redis는 기본적으로 127.0.0.1만 허용합니다. EC2-2에서 접속하려면 설정 변경이 필요합니다.
bash# EC2-1에서 redis.conf 수정 sudo vi /etc/redis/redis.conf # 아래 줄 수정 bind 127.0.0.1 → bind 0.0.0.0bashsudo systemctl restart redis2. EC2 Security Group에 6379 포트 오픈
EC2-2에서 EC2-1의 6379 포트로 접근할 수 있도록:
bashaws ec2 authorize-security-group-ingress \ --group-id sg-EC2-1의SG \ --protocol tcp \ --port 6379 \ --source-group sg-EC2-2의SG \ --region ap-northeast-2
단점
EC2-1 장애 발생 ↓ Redis도 같이 다운 ↓ EC2-2도 세션 조회 불가 → 서비스 장애초기 단계에서는 이 구조로 충분하지만 운영 안정성이 중요해지면 ElastiCache로 분리하는 것을 권장합니다.
현재 AWS에서 각각 다른 가용영역인 ap-northeast-2a, ap-northeast-2b의 private subnet에 배치한 ec2 인스턴스 2개가 서로 파일을 주고 받을 수 있는지 확인하고 싶어. 어떻게 할까?
4월 2일단계별로 확인해보겠습니다.
1. 각 EC2의 프라이빗 IP 확인
bashaws ec2 describe-instances \ --instance-ids i-09fe7a516d412b0bd i-0cc3535cc3a2b2849 \ --region ap-northeast-2 \ --query "Reservations[*].Instances[*].[InstanceId,PrivateIpAddress,Placement.AvailabilityZone]" \ --output table
2. EC2-1에서 EC2-2로 ping 테스트
EC2-1에 접속 후:
bash# EC2-2의 프라이빗 IP로 ping ping 10.0.x.x -c 4
3. 파일 전송 테스트 (scp)
EC2-1에서 EC2-2로 파일 전송:
bash# 테스트 파일 생성 echo "hello from EC2-1" > test.txt # EC2-2로 전송 scp -i ~/.ssh/your-key.pem test.txt ubuntu@10.0.x.x:/home/ubuntu/
4. Security Group 확인 (ping/scp 안 될 경우)
bash# EC2-2의 SG에 EC2-1에서 오는 트래픽 허용 여부 확인 aws ec2 describe-security-groups \ --group-ids sg-EC2-2의SG \ --region ap-northeast-2 \ --query "SecurityGroups[0].IpPermissions" \ --output json
통신 가능 조건 체크리스트
□ 두 EC2가 같은 VPC에 있는가? □ Security Group 인바운드에 상대방 SG 또는 IP가 허용되어 있는가? □ ICMP (ping) 허용되어 있는가? □ SSH (22번 포트) 허용되어 있는가?같은 VPC 내 private subnet 간 통신은 기본적으로 가능하지만 Security Group 설정에 따라 막힐 수 있습니다. ping 결과를 보여주시면 바로 원인 찾겠습니다!
Files hidden in shared chats
PING 10.1.4.35 (10.1.4.35) 56(84) bytes of data. --- 10.1.4.35 ping statistics --- 4 packets transmitted, 0 received, 100% packet loss, time 3093ms => 0 received 면 서로 접속이 안된다는 거야?
4월 2일네, 맞습니다. 패킷이 100% 손실되고 있어서 현재 두 EC2가 통신이 안 되는 상태입니다.
원인 확인
1. Security Group에 ICMP(ping) 허용 여부 확인
bash# EC2-2 (10.1.4.35) 의 Security Group ID 확인 aws ec2 describe-instances \ --filters "Name=private-ip-address,Values=10.1.4.35" \ --region ap-northeast-2 \ --query "Reservations[0].Instances[0].SecurityGroups" \ --output tablebash# 해당 SG의 인바운드 규칙 확인 aws ec2 describe-security-groups \ --group-ids sg-XXXX \ --region ap-northeast-2 \ --query "SecurityGroups[0].IpPermissions" \ --output json
2. ICMP 허용 규칙 추가 (ping 허용)
bashaws ec2 authorize-security-group-ingress \ --group-id sg-EC2-2의SG \ --protocol icmp \ --port -1 \ --source-group sg-EC2-1의SG \ --region ap-northeast-2
3. 파일 전송용 SSH 포트도 허용
bashaws ec2 authorize-security-group-ingress \ --group-id sg-EC2-2의SG \ --protocol tcp \ --port 22 \ --source-group sg-EC2-1의SG \ --region ap-northeast-2
규칙 추가 후 다시 ping 테스트해보세요. SG 확인 결과를 보여주시면 더 정확히 찾겠습니다!
Files hidden in shared chats
더 보기[ { "IpProtocol": "tcp", "FromPort": 3000, "ToPort": 3005, "UserIdGroupPairs": [ { "Description": "LoadBalancer to EC2", "UserId": "508153279153", "GroupId": "sg-09877e1099e4c42a2" } ], "IpRanges": [], "Ipv6Ranges": [], "PrefixListIds": [] }, { "IpProtocol": "tcp", "FromPort": 22, "ToPort": 22, "UserIdGroupPairs": [], "IpRanges": [ { "CidrIp": "0.0.0.0/0" } ], "Ipv6Ranges": [], "PrefixListIds": [] }, { "IpProtocol": "tcp", "FromPort": 5433, "ToPort": 5433, "UserIdGroupPairs": [], "IpRanges": [ { "Description": "RDS to EC2", "CidrIp": "10.0.0.0/16" } ], "Ipv6Ranges": [], "PrefixListIds": [] } ] 란 결과가 나왔어.
4월 2일현재 인바운드 규칙을 보면 EC2 간 통신 허용 규칙이 없습니다.
현재 규칙 분석
포트허용 대상용도3000-3005 ALB SG (sg-09877e1099e4c42a2) ALB → EC2 22 0.0.0.0/0 (전체) SSH 5433 10.0.0.0/16 RDS EC2-1 → EC2-2 통신 허용 규칙이 없어서 ping이 100% 손실된 것입니다.
EC2-1의 SG ID 확인
bashaws ec2 describe-instances \ --instance-ids i-09fe7a516d412b0bd \ --region ap-northeast-2 \ --query "Reservations[0].Instances[0].SecurityGroups" \ --output table
EC2 간 통신 허용 규칙 추가
bash# ICMP (ping) 허용 aws ec2 authorize-security-group-ingress \ --group-id sg-EC2-2의SG \ --protocol icmp \ --port -1 \ --source-group sg-EC2-1의SG \ --region ap-northeast-2 # SSH (파일전송 scp) 허용 aws ec2 authorize-security-group-ingress \ --group-id sg-EC2-2의SG \ --protocol tcp \ --port 22 \ --source-group sg-EC2-1의SG \ --region ap-northeast-2EC2-1의 SG ID 확인 후 알려주시면 정확한 명령어로 완성해드리겠습니다!
sg-03f8ec185b8210564 | audigo-server-security-group
4월 2일EC2-1의 SG ID가 확인됐습니다. 아래 명령어를 실행하세요.
ICMP (ping) 허용
bashaws ec2 authorize-security-group-ingress \ --group-id sg-03f8ec185b8210564 \ --protocol icmp \ --port -1 \ --source-group sg-03f8ec185b8210564 \ --region ap-northeast-2SSH (scp 파일전송) 허용
bashaws ec2 authorize-security-group-ingress \ --group-id sg-03f8ec185b8210564 \ --protocol tcp \ --port 22 \ --source-group sg-03f8ec185b8210564 \ --region ap-northeast-2두 EC2가 같은 SG(audigo-server-security-group)를 사용하고 있어서 자기 자신의 SG를 소스로 지정하면 됩니다. 이렇게 하면 같은 SG를 가진 EC2끼리 통신이 허용됩니다.
적용 후 다시 ping 테스트해보세요!
bashping 10.1.4.35 -c 4잘 되는데 문제는 보안 그룹 ID : sg-09877e1099e4c42a2 에 대해 22port 가 소스: 0.0.0.0/0 로 모든 외부에서 접근 가능한 보안상 위험한 상태가 되는데 private subnet 영역에 있는 EC2끼리만 서로 접근가능하도록 해야해!
4월 2일맞습니다. 현재 22포트가 전체 오픈되어 있는 것은 보안상 위험합니다. 아래 순서로 수정하세요.
1. 기존 22포트 전체 오픈 규칙 제거
bashaws ec2 revoke-security-group-ingress \ --group-id sg-03f8ec185b8210564 \ --protocol tcp \ --port 22 \ --cidr 0.0.0.0/0 \ --region ap-northeast-2
2. 같은 SG끼리만 22포트 허용 (EC2 간 통신만 허용)
bashaws ec2 authorize-security-group-ingress \ --group-id sg-03f8ec185b8210564 \ --protocol tcp \ --port 22 \ --source-group sg-03f8ec185b8210564 \ --region ap-northeast-2
3. 적용 후 확인
bashaws ec2 describe-security-groups \ --group-ids sg-03f8ec185b8210564 \ --region ap-northeast-2 \ --query "SecurityGroups[0].IpPermissions" \ --output json22포트 소스가 0.0.0.0/0 대신 sg-03f8ec185b8210564 로 바뀌어 있으면 정상입니다.
주의사항
22포트를 닫으면 로컬 PC에서 직접 SSH 접속이 불가합니다. EC2 접속은 AWS Systems Manager Session Manager 를 통해서만 가능하게 됩니다. 현재 Session Manager가 설정되어 있으니 문제없이 접속 가능합니다.
Files hidden in shared chats
이미 규칙이 있어. 해당 규칙을 확인하는 명령어를 알 수 있을까?
4월 2일bashaws ec2 describe-security-groups \ --group-ids sg-03f8ec185b8210564 \ --region ap-northeast-2 \ --query "SecurityGroups[0].IpPermissions[?FromPort==\`22\`]" \ --output json22포트에 대한 규칙만 필터링해서 보여줍니다. 현재 어떤 소스들이 등록되어 있는지 확인할 수 있습니다.
더 보기[ { "IpProtocol": "tcp", "FromPort": 22, "ToPort": 22, "UserIdGroupPairs": [ { "Description": "EC2 to EC2", "UserId": "508153279153", "GroupId": "sg-03f8ec185b8210564" } ], "IpRanges": [], "Ipv6Ranges": [], "PrefixListIds": [] } ] 이런 결과가 나와.
4월 2일이미 올바르게 설정되어 있습니다.
소스: sg-03f8ec185b8210564 (자기 자신 SG) 설명: EC2 to EC2같은 SG를 가진 EC2끼리만 22포트 접근 가능한 상태입니다. 0.0.0.0/0 규칙도 없으니 외부에서 직접 SSH 접속은 차단된 보안상 안전한 상태입니다.
따로 수정할 필요 없이 현재 설정이 정확합니다. ✅
Files hidden in shared chats
더 보기[ { "IpProtocol": "tcp", "FromPort": 3000, "ToPort": 3005, "UserIdGroupPairs": [ { "Description": "LoadBalancer to EC2", "UserId": "508153279153", "GroupId": "sg-09877e1099e4c42a2" } ], "IpRanges": [], "Ipv6Ranges": [], "PrefixListIds": [] }, { "IpProtocol": "tcp", "FromPort": 22, "ToPort": 22, "UserIdGroupPairs": [ { "Description": "EC2 to EC2", "UserId": "508153279153", "GroupId": "sg-03f8ec185b8210564" } ], "IpRanges": [], "Ipv6Ranges": [], "PrefixListIds": [] }, { "IpProtocol": "icmp", "FromPort": -1, "ToPort": -1, "UserIdGroupPairs": [ { "UserId": "508153279153", "GroupId": "sg-03f8ec185b8210564" } ], "IpRanges": [], "Ipv6Ranges": [], "PrefixListIds": [] }, { "IpProtocol": "tcp", "FromPort": 5433, "ToPort": 5433, "UserIdGroupPairs": [], "IpRanges": [ { "Description": "RDS to EC2", "CidrIp": "10.0.0.0/16" } ], "Ipv6Ranges": [], "PrefixListIds": [] } ] 이렇게 결과가 나오면 모두 세팅 완료된건가?
4월 2일네, 모두 올바르게 설정되어 있습니다. ✅
현재 규칙 정리
포트프로토콜소스용도3000-3005 TCP sg-09877e1099e4c42a2 (ALB SG) ALB → EC2 트래픽 22 TCP sg-03f8ec185b8210564 (자기 SG) EC2 ↔ EC2 SSH/SCP ICMP - sg-03f8ec185b8210564 (자기 SG) EC2 ↔ EC2 ping 5433 TCP 10.0.0.0/16 (VPC 내부) RDS 접근
보안 관점에서도 양호
- 외부(0.0.0.0/0)에서 직접 접근 가능한 포트 없음 ✅
- ALB를 통해서만 앱 포트 접근 가능 ✅
- EC2 간 통신은 같은 SG끼리만 허용 ✅
- RDS는 VPC 내부에서만 접근 가능 ✅
ping test를 하면 응답을 잘 받아. 지금까지 변경된 사항이 뭔지 확인할 수 있게 요약 부탁해.
4월 2일오늘 변경된 Security Group 규칙 요약
추가된 규칙 (sg-03f8ec185b8210564 / audigo-server-security-group)
포트프로토콜소스설명22 TCP sg-03f8ec185b8210564 EC2 ↔ EC2 SSH/SCP 통신 ICMP - sg-03f8ec185b8210564 EC2 ↔ EC2 ping 통신
제거된 규칙
포트프로토콜소스설명22 TCP 0.0.0.0/0 외부 전체 SSH 접근 (보안 위험)
변경 목적
Before: 외부 어디서든 SSH 접근 가능 (보안 취약) After: 같은 SG를 가진 EC2끼리만 통신 가능 (보안 강화)
최종 인바운드 규칙 현황
포트소스용도3000-3005 ALB SG ALB → EC2 앱 트래픽 22 자기 SG EC2 ↔ EC2 SSH/SCP ICMP 자기 SG EC2 ↔ EC2 ping 5433 10.0.0.0/16 RDS 접근 Files hidden in shared chats
최종 인바운드 규칙 현황에서 ICMP port 를 왜 오픈하지? 이 port 는 오픈하지 않아도 22번 포트가 열려있어서 EC2끼리 파일을 주고 받는 것에 전혀 문제없는 것 같아.
4월 3일맞습니다. 파일 전송(SCP)은 22번 포트만 있으면 충분합니다.
ICMP는 단순히 ping 테스트 용도로만 사용되고 실제 서비스나 파일 전송과는 무관합니다. 필요 없다면 제거하는 것이 보안상 더 좋습니다.
bashaws ec2 revoke-security-group-ingress \ --group-id sg-03f8ec185b8210564 \ --protocol icmp \ --port -1 \ --source-group sg-03f8ec185b8210564 \ --region ap-northeast-2제거 후 ping은 안 되지만 SCP 파일 전송은 정상적으로 동작합니다.
aws ssm start-session \ --target i-0399060e28e3fa091 \ --document-name AWS-StartPortForwardingSession \ --parameters '{"portNumber":["22"],"localPortNumber":["9922"]}' 이 방식으로 sftp 를 접근하려고 하면 filezilla 에서 timeout 오류가 발생해. 뭐부터 체크해볼까?
4월 13일단계별로 확인해보겠습니다.
1. SSM 포트포워딩 세션이 실제로 열렸는지 확인
bash# 세션 시작 후 아래 명령어로 로컬 9922 포트 리스닝 확인 netstat -an | grep 9922 # 또는 ss -tlnp | grep 99229922 포트가 LISTENING 상태여야 합니다.
2. FileZilla 설정 확인
항목값프로토콜 SFTP 호스트 127.0.0.1 포트 9922 로그온 유형 키 파일 사용자 ubuntu (또는 ec2-user) 키 파일 .pem 파일 경로
3. EC2 Security Group에 SSM에서 오는 22포트 허용 여부 확인
현재 22포트가 같은 SG끼리만 허용되어 있는데 SSM은 다른 경로로 접근합니다.
bashaws ec2 describe-security-groups \ --group-ids sg-03f8ec185b8210564 \ --region ap-northeast-2 \ --query "SecurityGroups[0].IpPermissions[?FromPort==\`22\`]" \ --output json
4. SSM Agent 실행 상태 확인
EC2에 Session Manager로 접속 후:
bashsudo systemctl status amazon-ssm-agent
가장 흔한 원인
SSM 포트포워딩은 22포트를 SSM 터널을 통해 우회하는 방식이라 EC2 SG의 22포트 규칙과 무관하게 동작합니다. 따라서 FileZilla 설정에서 호스트가 127.0.0.1, 포트가 9922 로 정확히 설정되어 있는지 먼저 확인해보세요.
FileZilla 오류 메시지 전체를 보여주시면 더 정확히 찾겠습니다!
내 컴퓨터에서는 잘되는데 팀원 컴퓨터에서 timeout 이 발생해.
4월 13일팀원 컴퓨터 환경 문제입니다. 아래 순서로 확인해보세요.
1. AWS CLI 설치 및 인증 확인
bashaws --version aws sts get-caller-identity인증이 안 되어 있으면 SSM 세션 자체가 열리지 않습니다.
2. SSM Plugin 설치 여부 확인
SSM 포트포워딩은 Session Manager Plugin 이 별도로 설치되어 있어야 합니다.
bashsession-manager-plugin --version설치 안 되어 있으면:
bash# Windows # https://s3.amazonaws.com/session-manager-downloads/plugin/latest/windows/SessionManagerPluginSetup.exe # Mac brew install --cask session-manager-plugin # Ubuntu curl "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/ubuntu_64bit/session-manager-plugin.deb" -o "session-manager-plugin.deb" sudo dpkg -i session-manager-plugin.deb
3. 포트포워딩 세션이 실제로 열렸는지 확인
팀원 PC에서 SSM 명령어 실행 후:
bash# Windows netstat -an | findstr 9922 # Mac/Linux ss -tlnp | grep 9922LISTENING 상태가 아니면 세션이 안 열린 것입니다.
4. IAM 권한 확인
팀원 IAM 계정에 SSM 권한이 있는지 확인:
bashaws iam list-attached-user-policies \ --user-name 팀원IAM계정이름 \ --output table필요한 권한:
json{ "Action": [ "ssm:StartSession", "ssm:TerminateSession", "ssm:DescribeSessions" ] }
체크리스트
□ AWS CLI 설치 및 인증 완료 □ Session Manager Plugin 설치 완료 □ IAM 계정에 SSM 권한 있음 □ SSM 명령어 실행 후 9922 포트 LISTENING 상태 □ FileZilla 호스트: 127.0.0.1, 포트: 9922팀원 PC에서 session-manager-plugin --version 결과가 어떻게 나오는지 확인해보세요!
localhost 와 127.0.0.1 의 차이입니다.
현대 OS(특히 Windows)에서 localhost 는 IPv6 주소 ::1을 먼저 조회하는 경우가 있습니다.
내 PC: localhost → 127.0.0.1 (IPv4) 로 해석 → 정상 ✅ 팀원 PC: localhost → ::1 (IPv6) 로 해석 → 연결 실패 ❌ 127.0.0.1 (IPv4) 로 직접 입력 → 정상 ✅SSM 포트포워딩이 IPv4(127.0.0.1)로만 열려있는데 팀원 PC가 localhost를 IPv6로 해석해서 불일치가 발생하는 겁니다.
팀원 PC에서 확인
bash# localhost 가 어떤 IP로 해석되는지 확인 ping localhost::1 이 나오면 IPv6로 해석되는 것입니다.
해결 방법
방법 1: FileZilla에서 127.0.0.1 사용 (가장 간단)
그냥 127.0.0.1 을 쓰도록 팀원에게 안내하면 됩니다.
방법 2: hosts 파일 수정
# Windows: C:\Windows\System32\drivers\etc\hosts # Mac/Linux: /etc/hosts 127.0.0.1 localhost # IPv4 우선으로 고정방법 1이 가장 간단하니 127.0.0.1 사용을 권장합니다.
내 pc에서는 ping localhost 하면 Ping Jays-Gram17 [::1] 32바이트 데이터 사용: ::1의 응답: 시간<1ms ::1의 응답: 시간<1ms ::1의 응답: 시간<1ms ::1의 응답: 시간<1ms 나오고 팀원 pc 에서는 127.0.0.1 ... 이라고 나오는데?
4월 13일반대네요! 다시 정리하면:
내 PC: localhost → ::1 (IPv6) → FileZilla에서 localhost 로 접속 ✅ 팀원 PC: localhost → 127.0.0.1 (IPv4) → FileZilla에서 localhost 접속 ❌ 127.0.0.1 로 직접 입력 시 ✅
원인
SSM 포트포워딩이 두 PC에서 서로 다른 주소로 바인딩되고 있는 겁니다.
내 PC: SSM → ::1:9922 (IPv6) 로 열림 → localhost(::1) 로 접속 성공 팀원 PC: SSM → 127.0.0.1:9922 (IPv4) 로 열림 → localhost(::1) 로 접속 실패 127.0.0.1 로 접속 성공
결론
동작은 하고 있으니 팀원은 그냥 127.0.0.1 을 사용하면 됩니다. 둘 다 정상이고 단지 OS의 localhost 해석 방식이 다를 뿐입니다.
FileZilla 설정을 팀원용으로 호스트: 127.0.0.1 로 저장해두면 매번 신경 쓸 필요 없습니다.
'AWS' 카테고리의 다른 글
S3 버킷으로 React 프로젝트를 배포하는 방법 (1) 2026.04.15 AWS storage 변경 : SSD 볼륨 8G ⇒ 15G 확장 후 linux 서버에서 expanded-volume 적용하기 (0) 2025.12.04