이 글은 월 1만 5천 원짜리 VPS에 24시간 AI 비서를 올렸다 시리즈의 여섯 번째 글이다.
앞 편: SSH 키 인증만 켰는데 비밀번호 로그인이 계속 열려 있었다
서버를 잠그고 에이전트를 올렸다. 게이트웨이는 정상적으로 떴다. 텔레그램 그룹에서 봇에게 말을 걸었다.
아무 대답이 없었다.
원인이 두 개 겹쳐 있었다. 그래서 하나를 고친 뒤에 “고쳤는데 안 된다”를 한 번 더 겪었다. 그리고 첫 번째 원인을 찾는 도중에 한 번 길을 잘못 들었다. 그 과정을 순서대로 적는다.

하나를 넘었더니 다음 벽이 나왔다.
첫 번째 벽 – 텔레그램에만 연결이 안 된다
에이전트 로그를 열었다.
[Telegram] Connecting to Telegram (attempt 1/8)…
[Telegram] Telegram network error, reconnecting in 5s. Error: httpx.ReadError
httpx.ReadError는 연결을 열다가 끊겼다는 뜻이다. 토큰이 틀렸다면 텔레그램이 “인증 실패”라고 응답을 했을 것이다. 응답조차 못 받았으니 토큰이 아니라 네트워크 쪽이다.
서버에서 직접 두 곳에 접속해봤다.
$ curl -sS -m 10 -o /dev/null -w '%{http_code}\n' https://api.telegram.org/
curl: (28) Connection timed out after 10002 milliseconds
$ curl -sS -m 10 -o /dev/null -w '%{http_code}\n' https://openrouter.ai/
200
모델 API는 된다. 텔레그램만 안 된다. 서버의 인터넷이 죽은 게 아니다.
범위를 좁히는 것까지는 잘했다. 문제는 그다음이었다.
여기서 한 번 길을 잘못 들었다
“텔레그램만 안 된다”를 보고 **”텔레그램이 막혔다”**로 넘어갔다.
그럴듯한 설명이 있었다. 호스팅 업체 중에는 남용을 막으려고 특정 서비스의 IP 대역을 차단하는 곳이 있다. 메신저 봇은 스팸에 자주 쓰이니 충분히 막힐 만하다고 생각했다.
그래서 지원팀에 보낼 영어 문의문까지 써뒀다. “이 서버에서 텔레그램에 접속이 안 되는데, 대역이 막혀 있는지 확인해달라.” 차단이 확인되면 리전을 옮기는 것까지 검토했다. 구축 초기라 잃을 게 별로 없다는 이유였다.
이 판단을 뒤집은 건 명령 한 줄이었다.
$ curl -4 -sS -m 10 -o /dev/null -w '%{http_code}\n' https://api.telegram.org/
302
-4는 IPv4로만 접속하라는 옵션이다. 302는 정상 응답이다. 막힌 게 아니었다.
돌아보면 “텔레그램만 안 된다”와 “텔레그램이 막혔다”는 다른 문장이다. 앞은 관찰이고 뒤는 해석이다. 정말 대역이 막혔다면 IPv4로도 안 됐어야 한다. 해석으로 넘어가기 전에 확인할 게 하나 더 있었다.
원인 1 – 주소는 있는데 길이 없다
IPv4로는 되고, 옵션 없이는 안 된다. 그렇다면 옵션 없이 접속할 때 서버가 IPv4가 아닌 다른 길을 타고 있다는 뜻이다.
주소를 조회해봤다.
$ getent hosts api.telegram.org
2001:67c:4e8:f004::9 api.telegram.org
IPv6 주소가 먼저 나왔다. (getent ahosts로 조회하면 IPv4 주소까지 전부 보인다.)
상황은 이랬다. 서버에 IPv6 주소는 할당돼 있는데, 실제로 IPv6로 나가는 길은 막혀 있었다. 그래서 서버는 IPv6가 된다고 믿고 그쪽을 먼저 시도했고, 응답이 없으니 기다리다 시간이 끝났다.

주소가 두 개인데, 죽은 쪽을 먼저 시도한다.
해결 — IPv4를 먼저 쓰게 한다
리눅스에서 “주소가 여러 개일 때 어느 쪽을 먼저 쓸지”는 /etc/gai.conf가 정한다. 파일 안에 이미 주석으로 막혀 있는 줄이 하나 있다.
sudo nano /etc/gai.conf
precedence ::ffff:0:0/96 을 검색해서 앞의 #을 지운다.
precedence ::ffff:0:0/96 100
IPv4 주소에 우선순위를 높게 주는 줄이다. 저장하고 옵션 없이 다시 접속해본다.
$ curl -sS -m 10 -o /dev/null -w '%{http_code}\n' https://api.telegram.org/
302
-4 없이도 된다.
IPv6를 통째로 끄는 방법도 있다. 나는 우선순위만 바꾸는 쪽을 택했다. IPv6 자체는 살려두고 순서만 바꾸는 게 건드리는 범위가 작다.
서버를 고쳤다고 에이전트가 고쳐지는 건 아니다
여기서 끝이 아니다. 텔레그램에 붙는 건 서버가 아니라 컨테이너 안에서 도는 에이전트다.
gai.conf는 프로그램이 주소를 고를 때 자기가 보는 파일 시스템에서 읽는다. 컨테이너는 서버와 별개의 파일 시스템을 갖고 있으니, 컨테이너 안에는 서버와 다른 /etc/gai.conf가 따로 있다. 서버 쪽 파일을 고쳐도 컨테이너 안은 그대로다.
앞 편에서 서버 시간대를 서울로 바꿨는데 컨테이너는 계속 UTC였던 것과 정확히 같은 형태다.
그래서 컨테이너 안에도 같은 줄을 넣었다.
docker exec -it [컨테이너] sh -c "echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf"
docker restart [컨테이너]
컨테이너 안에서 직접 확인한다.
docker exec -it [컨테이너] sh -c "curl -sS -m 10 -o /dev/null -w '%{http_code}\n' https://api.telegram.org/"
302가 나왔고, 봇이 1:1 대화에 대답하기 시작했다.
이 방법의 약점
docker exec로 넣은 내용은 컨테이너를 재시작해도 남지만, 새로 만들면 사라진다. 이미지를 업데이트하거나 docker compose down 후 다시 올리면 컨테이너가 새로 만들어지고, 그 순간 같은 증상이 조용히 돌아온다. 그때는 왜 다시 안 되는지 기억도 안 날 것이다.
오래가게 하려면 서버의 파일을 컨테이너에 붙여두는 방법이 있다. compose 파일에 이렇게 넣는다.
volumes:
- /etc/gai.conf:/etc/gai.conf:ro
이러면 컨테이너를 몇 번을 새로 만들어도 서버의 설정을 그대로 읽는다.
하나 더. 알파인 리눅스 기반 이미지는 gai.conf를 아예 읽지 않는다. 그런 컨테이너라면 이 방법 자체가 통하지 않고, IPv6를 끄는 쪽으로 가야 한다.
두 번째 벽 – 1:1은 되는데 그룹에서는 조용하다
네트워크가 뚫렸다. 1:1 대화는 된다. 그런데 그룹에서 말을 걸면 여전히 조용했다.
봇이 받은 메시지를 직접 확인해봤다.
$ curl "https://api.telegram.org/bot<TOKEN>/getUpdates"
{"ok":true,"result":[]}
그룹에 메시지를 보내도 비어 있었다. 봇에게 그룹 메시지가 아예 전달되지 않고 있었다.
(참고로 이 명령은 에이전트가 같은 봇으로 메시지를 받아가고 있으면 제대로 동작하지 않는다. 한 봇을 두 곳에서 동시에 폴링하면 텔레그램이 409 Conflict로 거절하기 때문이다. 이 제약은 나중에 워크플로 도구를 붙일 때 다시 만난다.)
Group Privacy
텔레그램 봇은 기본적으로 그룹의 모든 메시지를 받지 않는다. /로 시작하는 명령이나 봇에게 직접 단 답장 정도만 받는다. 이걸 privacy mode라고 부르고, 기본값이 켜져 있다.
명령어로만 부르는 봇이라면 이게 맞다. 그런데 AI 비서는 평범한 문장을 받아야 한다. 그래서 꺼야 한다.
BotFather에서 /mybots → 봇 선택 → Bot Settings → Group Privacy → Turn off.


BotFather에서 Group Privacy를 껐다. 그런데 이것만으로는 안 됐다.
나는 이걸 이미 꺼둔 상태였다. 설정 화면에서 분명히 Disabled를 확인했다. 그런데도 그룹 메시지가 안 왔다.
원인 2 – 설정은 그룹에 들어갈 때 적용된다
Group Privacy 설정은 봇이 그룹에 들어가는 시점에 적용된다. 이미 그룹에 들어가 있는 봇은, 나중에 설정을 바꿔도 들어갈 때의 상태를 그대로 유지한다.
나는 봇을 그룹에 먼저 초대하고, 그다음에 privacy를 껐다. 순서가 반대였다.
해결은 단순했다. 봇을 그룹에서 내보냈다가 다시 초대했다. 그 뒤로 그룹 메시지에 대답하기 시작했다.
이건 나만 겪은 게 아니다. 다른 AI 에이전트 프로젝트의 저장소에도 똑같은 증상이 이슈로 올라와 있다. 1:1은 정상인데 그룹 메시지는 로그에 한 줄도 안 찍히고, privacy를 끈 뒤 봇을 제거했다가 다시 넣어야 해결된다는 내용이다. 온보딩 문서에 이 순서가 안 적혀 있다는 지적까지 똑같다.
확인하는 법
지금 봇의 privacy 설정이 실제로 꺼져 있는지는 API로 볼 수 있다.
curl "https://api.telegram.org/bot<TOKEN>/getMe"
응답에 "can_read_all_group_messages": true가 있으면 봇 쪽 설정은 꺼진 게 맞다.
이게 true인데도 그룹이 조용하다면, 재초대가 답이다.
설정은 맞는데 그룹이 옛 상태를 기억하고 있는 것이다.
두 벽의 공통점
돌아보면 두 원인은 같은 모양이었다.
| 바꾼 곳 | 실제로 적용되는 곳 | |
|---|---|---|
| IPv6 | 서버의 gai.conf | 컨테이너 안의 gai.conf |
| Group Privacy | BotFather 설정 | 그룹에 들어가던 순간의 상태 |
설정을 바꾼 곳과 그 설정이 적용되는 곳이 달랐다. 앞 편의 sshd도 그랬고, 시간대도 그랬다. 이 시리즈에서 막힌 곳은 대부분 이 형태다.
그리고 원인이 두 개일 때 조심할 게 하나 있다.
첫 번째를 고친 순간 “고쳤는데 안 된다”가 온다. 이때 방금 고친 걸 의심하기 쉽다.
그런데 증상이 바뀌었는지를 먼저 봐야 한다. “연결 자체가 안 된다”에서 “1:1은 되는데 그룹만 안 된다”로 바뀌었다면, 첫 번째 수정은 맞았고 한 칸 전진한 것이다. 다음 벽을 찾으면 된다.
진단 순서로 정리하면 이렇다.
- 범위를 좁힌다 — 텔레그램만 안 되나, 다른 곳도 안 되나
- 해석하기 전에 한 번 더 확인한다 — 막힌 건지, 길이 다른 건지
- 한 겹씩 벗긴다 — 서버 다음 컨테이너, 1:1 다음 그룹
- 증상이 바뀌면 전진한 것이다 — 방금 고친 걸 되돌리지 않는다
이 편에서 막힌 것
증상 – 텔레그램 그룹에서 봇이 아무 대답을 안 했다.
원인 1 – 서버에 IPv6 주소는 있는데 IPv6 경로가 막혀 있었다. 서버가 IPv6를 먼저 시도하다 시간 초과.curl -4로 판별했다.
해결 1 –/etc/gai.conf에서 IPv4 우선 줄의 주석 해제. 서버와 컨테이너에 각각. 컨테이너 쪽은 새로 만들면 사라지므로 볼륨으로 붙이는 게 오래간다.
원인 2 – Group Privacy를 봇이 그룹에 들어간 뒤에 껐다. 설정은 들어가는 시점에 적용된다.
해결 2 – 봇을 그룹에서 내보냈다가 다시 초대.getMe의can_read_all_group_messages로 확인.
빗나간 길 – “텔레그램만 안 된다”를 “텔레그램이 막혔다”로 해석해 지원팀 문의와 리전 이전까지 검토했다.curl -4한 줄로 뒤집혔다.
다음 편 – 봇이 대답하기 시작했으니 창구를 정리한다. 텔레그램 포럼(토픽)으로 AI 비서 창구를 나눈 이야기.
참고
- 텔레그램 봇 기능 문서 (Privacy mode) — https://core.telegram.org/bots/features#privacy-mode
- NVIDIA NemoClaw 이슈 #4068, 그룹 채팅에서 봇이 조용히 실패하는 문제 — https://github.com/NVIDIA/NemoClaw/issues/4068
