이 글은 지금 muabow.com이 어떤 식으로 외부에 열려 있는지, 그리고 그 과정에서 무엇을 설치하고 어떤 서비스가 실제로 돌아가고 있는지를 정리한 기록이다.
예전처럼 공유기 포트포워딩을 열어두는 방식 대신, 이번에는 Cloudflare Tunnel을 중심으로 구성했다.
그래서 공인 IP를 직접 다루지 않아도 되고, 라즈베리파이가 외부로 outbound 연결만 유지하면 웹과 SSH를 모두 붙일 수 있다.
지금 구성의 큰 흐름
현재 구조는 아래처럼 단순하다.
- 라즈베리파이에서
nginx가 정적 사이트를 서비스한다. cloudflared가 Cloudflare와 상시 연결을 유지한다.- 메인 도메인, 보조 도메인, 관리용 도메인 요청은 Cloudflare를 거쳐 라즈베리파이 내부 서비스로 전달된다.
- 웹, SSH, 관리 기능은 각각 내부 서비스로 연결된다.
즉 외부에서 보이는 것은 도메인뿐이고, 실제 내부 서비스는 라즈베리파이 로컬 포트에서만 동작한다.
실제 설치된 프로그램
라즈베리파이에서 확인한 주요 패키지는 아래와 같다.
cloudflared 2026.3.0
nginx 1.26.3-3+deb13u2
openssh-server 1:10.0p1-7+deb13u2
운영체제는 다음 환경이다.
Debian GNU/Linux 13 (trixie)
Linux kernel 6.12.75+rpt-rpi-2712
정리하면:
- 웹 서버:
nginx - 터널 데몬:
cloudflared - 원격 접속:
openssh-server
현재 실행 중인 서비스
실제 systemctl로 확인한 결과, 아래 서비스는 모두 active, enabled 상태였다.
nginx active / enabled
cloudflared active / enabled
ssh active / enabled
즉 재부팅 후에도 자동으로 살아나는 상태다.
이게 중요한 이유는 단순하다.
nginx가 죽으면 웹이 안 열린다.cloudflared가 죽으면 도메인이 있어도 외부 연결이 끊긴다.ssh가 죽으면 원격 관리가 막힌다.
Cloudflare Tunnel 설정
현재 터널 설정은 대략 이런 구조다.
ingress:
- hostname: <main-domain>
service: http://localhost:<web-port>
- hostname: <secondary-domain>
service: http://localhost:<web-port>
- hostname: <ssh-host>
service: ssh://localhost:<ssh-port>
- hostname: <admin-host>
service: http://localhost:<admin-port>
- service: http_status:404
이 설정이 의미하는 것은 아래와 같다.
- 메인 도메인 -> 웹 서버
- 보조 도메인 -> 웹 서버
- SSH 호스트 -> SSH 서비스
- 관리용 호스트 -> 별도 앱
마지막 http_status:404는 매칭되지 않은 요청을 정리하는 기본 규칙이다.
DNS는 어떻게 연결했는가
도메인은 Cloudflare DNS를 기준으로 연결했다.
핵심은 루트 도메인과 www를 모두 같은 Tunnel에 붙인 점이다.
- 메인 도메인
- 보조 도메인
그리고 SSH, 사진첩은 별도 호스트로 분리했다.
- SSH 호스트
- 관리용 호스트
이렇게 나눈 이유는 웹과 SSH를 같은 호스트명에 섞지 않기 위해서다.
역할을 분리해두면 나중에 관리할 때도 훨씬 덜 헷갈린다.
nginx는 무엇을 서비스하는가
현재 웹 루트는 아래 경로다.
정적 빌드 결과물 디렉터리
이 폴더는 직접 수정하는 곳이 아니라, 빌드 결과물이 모이는 위치다.
실제 운영 흐름은 이렇다.
- 원본 글을 Markdown으로 작성한다.
node scripts/build.js가 정적 HTML을 생성한다.- 결과물이
public에 쌓인다. publish.sh가 라즈베리파이로 동기화한다.nginx는public만 서비스한다.
이 구조의 장점은 꽤 명확하다.
- DB가 없다.
- 런타임이 단순하다.
- 장애 지점이 적다.
- 백업과 복구가 쉽다.
다만 지금은 정적 파일만 있는 것은 아니다.
/info는 Go 대시보드로 프록시된다./api/visits는 방문자 수 집계를 위한 작은 API다.
즉 바깥에서 보기에는 한 사이트지만, 내부적으로는 nginx가 정적 페이지와 작은 앱 경로를 함께 정리해주는 구조다.
외부 SSH는 어떻게 붙였는가
SSH도 포트포워딩 없이 Tunnel에 태웠다.
외부에서 직접 192.168.x.x 같은 내부 IP로 들어오는 것이 아니라,
SSH 전용 호스트를 통해 Cloudflare가 SSH 트래픽을 내부 서비스로 넘긴다.
내 Mac 쪽에서는 SSH 키 로그인까지 맞춰둬서, 지금은 배포 스크립트도 비밀번호 없이 돌아간다.
즉 흐름은 대략 이렇다.
- 로컬에서 SSH 클라이언트 실행
- Cloudflare Tunnel을 통과
- 라즈베리파이
ssh서비스 도착 - SSH 키 인증
실제로 자주 보는 명령
운영하면서 많이 보는 명령은 이 정도면 충분하다.
sudo systemctl status nginx
sudo systemctl status cloudflared
sudo systemctl status ssh
sudo journalctl -u cloudflared --no-pager -n 100
curl -I http://127.0.0.1
dig +short <public-domain>
문제가 생기면 보통 아래 순서로 본다.
nginx가 살아 있는지cloudflared가 살아 있는지- 도메인이 Tunnel로 잘 연결되는지
- 로컬 웹 서비스가 응답하는지
왜 이 구성을 선택했는가
이번 구성에서 가장 마음에 드는 건, 복잡한 공개 서버를 만들어 놓고 억지로 유지하는 느낌이 적다는 점이다.
라즈베리파이는 여전히 집 안의 작은 장비이고,
외부 공개는 Cloudflare가 앞에서 받아준다.
그래서 운영 부담은 비교적 낮고, 확장성은 생각보다 나쁘지 않다.
지금 단계에서는 이 정도 구성이 꽤 균형이 좋다.
- 개인 사이트 운영 가능
- SSH 원격 접속 가능
- 포트포워딩 불필요
- 정적 사이트라 관리가 단순함
복잡한 앱이나 데이터베이스는 나중에 정말 필요할 때 붙여도 늦지 않다.