/인프라/Ingress 404 default backend·503 no healthy upstream 30초 진단표
인프라kubernetes-ingressingress-nginx

Ingress 404 default backend·503 no healthy upstream 30초 진단표

Kubernetes Ingress 404 default backend, 503 no healthy upstream, 502 too big header를 응답 문자열만으로 원인 계층에 매핑하는 판정표와 30초 확진 6단계, 복붙 가능한 수정 YAML과 컨트롤러별 차이까지 정리했습니다.

Ingress 404 default backend·503 no healthy upstream 30초 진단표

엔드포인트는 정상인데 브라우저만 404일 때

이 글은 K8s 네트워킹 심화 가이드 4편입니다. 3편에서 kubectl get endpointslice로 백엔드 Pod가 Service에 정상 등록된 것까지 확인했다는 전제에서 출발합니다.

전형적인 상황은 이렇습니다.

Bash
kubectl port-forward svc/myapp 8080:80
curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8080/
# 200

curl -s -o /dev/null -w '%{http_code}\n' https://example.com/
# 404

포트포워딩이 200을 주는 순간, Service → Pod 구간은 이미 무죄입니다. 문제는 그 앞단, 즉 L7 진입 경로에 있습니다.

CODE
[클라이언트]
    │  ① DNS / 방화벽 / 보안그룹
    ▼
[Cloud LB (ELB/GLB/Azure LB)]
    │  ② NodePort or LB 타깃 등록 / health check
    ▼
[Ingress Controller Pod (ingress-nginx 등)]
    │  ③ Ingress 리소스 매칭: host / path / ingressClassName
    ▼
[Service (ClusterIP)]
    │  ④ selector → EndpointSlice  ← 3편 범위
    ▼
[Pod]

명시적 분기 안내: kubectl get endpointslice -l kubernetes.io/service-name=myapp 결과에서 ADDRESSES가 비어 있거나 <none>이면 이 글이 아니라 3편(Endpoints <none>·no endpoints available 원인 편)으로 가야 합니다. 이 글은 엔드포인트가 채워져 있는데도 외부 접근만 실패하는 ①~③ 구간 전용입니다.

Ingress Controller 자체의 기본 구성과 라우팅 개념이 아직 낯설다면 Kubernetes Ingress 완전 가이드 — Nginx Ingress Controller로 외부 트래픽 라우팅을 먼저 훑고 오시면 이 글의 진단 절차가 훨씬 빨리 읽힙니다.

응답 원문 판정표 — 문자열 하나로 원인 계층 직행

브라우저나 curl -v에 찍힌 문자열만으로 원인 계층을 좁힐 수 있습니다. 아래 표에서 자기 상황을 먼저 찾으세요.

응답 원문 / 로그원인 계층다음 행동
HTTP/1.1 404 Not Found + 헤더 Server: nginx + 바디 <html><head><title>404 Not Found</title></head><body><center><h1>404 Not Found</h1></center><hr><center>nginx</center></body></html>③ Ingress 리소스 매칭 실패 — host/path/ingressClassName 중 하나가 안 맞아 default backend로 떨어짐확진 6단계의 ①~③
HTTP/1.1 503 Service Temporarily Unavailable + 컨트롤러 로그 no healthy upstream 또는 upstream connect error③→④ 경계 — Ingress는 매칭됐으나 backend Service 이름·포트 불일치, 또는 upstream이 빈 상태확진 6단계의 ④~⑤
HTTP/1.1 502 Bad Gateway + 로그 upstream sent too big header while reading response header from upstream③ 프록시 버퍼 부족(대형 Set-Cookie/JWT 헤더)조치표 proxy-buffer-size 항목
curl: (28) Operation timed out / 브라우저 ERR_CONNECTION_TIMED_OUT응답 헤더 자체가 없음①~② LB·보안그룹·NodePort 등 Ingress 이전 구간확진 6단계의 ⑥ + 결론부 실패 분기
TLS 경고 + 인증서 CN이 Kubernetes Ingress Controller Fake Certificate③ TLS Secret 미탑재 또는 tls.hosts와 요청 host 불일치조치표 TLS 항목

판정의 핵심은 응답 헤더가 존재하는가입니다. 헤더가 하나라도 돌아왔다면 Ingress Controller까지는 패킷이 닿은 것이고, 아예 없다면 그 앞에서 끊긴 것입니다.

30초 확진 6단계

순서대로 복붙해서 실행합니다. 각 단계의 정상/이상 출력을 나란히 비교하세요.

① Ingress가 컨트롤러에 인식됐는지

Bash
kubectl get ingress -o wide

정상 출력:

CODE
NAME    CLASS   HOSTS         ADDRESS         PORTS     AGE
myapp   nginx   example.com   203.0.113.10    80, 443   12m

이상 출력:

CODE
NAME    CLASS    HOSTS         ADDRESS   PORTS   AGE
myapp   <none>              *           80      12m

CLASS<none>이거나 ADDRESS가 계속 공란이면 컨트롤러가 이 Ingress를 아예 집어가지 않은 상태입니다. ③으로 건너뜁니다.

② describe로 Events와 default backend 확인

Bash
kubectl describe ingress myapp

정상:

CODE
Rules:
  Host          Path  Backends
  ----          ----  --------
  example.com
                /     myapp:80 (10.244.1.23:8080,10.244.2.11:8080)
Events:
  Type    Reason  Age   From                      Message
  Normal  Sync    2m    nginx-ingress-controller  Scheduled for sync

이상 (두 가지 대표 패턴):

CODE
Default backend:  <default> (<error: endpoints "default-http-backend" not found>)
CODE
                /     myapp:8080 (<none>)

앞은 매칭 규칙이 하나도 살아있지 않은 것, 뒤는 Service는 찾았지만 upstream이 비어 있는 503 직행 패턴입니다. EventsSync 자체가 없으면 컨트롤러가 이 리소스를 watch하지 않고 있다는 뜻입니다.

③ ingressClassName 확인 (v1.22+ 필수 체크)

Bash
kubectl get ingress myapp -o jsonpath='{.spec.ingressClassName}{"\n"}'
kubectl get ingress myapp -o jsonpath='{.metadata.annotations}{"\n"}'
kubectl get ingressclass

정상:

CODE
nginx
{"kubernetes.io/ingress.class":"nginx"}
NAME    CONTROLLER                      PARAMETERS   AGE
nginx   k8s.io/ingress-nginx            <none>       30d

이상: 첫 명령이 빈 줄만 출력되고 어노테이션에만 kubernetes.io/ingress.class가 있는 경우. 레거시 어노테이션은 Kubernetes 1.22에서 폐기 대상이 되었고, 컨트롤러 버전·기동 플래그에 따라 조용히 무시될 수 있습니다. kubectl get ingressclass 결과의 NAMEspec.ingressClassName 값이 문자 단위로 동일해야 합니다.

④ backend Service 이름·포트 대조

Bash
kubectl get ingress myapp -o jsonpath='{range .spec.rules[*].http.paths[*]}{.backend.service.name}{" -> "}{.backend.service.port}{"\n"}{end}'
kubectl get svc myapp -o jsonpath='{range .spec.ports[*]}{.name}{" "}{.port}{" -> "}{.targetPort}{"\n"}{end}'

정상:

CODE
myapp -> {"number":80}
http 80 -> 8080

이상:

CODE
myapp -> {"name":"https"}
http 80 -> 8080

Ingress가 참조한 포트 이름이 Service의 포트 이름 목록에 없으면 upstream이 만들어지지 않고 503이 납니다. 숫자로 참조할 때는 targetPort가 아니라 Service의 port 값을 써야 합니다.

⑤ 컨트롤러 로그 실시간 확인

Bash
kubectl logs -n ingress-nginx deploy/ingress-nginx-controller --tail=50

정상(요청이 실제로 들어오고 200):

CODE
10.0.1.5 - - [10/Aug/2026:04:11:02 +0000] "GET / HTTP/1.1" 200 1256 "-" "curl/8.4.0" 84 0.004 [default-myapp-80] [] 10.244.1.23:8080 1256 0.004 200

이상:

CODE
2026/08/10 04:12:31 [error] 31#31: *117 no live upstreams while connecting to upstream, client: 10.0.1.5, server: example.com, request: "GET / HTTP/1.1", upstream: "http://upstream-default-backend/"
CODE
2026/08/10 04:13:02 [error] 31#31: *120 upstream sent too big header while reading response header from upstream, client: 10.0.1.5, ...

대괄호 안의 [default-myapp-80]가 비어 있거나([]) upstream-default-backend로 찍히면 매칭 실패가 확정입니다.

⑥ DNS 요인 배제

Bash
curl -H 'Host: example.com' http://<LB-IP>/ -v

<LB-IP>kubectl get svc -n ingress-nginx ingress-nginx-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip}'로 얻습니다.

정상:

CODE
< HTTP/1.1 200 OK
< Server: nginx

이상 A — Host 헤더를 주면 200인데 도메인으로는 실패 → DNS/CNAME 문제. 클러스터는 정상입니다. 이상 B — 여기서도 curl: (28) 타임아웃 → LB·보안그룹·NodePort 구간. 아래 실패 분기로.

원인별 조치표

증상확인 명령수정검증
404 default backend, CLASS <none>kubectl get ingress -o wideingressClassName 추가kubectl get ingress -o wide에 CLASS·ADDRESS 표시
/api는 되고 /api/users만 404kubectl get ing myapp -o yaml | grep pathTypepathType: Prefix로 변경curl -o /dev/null -w '%{http_code}' https://example.com/api/users
503, describe에 (<none>)위 확진 ④Service 포트 이름/번호 정정kubectl describe ing myapp에 IP 목록 표시
502 + too big header컨트롤러 로그proxy-buffer-size 어노테이션로그에 에러 미재현, 200 응답
백엔드가 /api 프리픽스를 모름앱 라우팅 확인rewrite-target + 캡처그룹로그의 upstream 경로 확인
Fake Certificate 경고kubectl get secret myapp-tlsspec.tls 연결curl -vI https://example.com 인증서 CN

1) ingressClassName 추가

YAML
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp
  namespace: default
spec:
  ingressClassName: nginx
  rules:
  - host: example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: myapp
            port:
              number: 80

2) pathType 수정 — 404의 숨은 단골

Exact는 문자열이 정확히 같아야만 매칭됩니다. /apiExact를 쓰면 /api/users는 매칭되지 않고 default backend로 떨어집니다.

YAML
      paths:
      - path: /api
        pathType: Prefix      # Exact → Prefix
        backend:
          service:
            name: myapp
            port:
              number: 80
pathType/api 요청/api/users/apiv2
Exact매칭미매칭미매칭
Prefix매칭매칭미매칭(경로 요소 단위 비교)
ImplementationSpecific컨트롤러 구현에 위임구현별 상이구현별 상이

ImplementationSpecific은 컨트롤러를 교체하는 순간 라우팅이 바뀔 수 있어 이식성이 필요하면 피하는 편이 안전합니다.

3) backend Service 포트 정정

YAML
# Service
apiVersion: v1
kind: Service
metadata:
  name: myapp
spec:
  selector:
    app: myapp
  ports:
  - name: http
    port: 80
    targetPort: 8080
---
# Ingress backend — name 참조 시 Service의 ports[].name과 동일해야 함
          service:
            name: myapp
            port:
              name: http

4) proxy-buffer-size (502 too big header)

YAML
metadata:
  annotations:
    nginx.ingress.kubernetes.io/proxy-buffer-size: "16k"
    nginx.ingress.kubernetes.io/proxy-buffers-number: "4"

기본값이 작아 대형 Set-Cookie나 긴 JWT를 담은 응답 헤더에서 터집니다. 8k → 16k → 32k 순으로 올리며 확인합니다.

5) rewrite-target + 정규식 캡처그룹

/api/users 요청을 백엔드에 /users로 넘기려는 경우입니다.

YAML
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp
  annotations:
    nginx.ingress.kubernetes.io/use-regex: "true"
    nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
  ingressClassName: nginx
  rules:
  - host: example.com
    http:
      paths:
      - path: /api(/|$)(.*)
        pathType: ImplementationSpecific
        backend:
          service:
            name: myapp
            port:
              number: 80

자주 틀리는 지점: 캡처그룹 번호를 $1로 쓰면 / 또는 빈 문자열만 전달됩니다. (/|$)가 첫 번째 그룹이므로 실제 경로는 $2입니다. 정규식 경로에는 pathType: Prefix가 아니라 ImplementationSpecific을 써야 의도대로 동작합니다.

6) TLS Secret 연결

Bash
kubectl create secret tls myapp-tls --cert=tls.crt --key=tls.key -n default
YAML
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - example.com
    secretName: myapp-tls
  rules:
  - host: example.com
    ...

tls.hosts의 값과 rules.host, 그리고 인증서의 SAN이 세 곳 모두 일치해야 Fake Certificate가 사라집니다. Secret은 Ingress와 같은 네임스페이스에 있어야 합니다.

조용히 실패하는 함정 3가지

  • 네임스페이스 경계: Ingress는 같은 네임스페이스의 Service만 참조합니다. 다른 ns의 Service 이름을 적으면 에러 없이 upstream만 비고 503이 납니다. ExternalName 타입 Service를 같은 ns에 두어 우회하는 방법이 있습니다.
  • 동일 host 중복 선언: 여러 Ingress가 같은 host를 선언하면 경로가 병합되지만, 같은 path가 겹치면 대체로 생성 시각이 앞선 리소스가 우선합니다. kubectl get ingress -A | grep example.com으로 중복부터 확인하세요.
  • IngressClass 이름 불일치: 컨트롤러 기동 인자 --ingress-class=nginx와 IngressClass 리소스 이름이 다르면 아무것도 매칭되지 않습니다. kubectl -n ingress-nginx get deploy ingress-nginx-controller -o yaml | grep ingress-class로 대조합니다.

컨트롤러별 차이와 버전 분기

같은 증상이라도 컨트롤러마다 메시지와 기본값이 다릅니다.

항목ingress-nginxTraefikAWS ALB Controller
매칭 실패 메시지nginx 기본 404 페이지 (<center>nginx</center>)404 page not found 평문ALB 기본 404 (JSON/빈 바디, Server: awselb/2.0)
upstream 없음503 + 로그 no live upstreams503 Service Unavailable502/타깃 unhealthy
어노테이션 접두사nginx.ingress.kubernetes.io/traefik.ingress.kubernetes.io/alb.ingress.kubernetes.io/
프록시 타임아웃 기본60초 계열(read/send)무제한 계열, 명시 설정 권장idle timeout 60초
요청 바디 크기 기본1m (proxy-body-size로 변경)제한 없음에 가까움ALB 계층 제한 별도

정확한 기본값은 배포한 차트 버전에 따라 달라지므로, 실제 값은 각 프로젝트 공식 문서와 kubectl -n ingress-nginx get cm ingress-nginx-controller -o yaml로 확인하시기 바랍니다.

버전 분기표

변경버전증상대응
networking.k8s.io/v1beta1 제거Kubernetes 1.22error: unable to recognize ... no matches for kind "Ingress" in version "networking.k8s.io/v1beta1"v1으로 변환. serviceName/servicePortservice.name/service.port 구조 변경 동반
kubernetes.io/ingress.class 어노테이션 폐기1.18 deprecated, 1.22 이후 사실상 정리리소스는 생성되나 CLASS 공란·미매칭spec.ingressClassName 사용
snippet 어노테이션 제한ingress-nginx 1.xadmission webhook이 configuration-snippet 거부ConfigMap allow-snippet-annotations 정책 검토, 가능하면 표준 어노테이션으로 대체
annotation value blocklist·정규식 검증 강화ingress-nginx 1.xadmission webhook "validate.nginx.ingress.kubernetes.io" denied the request거부 사유 문구를 그대로 읽고 해당 어노테이션 값 정리

apply가 webhook에서 막힐 때는 우회 설정을 찾기 전에 거부 메시지 원문부터 읽는 편이 빠릅니다. 어떤 어노테이션의 어떤 토큰이 걸렸는지가 대부분 그대로 적혀 있습니다.

참고로 Ingress API는 사실상 기능 동결 상태이며 신규 라우팅 기능은 Gateway API 쪽으로 이관되는 흐름입니다. 다만 이번 편의 진단 대상은 Ingress로 한정합니다. 5xx를 Gateway API까지 포함해 넓게 보려면 K8s 5xx 에러 원인 분석: Ingress/Gateway API 7단계 디버깅 가이드를 함께 보시면 좋습니다.

그래도 안 될 때의 실패 분기

6단계를 다 돌렸는데도 해결되지 않으면 아래 세 가지를 순서대로 봅니다.

1) NetworkPolicy 차단 — 애플리케이션 네임스페이스에 default deny가 걸려 있고 ingress-nginx 네임스페이스에서 오는 트래픽을 허용하지 않은 경우입니다. Ingress는 매칭되는데 upstream 연결만 타임아웃됩니다.

Bash
kubectl get networkpolicy -A
YAML
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-from-ingress-nginx
  namespace: default
spec:
  podSelector:
    matchLabels:
      app: myapp
  policyTypes: ["Ingress"]
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: ingress-nginx

2) externalTrafficPolicy: Local — 컨트롤러 Pod가 없는 노드로 들어온 요청이 드롭되어 "가끔 되고 가끔 안 되는" 간헐적 실패로 나타납니다.

Bash
kubectl -n ingress-nginx get svc ingress-nginx-controller -o jsonpath='{.spec.externalTrafficPolicy}{"\n"}'

3) LB health check 경로 불일치 — 클라우드 LB가 /로 헬스체크하는데 애플리케이션은 /healthz만 200을 주면 타깃이 전부 unhealthy가 되고, 이때 증상은 헤더 없는 타임아웃입니다. 클라우드 콘솔의 타깃 그룹 상태를 직접 확인해야 합니다.

Pod 자체가 뜨지 않아 upstream이 비는 상황이라면 Pod Pending FailedScheduling 0/3 nodes 30초 진단·복구 런북이, 라우팅은 정상인데 지연이 문제라면 Kubernetes 네트워크 지연, eBPF로 근본 원인 진단하고 성능 최적화하는 완벽 가이드가 이어지는 다음 문서입니다.

다음 5편에서는 L7 진입 이후 구간인 TLS 종료와 cert-manager 인증서 발급 실패를 다룹니다.

자주 묻는 질문 (FAQ)

Q. 404가 뜨는데 Ingress 리소스는 분명히 만들었습니다. 무엇부터 볼까요? A. kubectl get ingress -o wideCLASSADDRESS 두 컬럼입니다. CLASS<none>이면 spec.ingressClassName이 없거나 IngressClass 이름과 다른 것이고, ADDRESS가 계속 공란이면 컨트롤러가 리소스를 인식하지 못한 상태입니다. 리소스가 존재하는 것과 컨트롤러가 반영한 것은 별개입니다.

Q. no healthy upstream 503과 502 Bad Gateway는 어떻게 구분하나요? A. 503은 보낼 upstream 자체가 없는 상태(Service 이름·포트 불일치, 엔드포인트 비어 있음)이고, 502는 upstream에 연결은 했으나 응답 처리에 실패한 상태입니다. 502는 컨트롤러 로그에 upstream sent too big header, connection reset 같은 구체적 사유가 함께 남으므로 로그 원문을 먼저 확인하세요.

Q. /api로는 되는데 /api/users가 404입니다. A. pathType: Exact일 가능성이 가장 큽니다. Prefix로 바꾸면 해결되며, 백엔드에 프리픽스를 제거해 전달해야 한다면 use-regex: "true" + path: /api(/|$)(.*) + rewrite-target: /$2 조합을 사용합니다. 이때 $1이 아니라 $2라는 점이 자주 틀리는 부분입니다.

✦ ✦ ✦
편집 검토 · Editorial Review

AI 도구는 자료 조사와 초안 작성의 보조 수단으로 사용될 수 있습니다. Nodelog는 공개 전 내용과 출처를 검토하고, 환경(OS·버전)에 따라 결과가 달라질 수 있는 기술 정보는 공식 문서를 함께 확인하도록 안내합니다. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.

편집 책임 · Nodelog 기술 편집팀·발행 ·

댓글

첫 번째 댓글을 남겨보세요.