Skip to content

fix(infra): 호스트 공개 포트를 루프백에만 바인딩 (BIND_ADDR) - #265

Merged
i3months merged 1 commit into
devfrom
fix/bind-ports-to-loopback
Sep 28, 2026
Merged

i3months merged 1 commit into
devfrom
fix/bind-ports-to-loopback

Conversation

@i3months

Copy link
Copy Markdown
Member

#252 에서 앞문만 닫았다

#252 에서 nginx 의 /ai/ 프록시를 지우며 "AI 서버(FastAPI)를 인터넷에 그대로 노출하고
있었다"
를 고쳤다고 적었다. AI 서버는 38030 에 그대로 publish 돼 있었다.

점검해 보니 AI 만이 아니다. 스택 포트 8개가 전부 0.0.0.0:

포트 서비스 포트 서비스
38010 Core 38040 PostgreSQL
38020 RealTime 38050 RabbitMQ
38030 AI 38051 RabbitMQ 관리 UI
38060 MinIO API
38061 MinIO 콘솔

docker-compose.yml 이 짧은 형식("PORT:PORT")을 써서 모든 인터페이스에 열렸다.
ufw 가 설치돼 있어도 소용없다 — 도커는 제 iptables 체인을 ufw 앞에 끼워 넣어
publish 한 포트를 그냥 통과시킨다.

측정한 것만 적는다

서버가 자기 공인 IP 로 부르면:

$ curl http://<public-ip>:38010/api/system/health
200

→ 호스트 방화벽은 막지 않는다.

외부(다른 네트워크)에서는 TCP 핸드셰이크만 간헐적으로 성립하고 HTTP 응답은 오지 않았다.
상류(학교망)가 걸러 주는 것으로 보인다.

그래서 "지금 당장 털린다"고는 말하지 않는다. 하지만 그 방어선은 우리 것이 아니고 바뀔 수
있다.
거기에 기대는 상태를 정상으로 두지 않는다.

필요한 곳이 하나도 없었다

nginx      → proxy_pass http://127.0.0.1:38010 / 127.0.0.1:38020   (그게 전부)
서비스 간   → 도커 네트워크 이름: backend:38010, minio:38060, rabbitmq:38050

BIND_ADDR(기본 127.0.0.1)로 전부 루프백에 묶었다. 기능 변화 없음.

  • 관리 콘솔은 SSH 터널로 계속 본다: ssh -L 38061:127.0.0.1:38061 stackup@stack-up.shop
  • LAN 노출이 필요하면 BIND_ADDR=0.0.0.0 으로 명시적으로 켠다 — 기본값이 아니라 선택이어야 한다

CI 가드

test_published_ports_bind_loopback_by_default. 이 한 줄은 지워져도 컴파일·테스트가 통과하고
운영에서도 조용하다.

가드가 실제로 회귀를 잡는지 확인했다 — BACKEND_PORT 매핑 하나를 짧은 형식으로 되돌리니
테스트가 실패했다. (통과만 하는 테스트는 테스트가 아니다.)

배포 시 주의

docker compose up -d 가 포트 매핑 변경을 반영하려면 컨테이너를 재생성한다. 배포 직후
ss -tlnp | grep 380 로 127.0.0.1: 로 바뀌었는지, 그리고 사이트가 정상인지 확인하겠다.

AI 전체 428건 통과.

#252 에서 nginx 의 /ai/ 프록시를 지우며 "AI 서버를 인터넷에 노출하고 있었다" 를
고쳤다고 적었다. 앞문만 닫았다 — **AI 서버는 38030 에 그대로 publish 돼 있었다.**

점검해 보니 AI 만이 아니었다. 스택 포트 8개가 전부 0.0.0.0 이다:

  38010 Core      38040 PostgreSQL
  38020 RealTime  38050 RabbitMQ      38051 RabbitMQ 관리 UI
  38030 AI        38060 MinIO API     38061 MinIO 콘솔

docker-compose 가 짧은 형식("PORT:PORT")을 써서 모든 인터페이스에 열렸다.
ufw 가 설치돼 있어도 소용없다 — 도커는 제 iptables 체인을 ufw 앞에 끼워 넣어
publish 한 포트를 그냥 통과시킨다(잘 알려진 함정).

## 측정한 것만 적는다

서버가 **자기 공인 IP** 로 부르면:

  curl http://<public>:38010/api/system/health  → 200

호스트 방화벽은 막지 않는다.

외부(다른 네트워크)에서는 TCP 핸드셰이크만 간헐적으로 성립하고 HTTP 응답은
오지 않았다. 상류(학교망)가 걸러 주는 것으로 보인다. 그건 우리 통제 밖이고
바뀔 수 있으므로, 거기에 기대는 상태를 정상으로 두지 않는다.

## 필요한 곳이 하나도 없었다

  nginx        → 127.0.0.1:38010 / 127.0.0.1:38020 로만 proxy_pass
  서비스 간     → 도커 네트워크 이름 (backend:38010, minio:38060, rabbitmq:38050)

BIND_ADDR(기본 127.0.0.1)로 전부 루프백에 묶었다. 관리 콘솔은 SSH 터널로 계속
볼 수 있다(-L 38061:127.0.0.1:38061). LAN 노출이 필요하면 BIND_ADDR=0.0.0.0 으로
명시적으로 켠다 — 기본값이 아니라 선택이어야 한다.

## CI 가드

test_published_ports_bind_loopback_by_default. 이 한 줄은 지워져도 컴파일·테스트가
통과하고 **운영에서도 조용하다.** 가드가 실제로 회귀를 잡는지 확인했다 —
BACKEND_PORT 매핑 하나를 짧은 형식으로 되돌리니 테스트가 실패했다.
@i3months
i3months merged commit f941370 into dev Sep 28, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant