이전 글에서는 홈서버에 Docker와 Docker Compose를 설치하고, 여러 self-hosted 서비스들을 올려봤다.
여기까지 하면 홈서버 안에서는 서비스가 잘 돈다.
http://xn--ip-v41jw5m:3000
http://xn--ip-v41jw5m:3001
http://xn--ip-v41jw5m:8081하지만 실제로 쓰다 보면 금방 다음 문제가 생긴다.
집 밖에서도 접속하고 싶다. 접속하려면, 어떻게 안전하게 열어야 하지?
self-hosted 서비스들 중에는 자체 보안 기능이 있는 것도 있지만, 그렇지 않은 것도 있다. 제공해주더라도 서비스마다 따로 관리하는 게 꽤 번거롭다.
같은 비밀번호를 반복해서 쓰기는 찜찜하고, 서비스마다 다르게 두자니 관리가 귀찮다 🫠
처음에는 단순히 공유기에서 포트포워딩만 하면 되는 줄 알았다. 기존에도 내 로컬 서버를 외부에서 접근하게 하려면 ngrok 이나 포트포워딩을 썼으니까.
그런데 막상 홈서버를 운영할 생각으로 접근하니 생각보다 고려할 것이 많았다.
이런 점들 때문에 Cloudflare를 사용하기로 결정했고, Cloudflare를 중심으로 홈서버를 구성했다.
내가 현재 사용하는 구조는 대략 이렇다.
사용자 브라우저
↓
Cloudflare DNS / HTTPS
↓
Cloudflare Tunnel
↓
Nginx Proxy Manager
↓
Docker 서비스들이 구조의 장점은 역할이 분리된다는 점이다.
Cloudflare는 외부 입구를 맡고, Tunnel은 집 안 서버까지 안전하게 연결하고, Nginx Proxy Manager는 홈서버 내부에서 각 서비스로 분기한다.
Cloudflare는 사용자와 서버 사이에 위치하는 글로벌 네트워크다.
여러 큰 서비스들도 사용하고 있고, 개인이 쓰기에도 생각보다 기능이 많다.
Cloudflare는 전 세계 여러 도시에 서버를 두고 있다.
정적 리소스는 사용자와 가까운 위치에서 더 빠르게 응답할 수 있고, 원본 서버 부하도 줄일 수 있다. 물론 모든 요청이 무조건 캐시되는 것은 아니지만, 적어도 사용자 → 바로 우리 집 서버 구조보다는 앞단이 한 번 정리된다.
Cloudflare는 빠르고 안정적인 DNS도 제공한다.
추가로, 아래에서 설명할 기능들을 제대로 쓰려면 도메인 관리 주체를 Cloudflare로 옮기는 게 사실상 편하다.
이건 Cloudflare 의존도가 커지는 선택이긴 하다. 그래도 직접 전부 구축하는 것보다, 빠르게 안정적으로 운영하는 쪽을 택했다.
Cloudflare를 쓰면 홈서버에서 외부 공개용 HTTPS 인증서를 직접 관리하지 않아도 된다.
즉,
이런 운영 부담이 꽤 줄어든다.
내 현재 구성에서도 외부 HTTPS는 Cloudflare가 처리하고, Nginx Proxy Manager는 내부 라우팅 역할에 더 집중하고 있다.
서비스를 외부에 열면 생각보다 별의별 요청이 많이 들어온다.
예를 들면:
GET /.env같은 것들이다.
이걸 전부 직접 막으려면 nginx 설정, 방화벽, 리다이렉트, 필터링 등 챙길 게 많다. Cloudflare는 이런 앞단 보안 기능도 어느 정도 제공한다.
다만 사용 가능한 범위와 세부 정책은 요금제에 따라 다를 수 있다.
Cloudflare Tunnel, Cloudflare Access처럼 개인이 직접 구현하면 꽤 번거로운 기능들도 같이 제공한다.
내 경우에는 사실 이 두 가지 때문에 Cloudflare를 중심으로 구조를 짰다고 봐도 된다.
도메인을 이미 등록기관에서 구매해서 쓰고 있다면, 대략 순서는 이렇다.
1. Cloudflare에 도메인 추가
2. Cloudflare가 제시하는 NS 2개 확인
3. 도메인 등록기관에서 네임서버를 Cloudflare NS로 변경
4. Cloudflare에서 Active 상태 확인
5. 이후 DNS 레코드는 Cloudflare에서 관리일반적인 요청의 형태는
사용자 → 내 도메인 → Public IP → 포트포워딩 → 홈서버
구조다.
즉, 외부에서 우리 집 서버로 직접 들어오는 inbound 요청이다.
하지만 Tunnel을 사용하면 방향이 조금 달라진다.
홈서버 → Cloudflare Tunnel → Cloudflare
사용자 → Cloudflare → Tunnel → 홈서버핵심은, 홈서버가 먼저 Cloudflare와 연결을 맺는다는 점이다.
이 방식의 장점은 꽤 크다.
물론 서비스 자체의 인증, 권한, 취약점 관리는 여전히 별개다. Tunnel을 쓴다고 보안 문제가 사라지는 건 아니지만, 적어도 집 서버를 인터넷에 바로 드러내는 부담은 크게 줄어든다.
Cloudflare 대시보드에서 Tunnel을 만든 뒤, 홈서버에서는 cloudflared 컨테이너를 띄우는 방식으로 붙였다.

services:
cloudflared:
image: cloudflare/cloudflared:latest
container_name: cloudflared
restart: unless-stopped
command: tunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN}
networks:
- homelab
networks:
homelab:
external: true실제 토큰 값은 당연히 코드나 글에 그대로 남기면 안 된다. 운영할 때도 .env나 별도 secret 파일로 분리하는 것이 좋다.

컨테이너 상태에서 healthy가 보이면 연결이 잘 된 상태다.
현재 SSH는 내부 네트워크에서만 연결이 가능하다.
이를 Cloudflare Tunnel을 통해 외부에서도 접근할 수 있게 해줄 수 있다.
Host homeserver
HostName {Route 경로}
User {로그인할 사용자명}
IdentityFile ~/.ssh/{Private Key 파일명}
IdentitiesOnly yes
ProxyCommand cloudflared access ssh --hostname %h.ssh/config에 이런 식으로 넣어두면 된다.
HostName: 실제 접근할 경로ProxyCommand: Cloudflare Access 인증을 거쳐 SSH 연결 수행이 방식에서는 SSH가 TCP 포트를 직접 여는 대신, cloudflared 프로세스를 통해 터널링된다. 즉 홈서버에 공인 IP를 직접 노출하거나 SSH 포트를 외부에 열지 않아도 된다.
Cloudflare Tunnel에서 각 서비스를 직접 연결할 수도 있다.
예를 들면 이런 식이다.
home.example.com → http://homepage:3000
files.example.com → http://filebrowser:80처음에는 이 방식도 단순해 보였다. 그런데 서비스가 늘어나면 Cloudflare 쪽에서 외부 라우팅을 일일이 관리해야 한다.
나는 내부 라우팅을 한 곳에서 관리하고 싶어서, 중간에 Nginx Proxy Manager(NPM)를 두는 쪽을 선택했다.
Node Package Manager가 아니다.
services:
npm:
image: jc21/nginx-proxy-manager:latest
container_name: npm
restart: unless-stopped
ports:
- "80:80"
- "81:81"
- "443:443"
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
networks:
- homelab
networks:
homelab:
external: true여기서 몇 가지를 같이 이해하면 편하다.
ports
volumes
/data는 실제 설정/DB 저장용/etc/letsencrypt는 NPM 인증서 저장용인데, 내 현재 구성에서는 사실상 예비용에 가깝다networks
homelab은 여러 컨테이너가 함께 붙는 공용 네트워크다external: true는 이 compose가 새로 만드는 네트워크가 아니라, 이미 존재하는 외부 네트워크를 붙여 쓰겠다는 뜻이다즉 NPM을 기반으로 이렇게 둘 수 있다.
home.example.com → http://npm:80
files.example.com → http://npm:80
status.example.com → http://npm:80
admin.example.com → http://npm:80그리고 NPM 내부에서 다시 서비스별로 라우팅한다.
home.example.com → homepage:3000
files.example.com → filebrowser:80
status.example.com → uptime-kuma:3001
admin.example.com → youngsu-blog-admin:3000이렇게 두면 역할이 훨씬 깔끔해진다.
그리고 NPM이 편한 점도 분명하다.
http://컨테이너이름:포트 식으로 연결할 수 있다나는 그래서 Cloudflare를 외부 입구, NPM을 내부 안내 데스크처럼 두고 쓴다.
Cloudflare Tunnel을 붙이면 외부 연결은 정말 쉬워진다. 하지만 쉬워진 만큼 민감한 서비스도 쉽게 외부에 노출될 수 있다는 뜻이다.
그래서 민감한 서비스 앞에는 일종의 보호장치가 필요하다.
이를 해결해주는 게 Cloudflare Access다.
예를 들어 이런 도메인에 적용할 수 있다.
files.example.com
admin.example.com
docs.example.com
batch.example.com
sync.example.com접속 흐름은 이렇게 된다.
사용자
↓
https://admin.example.com
↓
Cloudflare Access 인증
↓
인증 통과
↓
Cloudflare Tunnel
↓
NPM
↓
admin 서비스즉 서비스 자체 로그인보다 앞단에서 한 번 더 “너 누구냐”를 확인하는 셈이다.
Cloudflare Access는 여러 로그인 방식을 지원한다.
그 중에 나는 Google OAuth를 사용했다. 구글 계정 기반이 관리하기도 편하고, 나중에 특정 사람들만 허용하기도 쉬울 것 같았기 때문이다.
대략 흐름은 다음과 같다.
1. Cloudflare Zero Trust에서 Team domain 확인
2. Google Cloud Console에서 OAuth Client 생성
3. Authorized redirect URI 등록
4. Cloudflare Zero Trust에 Google Login method 추가
5. Access Application 생성
6. Allow policy에 내 이메일만 추가Cloudflare에서 Zero Trust로 들어가면 팀 도메인을 확인할 수 있다. 이 값은 뒤에서 Google OAuth redirect URI에 들어간다.

Google Cloud Console에서 API 및 서비스 → 사용자 인증 정보로 들어간 다음, OAuth 클라이언트 ID를 만든다.

애플리케이션 유형은 웹 애플리케이션으로 선택했다.
앞에서 확인한 팀 도메인을 기준으로 redirect URI를 등록한다.
https://<team-domain>.cloudflareaccess.com/cdn-cgi/access/callback
이 값을 정확히 넣어야 한다. 여기서 틀리면 redirect_uri_mismatch 같은 오류가 난다.
Cloudflare Zero Trust에서 Google을 로그인 방식으로 추가한다.

이때 Google에서 발급받은:
을 입력하면 된다.

중간에 UI가 바뀌어서 메뉴를 찾기 조금 헷갈릴 수 있다.
이제 실제 보호할 애플리케이션을 만든다.

예를 들어 home.example.today를 보호하고 싶다면:
homeexample.today
로 지정하면 된다.
특정 경로에만 적용하고 싶다면 path 단위로도 걸 수 있다.
마지막으로 허용 정책을 만든다.

예를 들어:
이런 식으로 설정할 수 있다.

브라우저에서 접속했을 때 Google 로그인 화면이나 to continue to cloudflareaccess.com 류의 안내가 뜨면 정상적으로 붙은 것이다.

홈서버를 만들고 Docker 서비스를 올리는 것까지는 꽤 재미있는 작업이었다.
하지만 도메인을 붙이고 인터넷에서 접근 가능하게 만드는 순간, 보안과 관리 기준이 필요해진다.
나에게 이 경계를 잡아준 게 Cloudflare의 기능들이었다.
Cloudflare Tunnel
→ 집 서버를 직접 열지 않고 외부에서 접근하게 해준다.
Nginx Proxy Manager
→ 여러 Docker 서비스를 도메인별로 나눠준다.
Cloudflare Access
→ 서비스 앞에 특정 사용자만 접근 가능한 인증 장벽을 둔다.덕분에 홈서버는 단순히 집 안에서만 접속하는 장비가 아니라, 필요한 서비스는 밖에서도 쓰고 민감한 서비스는 제한할 수 있는 개인 인프라에 가까워졌다.
다음에는 홈서버에 Hermes Agent를 세팅하고, 실제로 어떻게 쓰고 있는지 정리해보려고 한다.