🔑 핵심 요약
✦ AI·GEN친구들을 위해 팰월드(Palworld) 전용 서버를 상시 켜진 홈 머신에 Docker로 세운 전 과정을, 그대로 따라 할 수 있는 가이드로 정리한 기록. 발단은 친구들이 Radmin VPN으로 연결하던 것인데, 이런 P2P는 NAT를 못 뚫으면 제3자 중계로 물러나 트래픽이 밖을 돈다. 그래서 필자는 자기 상시 머신에서 자가 호스팅했다. 먼저 미신 하나를 깬다: 업체 스펙은 하나같이 '16GB부터'지만 소규모 평소는 아주 가벼워 필자 실측은 3~5명에 약 1.2GB; 정말 막아야 할 건 팰월드 명물 메모리 누수로, mem_limit + 매일 재시작으로 눌러둔다. 이어 곁가지를 쳐낸 docker-compose(port에 /udp 잊지 말고, RCON은 127.0.0.1에 고정), 그리고 가장 막히는 UDP 연결 세 관문: ufw로 udp 허용, 라우터 port forwarding(반드시 UDP), DDNS. 더해 두 오해를 바로잡는다: Cloudflare는 반드시 회색 구름(주황 구름 프록시는 UDP를 안 나르고, 켜면 못 붙음), UDP 게임 패킷은 nginx를 전혀 안 거침. 후반은 월드 설정 함정(ini를 직접 고치면 환경 변수로 재생성돼 덮어써짐), 친구 '버전 불일치' 고치기(buildid 비교, 재시작으로 SteamCMD 업데이트, 업데이트 전 RCON 브로드캐스트 + 저장), 그리고 18GB 게임 본체를 매일 백업에 넣지 않기. 끝에 자가 호스팅 체크리스트.
친구가 팰월드(Palworld)의 1.0 소식을 보고 같이 하자고 불렀다. 그들은 2년 전에 한 번 깬 적이 있어서 이 대규모 업데이트를 계기로 다시 한 바퀴 돌고 싶다고 했고, 나도 마침 그 무렵 한가했다.
솔직히 이 게임 첫인상은 별로였다. 그때 그 "포켓몬 표절" 논란이 있었으니까. 하지만 나중에 소송에서 이겼고, 1.0 업데이트 폭도 정말 커서, 생각만큼 심하진 않겠거니 하고 나도 손을 댔다.
친구는 처음에 Radmin VPN으로 다들 묶어서 연결했다. 이 방법이 안 된다는 건 아니지만, 나한테는 늘 보안상의 찜찜함이 남는다. Radmin 같은 가상 LAN은 P2P로 돈다. 양쪽이 직접 연결될 수 있으면 직접 잇고, 상대의 NAT나 방화벽을 못 뚫으면 서비스 제공사 자체의 로 물러난다. 내 친구들 집 네트워크를 생각하면, 모든 쌍이 다 뚫린다고는 못 믿겠고, 대부분 중계로 떨어질 것이다. 중계의 대가는 두 층이다. 하나는 느림(패킷이 먼 길을 돈다), 또 하나는 게임 트래픽이 무료·클로즈드소스 서비스의 서버를 지난다는 것. 물론 전 구간 암호화되어 있다. 하지만 연결을 제3자에게 한 바퀴 맡기느냐는, 나에게는 불필요한 리스크다. 그리고 나는 마침 24/7 켜져 있는 홈 머신이 있었으니, 그들 진도가 아직 얕을 때 끼어들었다. 전용 서버를 세우자, 다들 우리 집으로 바로 연결하면 된다.
그래서 이 글이다. Docker로 팰월드 전용 서버를 세우는 건 어렵지 않다. docker compose up이면 몇 분 만에 돈다. 어려운 건 다음 단계다: 세워졌는데, 친구가 못 들어온다. 여기서 막히는 사람이, "어떻게 세우나"에서 막히는 사람보다 훨씬 많다. 이 글은 전 과정을 한 번 훑는다. 리소스 산정, docker-compose, 가장 막히기 쉬운 UDP 연결, 그리고 "친구가 버전 불일치로 못 들어온다"를 어떻게 고치나까지. 모든 명령은 그대로 실행 가능하고, 비밀번호와 IP는 자리표시자이니 자기 것으로 바꾸면 된다.
먼저, 어떤 image를 쓰나, 그리고 메모리는 실제로 얼마나 먹나#
먼저 한마디: 대다수는 자가 호스팅에 공식 전용 서버 도구(SteamCMD로 PalServer를 직접 받는)를 쓰지, 꼭 Docker는 아니다. 내가 Docker로 가는 건 순전히 익숙하고, 머신의 다른 서비스와 일관되게 관리하기 좋아서다. 당신도 상시 켜진 서버가 있고 docker가 손에 익으면, 이 길은 편하다. 내가 쓰는 image는 커뮤니티가 관리하는 로, SteamCMD 다운로드·설정 파일 생성·백업·RCON을 전부 싸놓았다.
메모리는 먼저 미신 하나를 깨자. 호스팅 업체 스펙 페이지를 보면 하나같이 "16GB부터, 32GB면 더 안정"이라고 쓰여 있다. 그 숫자는 보수적이다(그들은 RAM도 판다). 실제로는 소규모라면 평소엔 아주 가볍다. 내 이 Linux docker에서 3~5명을 돌려, 실측은 대략 1.2GB 언저리였다(장시간 모니터링이 아니라 슬쩍 본 값).
그럼 왜 아직도 메모리를 신경 쓰나? 팰월드에는 라는 지병이 있기 때문이다. 프로세스 자체가 시간이 갈수록 조금씩 비대해진다. 게임 안에서 무엇을 얼마나 지었는지와는 무관하게. 커뮤니티 실측으로는, 4인 서버를 재시작 없이 계속 돌리면 일주일 안에 5G에서 8~12G까지 오를 수 있다. 그래서 옳은 수는 32G를 준비하는 게 아니라, mem_limit으로 상한을 막고, 매일 정시 재시작(메모리를 풀어준다. 누가 접속 중이면 미룸)을 조합해 누수를 합리적 범위로 눌러두는 것이다. 그러면 아무리 오래 돌려도 같은 머신의 다른 서비스를 끌어내리지 않는다.
최소한으로 도는 docker-compose#
핵심 설정은 그리 많지 않다. 곁가지를 쳐내고 정말 짚어야 할 몇 줄만 남긴 게 이것:
services:
palworld:
image: thijsvanloef/palworld-server-docker:latest
restart: unless-stopped
container_name: palworld
stop_grace_period: 30s # 정지 신호 후, 저장에 30초 남김
ports:
- "8211:8211/udp" # 게임 연결, UDP(중요, 아래서 상술)
- "27015:27015/udp" # Steam query, UDP
- "127.0.0.1:25575:25575" # RCON, 로컬만, 밖에서 못 건드림
environment:
PLAYERS: "16"
SERVER_NAME: "My Palworld"
SERVER_PASSWORD: "<당신의 서버 비밀번호>"
ADMIN_PASSWORD: "<당신의 관리 비밀번호>"
UPDATE_ON_BOOT: "true" # 컨테이너 시작 시 게임 업데이트 확인
RCON_ENABLED: "true" # 자동 백업/재시작이 우아한 종료에 씀
BACKUP_ENABLED: "true"
AUTO_REBOOT_ENABLED: "true" # 매일 재시작, 비대해진 메모리 해제
mem_limit: 16g
volumes:
- ./data:/palworld # 게임 본체 + 세이브가 여기지금은 안 보이지만 나중에 밟는 지점 몇 개:
8211과27015뒤의/udp를 빠뜨리지 말 것. 팰월드는 UDP로 돈다. 실수로 TCP로 쓰면, 서버는 뜨고 내 로컬에서도 연결되지만, 밖의 사람은 하나도 못 들어온다.- RCON을
127.0.0.1에 고정: 자동 백업/재시작이 먼저 경고를 브로드캐스트하고 우아하게 종료하기 위한 관리 채널로, 로컬 전용이며 밖에 내보내면 안 된다. mem_limit과AUTO_REBOOT_ENABLED는 한 조로, 위의 메모리 비대에 정면으로 대응하는 것.
진짜 막는 건 네트워크: UDP 연결 세 관문#
docker compose up -d 뒤, 서버는 8211에서 돌고, 같은 네트워크에서 랜IP:8211로 연결하는 건 대개 문제없다. 그리고 친구가 연결하면, 못 들어온다.
패킷이 인터넷에서 당신 집의 그 컨테이너까지 닿으려면, 도중에 세 관문이 있고, 하나라도 빠지면 못 지나기 때문이다:
먼저 패킷이 호스트에 닿게 한다. 로 UDP port 두 개를 연다:
sudo ufw allow 8211/udp comment 'Palworld game'
sudo ufw allow 27015/udp comment 'Palworld query'프로토콜은 반드시 udp. 여기가 세 관문 중 가장 자주 tcp로 잘못 쓰는 관문이다.
패킷이 인터넷에서 당신 집에 닿으려면, 라우터가 그것을 그 호스트로 전달해야 한다. 라우터 관리 화면에서 전달 규칙 두 개를 추가한다:
| 외부 (WAN) | → 내부 호스트 | 프로토콜 |
|---|---|---|
| 8211 | 192.168.x.x : 8211 | UDP |
| 27015 | 192.168.x.x : 27015 | UDP |
가정용 네트워크의 공인 IP는 보통 바뀌므로, 를 걸어, 친구가 고정 주소 하나만 외우면 되게 한다. 매번 IP를 묻지 않아도 된다. Cloudflare로 도메인을 관리한다면, 치명적인 디테일이 다음 절에 있다.
WARNING
Cloudflare는 "회색 구름"(DNS only)으로, "주황 구름"(Proxied)은 안 된다 Cloudflare의 주황 구름 프록시는 HTTP/HTTPS 트래픽만 전달한다. 임의의 UDP 패킷은 전달하지 않는다. 팰월드 도메인을 주황 구름으로 하면 친구는 100% 못 붙고, 게다가 조용히 실패한다(웹류 서비스는 정상, 게임만 연결 안 됨. 여기가 원인이라 떠올리기 어렵다). 반드시 회색 구름으로 해, 도메인이 집 IP로 바로 해석되게 한다.
두 가지 잦은 오해를, 시간 낭비 전에 확실히 해두자:
NOTE
nginx는 안 건드려도 된다, 한 글자도
웹 서비스를 세워본 사람은 "외부용"이라는 말만 들으면 리버스 프록시를 떠올린다. 하지만 . 팰월드는 UDP 게임 패킷으로 '라우터 → ufw → 컨테이너'를 직통하며, nginx와는 전혀 무관하다. 이번에 /etc/nginx는 한 글자도 안 건드린다.
그럼 27015은 열어야 하나? 이건 로, "당신 서버 상태가 Steam 서버 브라우저에 뜨느냐"에만 관여한다. 최소로 가고 싶으면 먼저 8211만 연다. 친구는 당신주소:8211로 바로 붙어 논다. 누가 정말 Steam 즐겨찾기에서 접속 상태를 보고 싶어 하면, 나중에 27015를 더하면 된다.
월드 설정의 함정: ini를 직접 고치면 덮어써진다#
서버가 붙은 뒤엔 경험치 배율 같은 걸 만지고 싶어질 거다. 여기 반드시 밟는 함정이 있다: 이 image는 부팅할 때마다 환경 변수로 PalWorldSettings.ini를 재생성한다. 그 ini 파일을 손으로 고쳐도, 다음 컨테이너 재시작에서 덮어써져, 고쳤는데 안 고친 상태가 된다.
옳은 방법은 설정을 docker-compose.yml의 environment 블록에 써, 생성될 때 당신 값을 갖게 하는 것. 흔히 쓰는 것들:
자주 쓰는 월드 설정(compose의 environment에 쓴다)
| 환경 변수 | 효과 | 예시 |
|---|---|---|
EXP_RATE | 경험치 배율 | 3.0 |
COLLECTION_DROP_RATE | 채집 드롭 배율 | 3.0 |
DEATH_PENALTY | 사망 페널티 | Item(아이템만 떨굼, 장비 유지) |
PAL_EGG_DEFAULT_HATCHING_TIME | 알 부화 시간(시간) | 1(기본 72) |
BASE_CAMP_WORKER_MAX_NUM | 거점 작업 팰 상한 | 20 |
SERVER_PLAYER_MAX_NUM | 인원 상한 | 32 |
PVP / ENABLE_FRIENDLY_FIRE | PvP / 아군 피해 | true / false |
고친 뒤 docker compose up -d로 컨테이너를 재구축하고, 새 환경 변수로 ini를 재생성하면 설정이 먹는다. 먹었는지 확인하려면 컨테이너 안의 생성된 ini를 본다(docker exec palworld cat .../PalWorldSettings.ini). 당신 값이 맞는지 확인하고 끝낸다.
친구가 "버전 불일치"로 못 들어온다: 서버가 뒤처진 것#
며칠 하다가, 어떤 친구가 "못 들어온다, 버전 불일치라 뜬다"고 알려왔다. 거의 항상 같은 원인이다: 그의 client는 이미 새 버전으로 자동 업데이트됐고, 당신의 server는 옛 버전에 멈춰 있다.
어떻게 확인하나? 를 비교한다: 로컬 server의 buildid를 Steam 최신 buildid와 맞춰보고, 다르면 뒤처진 것이다.
여기 고약한 착각을 하나 깨자: 컨테이너 로그에 container is up to date라고 떠서 이미 최신이라 여기는 것. 그 한 줄은 Docker image가 최신이라는 뜻이지, 게임 파일이 최신이라는 뜻이 아니다. 게임 업데이트는 UPDATE_ON_BOOT가 컨테이너 시작 시 SteamCMD를 한 번 돌려 받는 것으로, 컨테이너가 며칠 재시작 없이 돌았다면, 요 며칠의 새 버전을 잡을 기회가 없었던 셈이다.
고치는 법은 컨테이너를 재시작해, SteamCMD 업데이트를 한 번 트리거하는 것:
docker compose up -d --force-recreate하지만 재시작은 접속 중인 사람을 내쫓는다. 바로 하면 안 된다. 우아한 방법은 먼저 RCON으로 브로드캐스트하고 저장한 뒤, 재시작하는 것:
TIP
업데이트 전에 브로드캐스트 + 저장, 놀고 있는 도중에 그냥 내쫓지 말 것
RCON으로 한마디 브로드캐스트("서버는 5분 뒤 업데이트로 재시작합니다")를 접속 플레이어에게 보내고, 저장을 한 번 트리거한 뒤 --force-recreate. 내쫓긴 사람은 자기 client도 업데이트가 끝나면(보통 자동, 몇 분) 그대로 재접속하면 된다. 세이브는 영향받지 않는다. 업데이트는 1~2분(~5GB 다운로드), 끝나면 로컬 buildid가 친구의 client와 맞춰진다.
매번 손으로 지켜보기 싫으면, AUTO_UPDATE_ENABLED를 켠다. server가 정시에 스스로 업데이트를 확인하고(누가 접속 중이면 먼저 브로드캐스트한 뒤 업데이트하도록 설정도 가능), 게임에 새 버전이 나와도 친구가 못 들어오는 일이 다시는 없다.
18GB 게임 본체를 매일 백업 디스크에 넣지 마라#
마지막으로 놓치기 쉬운 운영 함정. 나처럼 서버 디렉터리 전체를 매일 rsync로 백업하는 구조가 있다면, 팰월드를 더한 뒤엔 주의하라: 그 data/ 폴더는 게임 본체 통째로, 18GB 가까이를 품고 있어, 방치하면 매일 백업 디스크에 쑤셔 넣어진다.
정말 백업이 필요한 건 세이브뿐이지, 그 18GB 게임 파일이 아니다(게임 파일은 언제든 다시 받을 수 있다). 그러니:
- 메인
rsync에서palworld/data(게임 본체)를 제외한다 - 세이브 디렉터리(
Pal/Saved)를 따로 백업한다. 지우면 아픈 건 그것이다 - 팰월드 세이브도 기존의 오래된 백업 정리 루프에 넣는다(예: 7일만 유지)
.gitignore 쪽도 data/가 제외됐는지 겸사겸사 확인해, 18GB 게임 파일이 버전 관리에 섞여 들어가지 않게 한다.
한 페이지 체크리스트#
전 과정을, 그대로 베낄 수 있는 순서로 압축:
팰월드 자가 호스팅 체크리스트
- RAM에 겁먹지 말 것: 소규모라면 실측 1~2G면 충분(업체의 16G는 보수적); 정말 막아야 할 건 메모리 누수로,
mem_limit+ 매일 재시작으로 눌러둔다. - compose: port에
/udp잊지 말 것; RCON은127.0.0.1에 고정; 비밀번호는 환경 변수에. - 네트워크 세 관문: ufw로
udp허용 → 라우터 port forwarding(UDP!) → DDNS. - Cloudflare는 회색 구름, 주황 구름 프록시는 UDP를 전달 안 함, 켜면 못 붙는다.
- nginx는 안 건드림, UDP 게임 패킷은 리버스 프록시를 안 거친다.
- 월드 설정은 compose의 environment에 쓴다, ini를 손으로 고치지 말 것(재생성으로 덮어써짐).
- 친구 버전 불일치 = server가 뒤처짐; buildid 비교, 컨테이너 재시작으로 SteamCMD 업데이트 트리거, 업데이트 전에 먼저 RCON 브로드캐스트 + 저장.
- 백업에서 게임 본체 제외(18GB), 세이브 디렉터리만 따로 백업.
- thijsvanloef/palworld-server-docker —— 이 글에서 쓴 Docker 이미지GitHub
- 팰월드 공식 전용 서버 안내Palworld Wiki
- Cloudflare —— 왜 Proxy(주황 구름)는 HTTP/HTTPS만 나르나Cloudflare Docs
아직 댓글이 없어요
✨ 첫 댓글을 남겨보세요