이 페이지 조회 3회

홈페이지 제작기

오래된 NAS와 라즈베리파이로 개인 웹서버 구축하기

작업 기록을 남길 공간을 직접 만들어보기로 했다.

익명화 안내: 장비 별칭, 계정명, 도메인·서브도메인, IP 주소, 포트, 네트워크 프로필명, 개인 폴더·공유 폴더·프로젝트·서비스명, 저장 장치 경로와 홈페이지 이름은 모두 임의의 예시 정보입니다. 본문·표·코드 블록 전체에 적용됩니다. 실제 제조사·모델 및 버전은 생략하거나 임의 표기로 바꿨습니다. 제품명, 명령어, 표준 시스템 경로와 설정 키는 설명을 위해 유지했습니다. 명령은 실제 환경에 맞게 변경해야 합니다.

시작한 계기

작업 기록을 올려둘 홈페이지가 필요했다. 어떤 방식으로 만들고 운영할지 고민하다가, 집에 오래돼서 사용하지 않는 NAS와 라즈베리파이가 있다는 것이 떠올랐다. 새로운 서버를 구매하기보다 이 장비들을 이용해 웹서버를 구축해보면 좋겠다고 생각해서 시작했다.

목표는 자기소개와 포트폴리오, 공부한 내용을 계속 업데이트할 수 있는 개인 홈페이지를 만드는 것이었다. 여기에 브라우저로 파일을 올리고 내려받을 수 있는 개인 파일 클라우드도 함께 운영하고 싶었다.

홈페이지 이름은 ‘배움을 기록하는 개발자’로 정했다. 계속 배우고 직접 만들어가는 과정을 기록하는 공간이라는 의미를 담았다.

장비별 역할 정하기

NAS는 Archive Box라는 가상 이름으로 표기한 오래된 장비였다(임의 장비명, 실제 제조사·모델 생략). NAS에서 홈페이지와 각종 프로그램까지 실행하기보다는, 파일을 보관하는 저장소 역할에 집중시키기로 했다.

라즈베리파이(모델 정보 생략)는 홈페이지와 파일 서버 프로그램을 실행하는 역할을 맡았다. 기존 NAS 자료는 옮기거나 초기화하지 않고 공유 폴더를 연결해 그대로 사용한다.

구성 요소 역할
라즈베리파이(모델 정보 생략) FastAPI 홈페이지, File Browser, Cloudflare Tunnel 실행
기존 NAS(제조사 정보 생략) 기존 자료와 업로드 파일 보관
Cloudflare 도메인 DNS, 외부 접속 터널, 개인 서비스 접근 인증
Windows PC와 VS Code SSH 접속과 원격 코드 편집

전체 연결은 다음과 같다.

인터넷 사용자
    │
    └─ Cloudflare ─ Cloudflare Tunnel ─ 라즈베리파이
                                           │
                                           ├─ FastAPI 홈페이지
                                           ├─ File Browser
                                           └─ 유선 LAN ─ NAS 공유 폴더

라즈베리파이의 인터넷 연결: 집 공유기 Wi-Fi
NAS 연결: 라즈베리파이 LAN 포트에 직결

첫 번째 문제: 원격 접속이 안 됐다

처음에는 모니터 없이 라즈베리파이에 접속하려 했다. Windows PowerShell에서 다음 명령을 실행했지만 호스트 이름을 찾을 수 없다는 오류가 나왔다.

ssh [email protected]

PC의 네트워크 상태와 주변 기기 목록을 확인하면서 라즈베리파이를 찾았다. 이 과정에서 기존 NAS를 라즈베리파이로 착각할 뻔하기도 했다. SSH 접속 시 표시된 호스트 키가 예전 NAS 접속 주소의 키와 같아서 다른 장비라는 것을 알아챘다.

나중에는 PC가 와이파이에 연결되지 않은 상태에서도 접속을 시도한 적이 있었다. 같은 로컬 네트워크에 연결돼 있는지 먼저 확인하는 것이 중요했다.

모니터를 연결하고 발견한 파일시스템 오류

모니터를 구해 연결하자 원격 접속이 되지 않았던 또 다른 원인이 보였다. 라즈베리파이가 정상 부팅되지 못하고 (initramfs) 복구 화면에 멈춰 있었다.

루트 파일시스템 검사에 실패했고, /dev/disk/by-label/EXAMPLE_ROOT에 수동 검사가 필요하다는 메시지가 표시됐다. 복구 화면에서 다음 명령을 실행했다.

fsck -f /dev/disk/by-label/EXAMPLE_ROOT

검사가 진행되면서 블록과 inode 개수 등의 오류를 수정할지 물었고, 수정에 동의했다. 검사가 끝난 뒤 다음 명령으로 부팅을 이어갔다.

exit

이후 키보드 설정을 거쳐 부팅이 진행됐고 SSH 접속에도 성공했다.

전원 문제도 있었다

랜선을 바꾸다가 라즈베리파이가 재부팅되는 일이 있었다. 전원 연결이 불안정했고, 나중에 커널 로그에서도 Undervoltage detected! 기록이 확인됐다.

초기 파일시스템 오류의 원인을 전원 문제로 단정할 수는 없지만, 서버를 안정적으로 운영하려면 전원 어댑터와 케이블, 단자 접촉 상태를 먼저 안정화해야 한다는 과제가 남았다. 종료할 때는 전원을 바로 뽑기보다 다음 명령을 사용한다.

sudo poweroff

라즈베리파이와 NAS를 랜선으로 직결하기

라즈베리파이는 와이파이로 인터넷에 연결하고, 유선 LAN 포트에는 NAS를 연결했다. 처음 확인했을 때 유선 링크는 잡혀 있었지만 eth0에는 IP가 없었다.

ip -br addr
ip route
nmcli connection show

유선 연결 프로필은 자동으로 IP를 받는 설정이었다. 직결한 쪽에서 IP를 나눠줄 장치가 필요했으므로 NetworkManager의 공유 모드를 사용했다.

sudo nmcli connection modify demo-lan \
  ipv4.method shared \
  ipv4.addresses 192.0.2.1/24 \
  ipv4.never-default yes \
  connection.autoconnect yes

sudo nmcli connection up demo-lan

주소 구성은 다음과 같았다(아래 IP는 전부 임의 예시).

  • 라즈베리파이 유선: 192.0.2.1
  • NAS: 192.0.2.20
  • 라즈베리파이 와이파이: 당시 198.51.100.10

NAS의 주소는 DHCP 할당 목록에서 확인했다. 위 주소는 모두 임의 예시이며 실제 주소는 공개하지 않는다. 당시 고정 임대까지 설정한 것은 아니다.

sudo cat /var/lib/NetworkManager/dnsmasq-eth0.leases
ping -c 3 -W 2 192.0.2.20

도메인 구매와 Cloudflare 연결

국내 등록 업체에서 개인 도메인을 구매했다. 이 글에서는 example.com으로 대체한다(임의 도메인). 이후 Cloudflare에서 기존 도메인을 연결했다.

처음에는 Cloudflare의 도메인 구매 화면으로 들어가 해당 도메인 확장자를 지원하지 않는다는 안내를 봤다. 이미 구매한 도메인을 연결하려면 구매 메뉴가 아니라 Connect a domain을 사용해야 했다.

Cloudflare에서 Free 요금제를 선택하고, 도메인 구매 업체의 네임서버를 Cloudflare가 지정한 두 주소로 변경했다. 반영을 기다린 뒤 도메인 상태가 Active가 된 것을 확인했다.

무료 요금제 화면의 Workers, D1, R2 한도는 각각의 서비스에 대한 제한이었다. NAS의 저장 용량이나 홈페이지 방문자 수가 그 숫자로 바로 제한되는 것은 아니었다. 다만 공개 HTTP 접속에는 요청 크기와 대용량 파일 제공에 관한 별도 제한이 있으므로, 파일 서비스의 운영 방식은 따로 고려해야 한다.

Cloudflare Tunnel 설치

공유기에서 포트를 외부에 여는 대신, 라즈베리파이에서 Cloudflare로 연결을 만드는 Tunnel 방식을 사용했다.

ARM64용 cloudflared를 설치하고 계정을 연결했다.

cloudflared tunnel login
cloudflared tunnel create demo-node

브라우저에서 도메인 접근을 승인한 뒤 터널을 생성했다. 터널 인증 파일은 라즈베리파이에 보관하고 공개하지 않는다.

설정 파일을 만들고 systemd 서비스로 설치했다. 실제 서비스가 사용하는 설정 경로는 /etc/cloudflared/config.yml이었다.

sudo cloudflared --config /home/demo_user/.cloudflared/config.yml service install
sudo systemctl enable --now cloudflared
sudo journalctl -u cloudflared -n 30 --no-pager

로그에 Registered tunnel connection이 표시돼 실제 연결을 확인했다. 이 설정으로 SSH 창을 닫아도 터널이 계속 실행된다.

FastAPI와 uv로 홈페이지 실행하기

홈페이지는 Python FastAPI로 만들기로 했다. 처음에는 venv와 pip로 시작했지만, 이후 프로젝트 관리를 uv로 전환했다.

작업 폴더는 /home/demo_user/demo_site로 표기한다(계정·폴더명 모두 임의).

cd ~/demo_site
uv init --bare
uv add fastapi uvicorn
uv run uvicorn main:app --host 127.0.0.2 --port 8100

처음에는 간단한 테스트 페이지를 반환하도록 만들었다. 외부 네트워크 인터페이스에 직접 서버를 열지 않고 127.0.0.2:8100에서 실행한 뒤, Cloudflare Tunnel이 전달하도록 구성했다.

demo_site.service도 만들어 재부팅 후 자동으로 실행되도록 했다. 서비스의 실행 명령은 다음과 같다.

/home/demo_user/.local/bin/uv run --frozen uvicorn main:app --host 127.0.0.2 --port 8100

서비스를 시작하자마자 보낸 첫 요청은 실패했지만, 잠시 뒤 다시 요청했을 때 페이지가 정상 반환됐다.

DNS 레코드 충돌 해결하기

홈페이지 주소를 Tunnel에 연결할 때 기존 A·CNAME 레코드 때문에 오류가 발생했다.

cloudflared tunnel route dns demo-node example.com
cloudflared tunnel route dns demo-node blog.example.com

www는 덮어쓰기 옵션으로 연결됐다. 루트 도메인은 기존 A 레코드 두 개 때문에 같은 방법으로 해결되지 않아, Cloudflare DNS에서 해당 레코드를 정리하는 절차를 안내받았다.

이 기록에서 실제 홈페이지 확인에 사용한 주소는 https://blog.example.com다. 루트 도메인의 최종 연결 상태는 별도로 확인할 필요가 있다.

NAS 관리 화면과 개인 서비스 보호

NAS 관리 화면은 admin.example.com로 연결했다. 단순히 주소를 공개하기 전에 Cloudflare Access를 설정했다.

본인 이메일만 허용하는 정책을 만들고, 이메일로 인증번호를 받는 One-time PIN 로그인 방식을 추가했다. 정책을 저장하는 것과 앱을 최종 생성하는 것은 별도 단계였다. 중간에 앱을 저장하지 않고 화면을 나가 다시 만드는 시행착오도 있었다.

외부에서의 접속 흐름은 다음과 같다.

Cloudflare 이메일 인증 → NAS 로그인 화면 → NAS 계정 인증

이 연결에서 라즈베리파이는 웹 요청을 전달하는 중계 역할이다. NAS 파일을 직접 읽고 저장하는 연결은 별도로 설정해야 했다.

NAS 공유 폴더 연결하기

라즈베리파이에 SMB 도구를 설치했다.

sudo apt install -y smbclient cifs-utils

첫 연결은 프로토콜 협상 시간 초과로 실패했다. NAS가 절전 상태에서 깨어나는 데 시간이 걸렸을 가능성도 있었지만, 원인을 확정한 것은 아니다. SMB 포트인 445와 139는 연결됐고, SMB2·3을 지정한 재시도는 성공했다.

smbclient -L //192.0.2.20 -U demo_user \
  --option='client min protocol=SMB2' \
  --option='client max protocol=SMB3' -t 10

확인한 공유 폴더는 다음과 같다(아래 이름은 전부 임의로 대체).

demo_repos
demo_users
demo_audio
demo_images
demo_uploads
demo_movies
demo_legacy_site
demo_packages

먼저 비어 있던 demo_uploads를 /mnt/demo_storage/demo_uploads에 연결했다. 이후 NAS 로그인 정보를 /etc/demo-storage-credentials에 저장하고 파일 권한을 600으로 제한했다. 비밀번호를 명령이나 공개 코드에 직접 넣지 않는 방식이다.

자동 연결은 /etc/fstab과 systemd automount를 사용했다. 네트워크 저장소임을 명시하고, 접근할 때 마운트하도록 설정했다.

처음에는 인증 파일에 저장한 로그인 정보가 맞지 않아 STATUS_LOGON_FAILURE 오류가 발생했다. NAS 비밀번호를 수정한 뒤 재시도하자 자동 연결이 정상 작동했다.

File Browser로 웹 파일 서버 만들기

파일 업로드·다운로드와 폴더 관리는 라즈베리파이에서 실행하는 File Browser에 맡겼다. 설치 당시 버전은 v2.x.x (임의 버전 표기)이었다.

프로그램의 설정과 사용자 데이터베이스는 라즈베리파이의 /home/demo_user/demo_fileapp에 두고, 실제 파일 저장소만 NAS를 사용했다.

웹 로그인 계정을 만들 때 12자 미만 비밀번호가 거부됐다. 또 비밀번호 입력 안내문과 실제 입력값을 혼동하는 시행착오도 있었다. 최종적으로 비밀번호를 숨김 입력으로 받아 계정을 생성했다.

서비스는 127.0.0.2:8180에서 실행하도록 설정했다. NAS 저장소가 마운트돼야 시작할 수 있도록 RequiresMountsFor와 시작 전 확인 명령도 넣었다.

sudo systemctl enable --now filebrowser
curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.2:8180/

HEAD 요청은 404를 반환했지만 실제 GET 요청은 200을 반환했다. 실행 중인 프로그램의 데이터베이스를 CLI로 읽으려다 timeout이 나기도 했다. 웹페이지 응답 자체는 정상이었다.

Cloudflare Access에 files.example.com용 앱을 추가하고, 기존 본인 전용 정책을 연결했다. 이후 Tunnel에서 로컬 8180 포트로 전달하도록 설정했다.

기존 NAS 자료도 그대로 사용하기

처음 파일 서버는 demo_uploads만 보여줬기 때문에 기존 자료가 보이지 않았다. 자료가 사라진 것이 아니라 다른 공유 폴더를 아직 연결하지 않았던 것이다.

나머지 공유 폴더를 각각 /mnt/demo_storage 아래에 연결하고, File Browser의 루트 경로를 /mnt/demo_storage로 바꿨다.

폴더 현재 연결 방식
demo_uploads 읽기·쓰기, 업로드용
demo_repos, demo_users, demo_audio, demo_images, demo_movies, demo_legacy_site, demo_packages 읽기 전용, 기존 자료 조회·다운로드용

기존 자료를 이동하거나 초기화하지 않고 모두 표시되는 것을 확인했다. 나중에 다른 사람에게 파일을 공유할 때는 사용자별 접근 폴더를 제한해야 한다. 특히 demo_users는 개인 자료를 포함할 수 있다.

VS Code로 원격 개발 환경 구성하기

Windows의 VS Code에 Remote - SSH를 설치하고 라즈베리파이에 연결했다. 프로젝트 폴더 /home/demo_user/demo_site를 열어 PC에서 원격 파일을 편집할 수 있게 했다.

매번 비밀번호를 입력하는 불편을 줄이기 위해 Windows에서 Ed25519 SSH 키도 생성했다. 공개키 등록 절차를 진행했고, VS Code 원격 연결에 성공했다.

AI 코딩을 위해 OpenAI의 Codex 확장도 확인했다. VS Code의 원격 연결과 이 대화의 실행 환경은 자동으로 공유되는 것이 아니므로, 어디에서 코드와 명령이 실행되는지 구분하는 것이 중요했다.

홈페이지 방향과 현재 상태

공개 홈페이지는 개인 서버 관리 화면이 아니라 소개·포트폴리오·공부 기록을 담는 블로그로 만들기로 했다. 공개 페이지에 NAS 관리 링크나 개인 파일 목록을 넣지 않는 방향이다.

참고 사이트를 살펴본 뒤, 글을 읽기 편한 간결한 블로그 형태를 선택했다. 홈페이지 제목은 ‘배움을 기록하는 개발자’로 정했고, 이후 메인 페이지 작업을 완료했다.

현재까지 구성한 서비스는 다음과 같다.

주소 용도
blog.example.com 공개 홈페이지
admin.example.com 본인 인증으로 보호하는 NAS 관리 화면
files.example.com 본인 인증으로 보호하는 웹 파일 서버

라즈베리파이가 프로그램 실행과 웹 접속을 담당하고, 오래된 NAS는 기존 자료와 새로운 파일을 보관하는 저장소로 다시 활용하게 됐다.

앞으로 확인하고 개선할 것

  • 전원 접촉 불량과 저전압 문제 해결
  • NAS IP 고정 임대 또는 주소 변경 대응
  • 작은 파일 업로드·다운로드 및 NAS 저장 확인
  • 재부팅 후 홈페이지·Tunnel·NAS 마운트·파일 서버 정상 동작 확인
  • 백업 구성과 NAS 디스크 상태 점검
  • 공부 기록 작성·수정·공개 흐름 정리
  • 다른 사용자에게 공유할 경우 폴더별 권한 분리
  • 대용량 파일은 Cloudflare 제한과 약관에 맞는 별도 비공개 전송 경로 검토

마치며

처음에는 작업 기록을 올릴 홈페이지 하나가 필요했다. 하지만 직접 서버를 구성하면서 부팅 복구, 전원 문제, 네트워크 연결, DNS, 터널, 접근 인증, 공유 폴더, 자동 실행까지 여러 부분을 경험했다.

오래된 장비를 다시 활용한 것도 의미가 있지만, 문제가 생길 때 로그와 명령 결과를 확인하며 한 단계씩 해결한 과정 자체가 공부가 됐다.

이제 이 서버에 앞으로의 작업과 공부 기록을 쌓아갈 생각이다. 배움을 기록하는 개발자라는 이름처럼, 계속 배우고 만들며 기록을 남기는 공간으로 운영해보려 한다.