[FastAPI + Ollama] 404의 진짜 범인을 찾아서 — Hunting the Real 404

라우트는 있는데 왜 404가 날까요?
FastAPI + Ollama 삽질기 🔍

집에서 mini AI-RAG와 챗봇 서비스를 앱에 간신히 연결했습니다. 아직 프로토타입이라 텍스트밖에 없어 볼품없지만, 딱 붙여서 되었을 땐 정말 뿌듯했습니다. 그 과정에서 생긴 이야기입니다.

⚡ FastAPI 🐳 Docker 🤖 Ollama ☁️ Cloud Run

🏗️ 서비스 구성

📱 앱 ☁️ Cloud Run 🔒 Cloudflare 🏠 공유기 🖥️ 운영 서버 💻 개발 서버 (RTX 3060)
😰레이어가 쌓일수록 이상한 일이 생겼습니다

iptables + NordVPN + Docker가 얽히면서 패킷이 사라졌고, tcpdump로 겨우 방향을 잡았습니다. 맥북 SSH 터널이 끊겼다 붙었다 하면서 개발 서버가 요청을 통째로 삼키는 증상도 있었습니다. 로그엔 아무것도 안 찍히는데 서버는 살아있는 상황 — 껐다 켜니까 바로 해결됐습니다.

그걸 뚫고 나니 이번엔 404가 기다리고 있었습니다.

🚨증상: 라우트는 있는데 404

Docker 컨테이너에서 POST /internal/llm/chat 요청이 계속 404를 반환했습니다. 개발 서버에서는 정상 동작. 라우트 목록을 직접 출력해봤더니 엔드포인트는 분명히 등록되어 있었습니다.

💡 내부(127.0.0.1)에서는 401 Unauthorized, 외부(nginx)에서는 404 Not Found. 같은 컨테이너, 같은 프로세스에서 다른 결과가 나왔습니다.

🔎결정적 단서: 응답 크기

nginx 액세스 로그의 응답 크기가 이상했습니다.

⚠️ FastAPI 기본 404 응답 {"detail":"Not Found"} = 22바이트
실제 응답 크기 = 64~89바이트
누군가 다른 404를 만들고 있었습니다.

역산해보니 Ollama의 에러 메시지였습니다.

{“detail”:”model ‘llama3’ not found, try pulling it first”}
🕵️원인: upstream 에러 전파

클라이언트 서비스가 GCP Secret Manager에서 OLLAMA_MODEL=llama3를 읽어 요청 body에 담아 보내고 있었고, 서버에 실제 설치된 모델은 llama3.1:8b였습니다. Ollama의 404가 FastAPI를 통해 그대로 전파된 것입니다. 라우팅 문제가 아니라 upstream 에러 전파 문제였습니다.

🤖AI와 함께 판 이야기

AI에게 대량의 로그 분석을 맡겼는데, 어느 순간 AI가 설계 구성 자체를 무너뜨리는 방향으로 유도하기 시작했습니다. Claude와 함께 다시 처음부터 파고들어서 간신히 실제 원인을 찾아냈습니다.

AI는 몇 주의 삽질을 며칠로 줄여줬지만, 설계를 지키는 건 결국 사람의 몫이었습니다. 네트워크 관리 포인트가 늘어날수록, AI도 길을 잃습니다.


📝이 경험에서 얻은 교훈
1️⃣

응답 크기를 확인하라

22바이트 vs 64바이트. 같은 404도 내용이 다릅니다. 응답 크기 하나가 결정적 단서가 됩니다.

2️⃣

404가 항상 라우팅 문제는 아니다

Upstream 에러는 조용히 전파됩니다. 명시적으로 처리하지 않으면 추적이 매우 어렵습니다.

3️⃣

Upstream 에러는 502/503으로 변환하라

내부 오류와 라우팅 오류를 같은 코드로 반환하면 디버깅 시간이 몇 배로 늘어납니다.

4️⃣

이해 안 되면 일단 끄고 켜라 — 진지하게

뭔가 정말 이해 안 되는 상황이 생기면, 일단 정리하고 컴퓨터를 껐다 키는 게 정신건강에 좋습니다.

로그가 조용할수록, 시야를 넓혀야 합니다. 🔭
그리고 뭔가 이상하면 — 일단 재부팅부터.

#Backend#FastAPI#Docker#Ollama#AI#RAG#Debugging#SoftwareEngineering

The Route Exists. So Why Is It Returning 404?
A FastAPI + Ollama Deep Dive 🔍

I finally got my mini AI-RAG and chatbot service talking to my app. Still a prototype — text only, nothing fancy to look at. But when it all clicked together, that feeling made every late night worth it.

⚡ FastAPI 🐳 Docker 🤖 Ollama ☁️ Cloud Run

🏗️ Service Architecture

📱 App ☁️ Cloud Run 🔒 Cloudflare 🏠 Home Router 🖥️ Prod Server 💻 Dev Server (RTX 3060)
😰As the layers stacked up, strange things started happening

iptables + NordVPN + Docker were interfering with each other — packets were vanishing, and tcpdump was the only thing giving me any direction. My MacBook’s SSH tunnel kept dropping and reconnecting, and at some point the dev server started silently swallowing requests. Nothing in the logs, server still running. Rebooted the MacBook. Fixed immediately.

Once I got through all that, a 404 was waiting for me.

🚨The route exists. So why 404?

POST /internal/llm/chat kept returning 404 inside Docker while working perfectly on the dev server. I printed the route table directly — the endpoint was clearly registered. It wasn’t a proxy issue either, since requests were showing up in the FastAPI logs.

💡 From inside the container (127.0.0.1): 401 Unauthorized. From outside via nginx: 404 Not Found. Same container, same process, different results.

🔎The key clue: response size

The response sizes in the nginx access log looked wrong.

⚠️ FastAPI default 404 {"detail":"Not Found"} = 22 bytes
Actual response sizes = 64–89 bytes
Something else was generating that 404.

Working backwards, it was an Ollama error message:

{“detail”:”model ‘llama3’ not found, try pulling it first”}
🕵️Root cause: upstream error propagation

A client service was reading OLLAMA_MODEL=llama3 from GCP Secret Manager and passing it in the request body. The model actually installed was llama3.1:8b. Ollama’s 404 was propagating straight through FastAPI to the client — making it look like the route didn’t exist. Not a routing problem. An upstream error propagation problem.

🤖Debugging with AI

I had been relying on AI to analyze the flood of logs. At some point, it started nudging me toward tearing down the architecture itself. I stepped back, worked through it with Claude from scratch, and eventually tracked down the real cause.

AI compressed what could have been weeks of debugging into days. But holding the design together was still a human job. The more network layers you add, the easier it is for AI to lose the thread too.


📝What I took away
1️⃣

Check the response size

22 bytes vs 64 bytes. Two 404s can mean completely different things. That single number was the decisive clue.

2️⃣

404 doesn’t always mean routing failure

Upstream errors propagate silently. If you don’t handle them explicitly, they’re very hard to trace.

3️⃣

Wrap upstream errors as 502/503

Returning internal errors and routing errors with the same status code multiplies your debugging time significantly.

4️⃣

When nothing makes sense — reboot. Seriously.

Sometimes the most powerful debugging tool is turning it off and back on. Your mental health will thank you.

When the logs go quiet, widen your view. 🔭
And when nothing makes sense — reboot first.

#Backend#FastAPI#Docker#Ollama#AI#RAG#Debugging#SoftwareEngineering