이 글은 처음으로 홈서버를 구축한 과정을 기록한 글입니다. 미니 PC에 Ubuntu Server LTS를 설치하고, 기본적인 운영·보안 설정을 어떻게 했는지 정리했습니다. 처음 홈서버를 구축하시는 분들이 ‘이 사람은 이렇게 했구나.’ 정도로 가볍게 참고해 주시면 좋겠습니다.

혹시 내용 중 궁금한 점이나 수정이 필요한 부분이 있다면 언제든 댓글로 알려주세요. 그럼 시작하겠습니다!


1 하드웨어 선택

서버용 장비로 무엇을 쓸지 고민하다가, 아래와 같은 이유로 미니 PC를 선택했습니다.

  • 전력 효율: 데스크탑 대비 전력 소모가 적어, 24시간 켜두어도 전기세 부담이 적음.
  • 공간 활용: 크기가 작아 어디에나 둘 수 있음.
  • 성능 & 확장성: 라즈베리파이보다는 성능이 좋고, 필요한 경우 RAM이나 SSD 교체가 쉬움.

2 리눅스 설치

미니 PC에 모니터와 키보드를 연결한 뒤, 운영체제를 설치했습니다. 운영체제는 대중적이고 참고 자료가 많은 Ubuntu Server LTS를 선택했습니다.

💡
Tip
설치 과정에서 Install OpenSSH server 옵션이 나오면 체크해 주세요.

3 기본 시스템 설정

운영체제를 갓 설치한 서버는 ‘돌아는 가는 상태’일 뿐입니다. 서버 운영과 관리를 편하고 안전하게 하기 위해 몇 가지 기본 설정을 진행했습니다.


3.1 패키지 업데이트

설치에 사용한 ISO 이미지가 만들어진 시점과 현재 시점 사이에는 패키지들과 커널 업데이트가 있었을 수 있습니다. 보안을 위해 제일 먼저 최신 상태로 업데이트해 주었습니다.

1
2
sudo apt update
sudo apt full-upgrade -y

커널 업데이트가 포함되었을 수 있으므로 재부팅을 해주었습니다.

1
sudo reboot 

3.2 zsh로 변경

기본 쉘은 bash지만, 자동 완성이나 구문 강조 같은 플러그인을 편하게 사용하고 싶어서 기본 쉘을 zsh로 바꿔주었습니다.

1
2
3
4
5
6
7
8
# zsh 설치
sudo apt install zsh -y

# 설치 확인
which zsh

# 쉘 변경
chsh -s $(which zsh)

이제 SSH 세션을 끊고 다시 서버에 접속하거나 서버를 재부팅하면 zsh이 기본 쉘로 뜹니다.

Oh My Zsh이나 Powerlevel10k등을 적용하면 터미널이 예쁘고 편해지지만, 이 글에서는 ‘기본 쉘을 zsh로 바꾼 것’까지만 다루겠습니다.


3.3 시간대 설정

서버 시간과 제가 실제로 활동하는 시간이 맞춰져 있어야 로그를 볼 때 훨씬 편합니다. 그래서 서버의 시간대를 한국 시간(Asia/Seoul)으로 바꿔주었습니다.

1
2
3
4
5
6
7
8
# 시간대 확인
timedatectl status

# 시간대 변경
sudo timedatectl set-timezone Asia/Seoul

# 시간대 확인
timedatectl status

3.4 Swap 설정

제 미니 PC는 RAM이 넉넉하지 않습니다. 무거운 작업을 돌리다가 메모리가 부족해지면 리눅스가 프로세스를 강제로 죽이는 상황이 발생할 수 있습니다. 이를 방지하기 위해 Swap 설정을 확인했습니다.

Swap을 간단히 말하자면 ‘RAM이 부족할 때 디스크의 일부를 빌려 쓰는 예비 메모리‘입니다.

[ 3.4.1 Swap 상태 확인 ]

먼저, 현재 메모리와 Swap 상태를 확인했습니다.

1
2
3
4
5
6
7
8
# 메모리, swap 사용량 확인
free -h

# 활성화된 swap 확인
swapon --show

# 재부팅 시 자동 마운트 설정 확인
grep swap /etc/fstab

최신 Ubuntu Server는 설치 시 자동으로 /swap.img 파일을 만들어 줍니다. 제 서버도 확인해 보니 이미 Swap 파일이 만들어져 있고 fstab에도 등록되어 있어서, 그대로 사용하기로 했습니다.

[ 3.4.2 Swappiness 조정 ]

Swap을 얼마나 적극적으로 쓸지 결정하는 값이 vm.swappiness입니다. 기본값 60은 꽤 자주 Swap을 사용합니다. 저는 SSD 수명 보호와 성능을 위해 ‘RAM을 최대한 쓰고, 정말 부족할 때만 Swap을 쓰라‘는 의미로 값을 10으로 낮췄습니다.

1
2
3
4
5
6
7
echo 'vm.swappiness=10' | sudo tee /etc/sysctl.d/99-swappiness.conf

# 설정 반영
sudo sysctl -p /etc/sysctl.d/99-swappiness.conf

# 설정 확인
cat /proc/sys/vm/swappiness

3.5 자동 보안 업데이트 설정

홈서버를 매일 들여다보지 못할 수도 있어서, 보안 패치는 자동으로 설치되도록 unattended-upgrades를 설정했습니다.

[ 3.5.1 설치 & 활성화 ]

1
2
3
4
5
# 패키지 설치
sudo apt install unattended-upgrades -y

# 기능 활성화 ('Yes' 선택)
sudo dpkg-reconfigure unattended-upgrades

[ 3.5.2 불필요한 의존성 자동 제거 ]

업데이트 과정에서 구버전 커널이나 안 쓰는 라이브러리가 쌓이지 않도록, 자동 삭제 옵션인 Remove-Unused-Dependencies을 켰습니다.

1
sudo nano /etc/apt/apt.conf.d/50unattended-upgrades
1
2
# 주석(`//`)을 제거하고 `"true"`로 변경
Unattended-Upgrade::Remove-Unused-Dependencies "true";

[ 3.5.3 재부팅 알림 추가 ]

보안 패치 후 재부팅이 필요한 경우가 있습니다. 저는 서버가 멋대로 재부팅되는 건 싫어서 자동 재부팅 옵션은 켜지 않고, SSH 접속 시 알림을 띄우도록 .zshrc에 스크립트를 추가했습니다.

1
nano ~/.zshrc

파일 맨 아래에 다음 내용을 붙여 넣었습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
RED="$(tput setaf 1)"
RESET="$(tput sgr0)"

if [ -f /var/run/reboot-required ]; then
  echo
  echo "${RED}############################################"
  echo " ⚠️  SYSTEM REBOOT REQUIRED!"
  echo
  echo " - Details: cat /var/run/reboot-required"
  echo " - Reboot: sudo reboot"
  echo "############################################${RESET}"
  echo
fi

4 집 안에서 접속하기

미니 PC에서 모니터와 키보드를 떼고, 원격으로 접속할 수 있게 설정했습니다.


4.1 SSH 접속 준비

[ 4.1.1 SSH 데몬 상태 확인 ]

Ubuntu Server를 설치하는 과정에서 SSH도 설치해 두었기 때문에, SSH가 잘 떠 있는지 확인했습니다.

1
sudo systemctl status ssh

[ 4.1.2 서버 IP 확인 ]

서버에서 다음 명령어로 192.168.x.x 형태의 현재 할당받은 IP를 확인했습니다.

1
hostname -I

[ 4.1.3 첫 SSH 접속 ]

같은 공유기에 연결된 노트북에서 접속을 시도했습니다.

1
ssh -p 22 {username}@192.168.x.x

접속에 성공했다면, 이제 미니 PC의 모니터와 키보드는 제거해도 됩니다. 이후 작업은 모두 노트북에서 SSH로 편하게 진행했습니다.


4.2 UFW 활성화

우분투의 기본 방화벽인 UFW (Uncomplicated Firewall) 를 켰습니다.

⚠️
주의
SSH 포트를 열지 않고 방화벽을 켜면 접속이 끊겨서, 다시 모니터와 키보드를 연결해야 합니다. 반드시 아래 순서를 지켜야 합니다.
1
2
3
4
5
6
7
8
# 기본 SSH 포트(22) 허용
sudo ufw allow OpenSSH

# 방화벽 활성화
sudo ufw enable

# 상태 확인
sudo ufw status 
💡
UFW와 Docker의 충돌 문제

나중에 Docker를 설치할 때 ‘UFW와 Docker의 충돌 문제‘에 주의해야 합니다. 이는 흔히 겪는 보안 이슈 중 하나로, 한마디로 ‘사용자가 UFW로 특정 포트만 열어 놓더라도, Docker 컨테이너를 실행하면 그 포트 외의 다른 포트도 외부로 뚫려버리는 현상’을 말합니다.

포트를 로컬호스트(127.0.0.1)에만 연결하거나, ufw-docker 같은 도구를 사용하는 등의 해결 방안이 있으니 참고하시기 바랍니다.


4.3 SSH 포트 변경

기본 포트 22는 해커들의 자동화 봇이 가장 많이 공격하는 경로입니다. 포트 번호만 바꿔도 무차별 대입 공격(Brute Force) 로그가 확연히 줄어듭니다. 예시로 2222번 포트로 바꿔보았습니다.

[ 4.3.1 새 포트 허용 ]

설정을 바꾸기 전에 방화벽부터 열어주었습니다. 안 그러면 설정 바꾸고 재시작하자마자 튕겨 나갈 수 있기 때문입니다.

1
sudo ufw allow 2222/tcp

[ 4.3.2 설정 수정 ]

혹시 모를 상황에 대비해 설정 파일을 백업한 뒤, 파일을 수정했습니다.

1
2
3
4
5
# 원본 설정 파일 백업
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak

# 설정 파일 편집
sudo nano /etc/ssh/sshd_config

#Port 22라고 적힌 부분을 찾아 주석(#)을 제거하고 번호를 바꿔주었습니다.

1
Port 2222

Ubuntu에서는 /etc/ssh/sshd_config.d/*.conf 파일들이 메인 설정을 덮어쓸 수 있습니다. 혹시 다른 파일에서 포트 번호를 정의하고 있는지 확인했습니다. 만약 다른 파일에 포트 번호 설정이 있다면, 해당 파일에서도 포트 번호를 위와 똑같이 바꿔줘야 합니다.

1
sudo grep -R "Port" /etc/ssh/sshd_config.d/

[ 4.3.3 재시작 & 테스트 ]

⚠️
주의
현재 터미널 창을 닫지 마세요. 설정이 꼬여서 접속이 안 될 경우, 현재 연결된 세션이 생명줄입니다. 새 터미널 창을 열어서 접속이 되는지 확인될 때까지 현재 창은 유지해야 합니다.
1
2
3
4
5
# SSH 설정 문법 검사
sudo sshd -t

# SSH 재시작
sudo systemctl restart ssh

현재 접속된 터미널을 닫지 않고, 새 터미널을 하나 더 열어 새 포트로 접속을 시도했습니다.

1
ssh -p 2222 {username}@192.168.x.x

새 터미널에서 정상적으로 접속이 되는 것을 확인했습니다.

[ 4.3.4 기본 포트 닫기 ]

사용하지 않는 22번 포트 규칙을 방화벽에서 삭제했습니다.

1
2
3
4
sudo ufw delete allow OpenSSH

# 상태 확인
sudo ufw status

이제 SSH는 2222 포트에서만 접속을 받습니다.


4.4 SSH 키 로그인 설정

보안을 위해 비밀번호 입력을 막고, 등록된 SSH 키 파일(Key File)을 가진 기기만 접속되도록 설정합니다.

[ 4.4.1 키 페어 생성 및 전송 ]

이 과정은 서버가 아닌 접속하려는 노트북(Client) 에서 수행했습니다.

1
ssh-keygen -t ed25519 -C "homeserver"

생성된 공개키를 노트북에서 서버로 전송했습니다.

1
ssh-copy-id -i ~/.ssh/id_ed25519.pub -p 2222 {username}@192.168.x.x

비밀번호 없이 접속되는지 확인했습니다.

1
ssh -p 2222 {username}@192.168.x.x

정상적으로 로그인되면, 노트북에서는 SSH 키로 서버에 접속할 수 있는 상태입니다.

[ 4.4.2 비밀번호 로그인 차단 ]

키 로그인이 잘 동작하는 것을 확인하고 나서, 비밀번호 로그인을 꺼 주었습니다.

설정 파일에서 PasswordAuthentication 옵션의 주석(#)을 제거하고 no로 바꿨습니다.

1
sudo nano /etc/ssh/sshd_config
1
PasswordAuthentication no

포트 번호 설정 때와 마찬가지로 /etc/ssh/sshd_config.d/*.conf 파일이 이 설정을 덮어쓰고 있는지 확인했습니다. (실제로 다른 파일에서 같은 옵션이 정의되어 있어서, 해당 파일을 열어서 똑같이 no로 변경해 주었습니다.)

1
sudo grep -R "PasswordAuthentication" /etc/ssh/sshd_config.d/

설정을 바꿨으니, SSH를 다시 시작해 주었습니다.

1
2
3
4
5
# SSH 설정 문법 검사
sudo sshd -t

# SSH 재시작
sudo systemctl restart ssh

4.5 Root 로그인 끄기

다음으로, root 계정 직접 로그인을 막았습니다.

설정 파일에서 PermitRootLogin 옵션의 주석(#)을 제거하고 no로 바꿨습니다.

1
sudo nano /etc/ssh/sshd_config
1
PermitRootLogin no

포트 번호 설정 때와 마찬가지로 /etc/ssh/sshd_config.d/*.conf 파일이 이 설정을 덮어쓰고 있는지 확인했습니다.

1
sudo grep -R "PermitRootLogin" /etc/ssh/sshd_config.d/

설정을 바꿨으니, SSH를 다시 시작해 주었습니다.

1
2
3
4
5
# SSH 설정 문법 검사
sudo sshd -t

# SSH 재시작
sudo systemctl restart ssh

4.6 Fail2Ban 설정

여기까지 설정해도, 포트를 알아낸 누군가가 계속해서 SSH 접속을 시도할 수 있습니다. 비밀번호 로그인을 꺼 두었기 때문에 쉽게 뚫리지는 않겠지만, 로그가 실패 로그로 도배될 수는 있습니다.

그래서 Fail2Ban을 설치해서, 일정 횟수 이상 실패한 IP는 일정 시간 동안 차단하도록 설정했습니다.

[ 4.6.1 설치 ]

1
2
3
4
5
# Fail2Ban 설치
sudo apt install fail2ban -y

# 설치 확인
systemctl status fail2ban

[ 4.6.2 설정 파일 작성 ]

기본 설정 파일(/etc/fail2ban/jail.conf)은 패키지 업데이트 시 덮어쓰게 될 수 있기 때문에, /etc/fail2ban/jail.local을 새로 만들어 기본 설정을 덮어썼습니다.

1
sudo nano /etc/fail2ban/jail.local

아래와 같이 설정했습니다. port에는 앞에서 바꿔둔 SSH 포트 번호(예: 2222)를 넣었습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
[DEFAULT]

# (예: 10분 동안 5번 실패 시, 1시간 차단)
bantime = 1h 
findtime = 10m 
maxretry = 5  

# 내 IP 차단 방지 (Localhost)
ignoreip = 127.0.0.1/8

# UFW를 사용 중이므로 차단 액션을 ufw로 지정
banaction = ufw

# 로그 저장 위치
logtarget = /var/log/fail2ban.log


[sshd]

enabled = true
port = 2222
backend = systemd

[ 4.6.3 설정 적용 ]

1
2
3
4
5
6
7
8
# 부팅 시 자동 시작
sudo systemctl enable fail2ban

# 설정 적용
sudo systemctl restart fail2ban

# 상태 확인
sudo systemctl status fail2ban

5 집 밖에서 접속하기

4장에서 설정한 서버는 ‘집 안에서만 쓸 수 있는 서버’였습니다. 5장에서는 서버를 집 밖에서도 안전하게 접속할 수 있도록 설정하는 방법을 다루겠습니다.

공유기에서 포트포워딩만 열어주면 서버를 인터넷에 그대로 노출하는 것도 가능합니다. 하지만 중간에 한 번 더 안전망을 만들어 두고 싶어서, Tailscale을 사용하기로 했습니다.

저는 서버 운영 방법을 바닥부터 익혀가는 것이 목표라 ‘SSH 서버 + Tailscale 네트워크’ 조합을 선택했습니다. 편리함이 더 중요한 분들은 Tailscale에서 제공하는 ‘Tailscale SSH’ 기능을 쓰는 것도 방법입니다.


5.1 Tailscale 사용

[ 5.1.1 설치 & 등록 ]

홈서버에 Tailscale을 설치했습니다.

1
2
3
4
5
# 설치 스크립트 실행
curl -fsSL https://tailscale.com/install.sh | sh

# 서비스 구동 (출력되는 인증 URL로 로그인)
sudo tailscale up

sudo tailscale up을 실행하면 터미널에 인증용 URL이 뜨는데, 이 URL을 브라우저로 열어 로그인하면 홈서버가 내 Tailscale 네트워크에 등록됩니다.

이후 노트북에도 Tailscale을 설치하고 로그인하여, Tailscale 대시보드에서 각 기기의 Tailscale IP(100.x.x.x)를 확인했습니다.

[ 5.1.2 SSH 접속 테스트 ]

노트북에서 홈서버의 Tailscale IP로 SSH 접속을 테스트했습니다. 포트 번호는 앞에서 바꿔둔 2222입니다.

1
ssh -p 2222 {username}@{Tailscale_IP}

Tailscale 덕분에 공유기 포트포워딩 없이도 집 밖에서 홈서버에 바로 접속할 수 있게 되었습니다.

[ 5.1.3 키 만료 설정 ]

Tailscale은 각 기기의 키가 기본적으로 180일마다 만료되게 되어 있습니다.

‘서버가 갑자기 Tailscale에서 사라지면 곤란하다’라는 생각이 들어서, Tailscale 웹 대시보드에 접속하여 Machines 탭 → 홈서버 우측 메뉴(…) → Disable key expiry 옵션을 켜주었습니다.

반대로, 노트북은 키 만료를 그대로 둔 상태로 사용하고 있습니다.


5.2 UFW 설정

SSH를 인터넷 전체에 열어두지 않고, Tailscale + 집 안 LAN에서만 받도록 방화벽을 조정했습니다. 이렇게 하면 외부에서 홈서버 공인 IP로 포트 스캔을 해도 SSH 포트가 닫혀 있는 것처럼 보입니다.

대신, 단점들도 있어서 환경에 맞게 선택하시길 추천해 드립니다.

  • 단점:
    • 외부에서 Tailscale이 먹통이 되면 서버에 접속할 방법이 없음
    • 외부에서 Tailscale 계정이나 키에 문제가 생기면 서버에 접속할 방법이 없음
    • 외부에서 사용하는 Wi-Fi가 Tailscale 트래픽을 차단하면 서버에 접속할 방법이 없음

[ 5.2.1 Tailscale 허용 ]

Tailscale을 통해 들어오는 트래픽만 콕 집어서 허용해 주었습니다. 보통 Tailscale 인터페이스 이름은 tailscale0입니다.

앞에서와 마찬가지로 SSH 포트를 예시로 2222라고 가정합니다. (실제로는 사용 중인 포트를 사용하셔야 합니다.)

1
2
# 인터페이스 이름 확인 (보통 tailscale0)
ip link show
1
2
3
4
sudo ufw allow in on tailscale0 to any port 2222 proto tcp

# 상태 확인
sudo ufw status

[ 5.2.2 집 안 LAN 허용 ]

집 안의 다른 기기에서도 (Tailscale 없이) 접속할 수 있도록 설정했습니다.

먼저 서버의 IP 대역을 확인했습니다.

1
ip a

출력 결과 중 inet 192.168.0.50/24 ...에서 /24는 앞의 세 덩이(192.168.0)가 같은 기기들은 하나의 그룹으로 보라는 뜻입니다. 그래서 UFW 규칙에는 대역 전체를 나타내는 192.168.0.0/24를 넣어주면 됩니다.

1
2
3
4
sudo ufw allow from 192.168.0.0/24 to any port 2222 proto tcp

# 상태 확인
sudo ufw status

[ 5.2.3 전역 포트 허용 제거 ]

마지막으로, 이전에 추가해 두었던 ‘모든 곳에서 2222 포트 허용’ 규칙을 제거했습니다.

1
2
3
4
sudo ufw delete allow 2222/tcp

# 상태 확인
sudo ufw status