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

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


1 하드웨어 선택

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

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

2 Ubuntu 설치

미니 PC에 모니터와 키보드를 연결한 뒤, 운영체제를 설치했습니다.

운영체제는 대중적이고 참고 자료가 많은 Ubuntu Server LTS를 선택했습니다.


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이 넉넉하지 않아서, ‘무거운 작업을 돌리면 OOM(Out Of Memory)으로 프로세스가 죽을 수도 있겠다’라는 걱정이 되었습니다. 이 문제를 조금이나마 완화하기 위해 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 파일이 잡혀있어서, 크기를 조절하지 않고 그대로 사용하기로 했습니다.

[ 3.4.2 Swappiness 조정 ]

Swap을 언제부터 쓰기 시작할지 제어하는 값이 vm.swappiness입니다. 기본값은 60으로, 이 값은 꽤 적극적으로 Swap을 사용합니다. 저는 ‘최대한 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

이제 서버에 SSH로 들어올 때마다, 재부팅이 필요한지 한 눈에 알 수 있게되었습니다.


4 집 안에서 접속하기

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


4.1 SSH 서버 준비

[ 4.1.1 SSH 데몬 상태 확인 ]

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

1
sudo systemctl status ssh

[ 4.1.2 서버 LAN IP 확인 ]

같은 공유기를 쓰는 노트북에서 서버에 접속하기 위해, 서버의 LAN IP를 확인했습니다.

1
hostname -I

출력되는 값 중 192.168.x.x 형태의 IP를 메모장에 적어두었습니다.

[ 4.1.3 첫 SSH 접속 ]

이제 홈서버와 같은 공유기를 사용하는 노트북에서 접속을 시도했습니다.

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

접속이 잘 돼서, 이 시점부터는 미니 PC에 연결되어 있던 모니터와 키보드를 제거했습니다. 이후 작업은 모두 노트북에서 SSH로 서버에 접속해서 진행했습니다.


4.2 UFW 활성화

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

1
2
3
4
5
6
7
8
# 기본 SSH 포트(22) 허용
sudo ufw allow OpenSSH

# 방화벽 활성화
sudo ufw enable

# 상태 확인
sudo ufw status 

4.3 SSH 포트 변경

SSH 기본 포트인 22는 전 세계 봇들이 제일 많이 두드리는 포트입니다. 서버를 인터넷에 열어두면 금방 각종 로그가 쌓입니다. 그래서 포트를 22에서 다른 번호(예: 2222)로 바꿨습니다.

[ 4.3.1 새 포트 허용 ]

먼저 방화벽에 새 포트를 열어줬습니다. 예시로 2222번 포트를 사용하겠습니다.

1
sudo ufw allow 2222/tcp

[ 4.3.2 설정 수정 ]

설정 파일에서 포트 번호를 바꿨습니다.

1
sudo nano /etc/ssh/sshd_config
1
2
3
4
5
#Port 22

↓↓↓

Port 2222

Ubuntu에서는 /etc/ssh/sshd_config.d/*.conf 파일들도 함께 읽기 때문에, 혹시 다른 곳에서 포트 번호를 다시 정의하고 있지 않은지 확인해 줬습니다. 만약 다른 파일에 포트 번호 설정이 있다면, 해당 파일에서도 포트 번호를 2222로 바꿔줘야 합니다.

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

설정을 바꿨으니, SSH를 다시 시작해 줍니다.

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

# SSH 재시작
sudo systemctl restart ssh

[ 4.3.3 새 포트 접속 테스트 ]

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

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

정상적으로 접속된다면 새 포트에서 SSH가 잘 동작하는 것입니다.

[ 4.3.4 기본 포트 닫기 ]

새 포트가 잘 동작하는 것을 확인한 뒤, 22번 포트 허용 규칙을 삭제했습니다.

1
2
3
4
sudo ufw delete allow OpenSSH

# 상태 확인
sudo ufw status

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


4.4 SSH 로그인 방식 변경

보안을 위해 ‘SSH 키를 가진 기기만 접속 가능‘하도록 설정하고, ‘비밀번호 로그인은 끄는 방식‘을 선택했습니다.

[ 4.4.1 키 페어 생성 ]

노트북에서 SSH 키를 생성했습니다.

1
2
# Ed25519 알고리즘 사용
ssh-keygen -t ed25519 -C "homeserver"

생성된 공개키를 서버에 복사했습니다.

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

[ 4.4.2 키 로그인 테스트 ]

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

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

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

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

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

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

1
sudo nano /etc/ssh/sshd_config
1
2
3
4
5
#PasswordAuthentication yes

↓↓↓

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
2
3
4
5
#PermitRootLogin yes

↓↓↓

PermitRootLogin no

포트 번호 설정 때와 마찬가지로 /etc/ssh/sshd_config.d/*.conf 안에 설정이 덮어씌워지지 않았는지도 확인했습니다. 다른 파일에 같은 옵션이 정의되어 있다면, 파일을 열어서 똑같이 no로 변경해 줘야 합니다.

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장에서 설정한 서버는 ‘집 안에서만 쓸 수 있는 서버’였습니다. 이제 이 서버를 집 밖에서도 안전하게 접속할 수 있도록 만들어보겠습니다.

공유기에서 포트포워딩만 열어주면 서버를 인터넷에 그대로 노출하는 것도 가능합니다. 하지만 중간에 한 번 더 안전망을 만들어 두고 싶어서, 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 웹 대시보드에서 홈서버의 ‘Disable key expiry’ 옵션을 켰습니다.

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


5.2 UFW 설정

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

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

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

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

[ 5.2.1 Tailscale 허용 ]

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

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