Kubernetes Ingress 504 Gateway Timeout

Kubernetes Ingress 504 Gateway Timeout: How to Debug It with Helm

A Kubernetes Ingress returning 504 Gateway Timeout does not necessarily mean your application is down.

That is one of the most common mistakes when debugging this error.

A 504 means that something in the request path waited for an upstream response and eventually gave up. The component returning the 504 could be the Ingress controller, a cloud load balancer, another reverse proxy, or an upstream service.

The difficult part is identifying which layer actually timed out.

This guide shows how to debug kubernetes ingress 504 gateway timeout errors systematically, how to verify the configuration with Helm, how to test the backend without the Ingress, and how to avoid the common mistake of changing timeout values blindly.

Debugging Kubernates Ingress 504 Gateway time out errors

Testing note: The commands and configuration checks in this guide are based on Kubernetes and Ingress-NGINX documentation plus real publicly documented incidents. I am not claiming that the commands were executed against a live production Kubernetes cluster from this writing environment. A live cluster test requires access to an actual Kubernetes environment with kubectl, the relevant Ingress controller, Helm, and its surrounding load-balancer infrastructure.


Requirements Before You Start

Before troubleshooting a 504, collect enough information to identify every layer involved.

Kubernetes clusters

You should have:

  • kubectl access to the affected cluster
  • Helm access if the controller was installed or managed with Helm
  • the Kubernetes namespace containing the affected Ingress
  • the Ingress name
  • the Service name and port
  • access to the application Pod logs
  • access to the Ingress controller logs
  • access to the cloud load-balancer configuration if one exists
  • the controller type and version
  • the Kubernetes version
  • the Helm chart version, if Helm manages the controller
  • permission to run an internal request from inside the cluster

For a Helm-managed Ingress-NGINX deployment, these commands establish the basic environment:

kubectl version
helm version

helm list -A

kubectl get ingress -A
kubectl get ingressclass
kubectl get pods -A

Do not start by changing the timeout.

First establish where the request is failing.


What Does a Kubernetes Ingress 504 Gateway Timeout Mean?

A 504 is a gateway or proxy timeout.

In a typical Kubernetes application, the request can travel through several components:

User
  |
  v
DNS
  |
  v
Cloud Load Balancer
  |
  v
Ingress Controller
  |
  v
Ingress Rule
  |
  v
Kubernetes Service
  |
  v
Endpoint / EndpointSlice
  |
  v
Pod
  |
  v
Application

Kubernetes itself does not define one universal “Ingress timeout.”

Ingress Controller

An Ingress is an API object containing HTTP routing rules. An Ingress controller is responsible for implementing those rules, and different controllers can behave differently. Kubernetes explicitly notes that an Ingress requires a controller and recommends reviewing the controller-specific documentation.

Therefore:

A Kubernetes Ingress 504 is a symptom, not a diagnosis.

The timeout may be caused by:

  • the external load balancer
  • the Ingress controller
  • the connection from the controller to the backend
  • the Kubernetes Service
  • missing or unhealthy endpoints
  • NetworkPolicy or networking problems
  • slow application processing
  • application resource exhaustion
  • an upstream API
  • TLS problems between proxy and backend
  • controller configuration
  • controller version-specific behavior
  • client-side connection behavior

Why Simply Increasing the Ingress Timeout Often Does Not Work

This is the most important concept in the entire troubleshooting process.

Suppose your request passes through these layers:

Client
   |
   | 60s
   v
Load Balancer
   |
   | 300s
   v
Ingress Controller
   |
   | 600s
   v
Application

If the load balancer terminates the connection after 60 seconds, increasing the Ingress timeout from 60 to 600 seconds does not solve the problem.

The request never gets the opportunity to use the 600-second timeout.

The shortest relevant timeout in the request path can win.

AWS documentation, for example, states that an Application Load Balancer can return HTTP 504 when it establishes a connection to a target but the target does not respond before the load balancer’s idle timeout. Its default idle timeout is 60 seconds.

That gives us a much better troubleshooting model:

LayerWhat can time out?What to inspect
ClientClient-side connectionBrowser, curl, application client
Load balancerIdle/target connectionLB configuration and access logs
Ingress controllerUpstream connection/read/writeController ConfigMap and annotations
ServiceBackend routingService selectors and ports
EndpointSliceAvailable backend endpointsEndpointSlice objects
NetworkConnection pathNetworkPolicy, routing, security groups
PodReadiness/availabilityPod status and probes
ApplicationSlow processingApplication logs and metrics
External APIUpstream dependencyApplication dependency logs

This is why debugging must proceed from the outside toward the backend and then back outward.


First Check: Which Ingress Controller Are You Actually Using?

Do not assume that “Ingress” means Ingress-NGINX.

Kubernetes Networking & NGINX with error Debugging

Run:

kubectl get ingressclass

Then:

kubectl describe ingressclass <ingress-class-name>

Also inspect the Ingress:

kubectl get ingress <ingress-name> -n <namespace> -o yaml

Look for:

spec:
  ingressClassName: nginx

The modern Kubernetes field is spec.ingressClassName. Kubernetes documents it as the replacement for the older annotation-based approach.

If your cluster uses another controller, such as AWS, GKE, Traefik, HAProxy, or another implementation, do not blindly apply Ingress-NGINX annotations.

For example:

nginx.ingress.kubernetes.io/proxy-read-timeout: "300"

only makes sense when the controller actually understands that annotation.


Important 2026 Warning: Ingress-NGINX Is Retired

If you are troubleshooting an existing Ingress-NGINX deployment in 2026, this matters.

The Kubernetes Ingress-NGINX project was retired in March 2026. The repository was archived on March 24, 2026, and is now read-only. The project documentation states that after retirement, there are no further releases, bug fixes, or security updates, although existing deployments are not automatically broken.

Therefore, if your investigation finds:

Ingress
    |
    v
ingress-nginx

you should distinguish between two questions:

  1. How do I fix the current 504?
  2. Should this controller remain part of the architecture?

Those are different questions.

For an existing deployment, you may need to troubleshoot the current controller. For a new architecture, Kubernetes recommends Gateway API rather than continuing to build around the frozen Ingress API.

If you are comparing Ingress controllers with broader gateway solutions, see our guide to open-source API gateways for microservices, which covers tools such as Traefik, Envoy, Kong, APISIX, and NGINX.


Step 1: Confirm the 504 Is Really Coming From the Ingress Layer

Start with an external request.

Use:

curl -vk https://your-domain.example/api/test

For timing information:

curl -vk -o /dev/null \
  -s \
  -w '\nHTTP: %{http_code}\nTotal: %{time_total}s\nConnect: %{time_connect}s\nStartTransfer: %{time_starttransfer}s\n' \
  https://your-domain.example/api/test

The important values are:

  • HTTP status
  • total time
  • connection time
  • time to first byte

If the response consistently appears at approximately the same time, that is an important clue.

For example:

HTTP: 504
Total: 60.12s

is much more interesting than simply knowing that the status is 504.

A repeated approximately-60-second failure should immediately make you investigate every component with a 60-second timeout, including the external load balancer.

Do not automatically conclude that NGINX caused it.


Step 2: Check the Ingress Object

Run:

kubectl describe ingress <ingress-name> -n <namespace>

Then:

kubectl get ingress <ingress-name> -n <namespace> -o yaml

Check:

  • host
  • path
  • path type
  • IngressClass
  • backend Service
  • backend port
  • annotations
  • events

A typical configuration might contain:

metadata:
  annotations:
    nginx.ingress.kubernetes.io/proxy-connect-timeout: "120"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "120"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "120"

Ingress-NGINX documents these as supported timeout annotations. Their values are strings, and the timeout numbers are expressed in seconds.

But before changing them, determine whether they are actually relevant to your failure.


Step 3: Verify the Kubernetes Service

A surprisingly large number of “Ingress problems” are actually Service configuration problems.

Run:

kubectl get svc <service-name> -n <namespace>

Then:

kubectl describe svc <service-name> -n <namespace>

Check:

Selector
Port
TargetPort
Endpoints

A common mistake is:

Ingress → Service port 80
Service → targetPort 8080
Pod → application actually listening on 9090

The Ingress can be perfectly configured while the backend connection fails.


Step 4: Check EndpointSlices

Do not stop at the Service.

Kubernetes automatically maintains EndpointSlice objects describing the endpoints backing a Service.

Run:

kubectl get endpoints <service-name> -n <namespace>

And:

kubectl get endpointslice -n <namespace> \
  -l kubernetes.io/service-name=<service-name>

You want to see actual backend addresses.

If the Service has no usable endpoints, investigate:

kubectl get pods -n <namespace> --show-labels

Compare the Pod labels with the Service selector:

kubectl describe svc <service-name> -n <namespace>

For example:

Service selector:
app=payments

but the Pods might have:

app=payment

That one-character mismatch can prevent the Service from selecting the intended Pods.


Step 5: Check Pod Readiness

A running Pod is not necessarily a ready backend.

Run:

kubectl get pods -n <namespace>

Then:

kubectl describe pod <pod-name> -n <namespace>

Look at:

  • Ready condition
  • readiness probe
  • liveness probe
  • restart count
  • container state
  • recent events

Also check:

kubectl get pods -n <namespace> -o wide

If Pods are repeatedly restarting, becoming unready, or being killed under load, increasing the Ingress timeout is usually treating the symptom rather than the cause.


Step 6: Test the Service Without the Ingress

This is one of the most valuable tests.

If the application works internally but fails through the Ingress, the problem is likely somewhere between:

Ingress → Load Balancer → Controller → Service

or in the controller configuration.

If the application also fails internally, the problem is probably further downstream.

Start a temporary curl Pod:

kubectl run curl-test \
  --rm -it \
  --restart=Never \
  --image=curlimages/curl \
  -- sh

Then, from inside the Pod:

curl -v http://<service-name>.<namespace>.svc.cluster.local:<port>/health

For example:

curl -v http://api.default.svc.cluster.local:8080/health

Test the slow endpoint too:

time curl -v http://api.default.svc.cluster.local:8080/api/slow

This creates an important comparison.

TestResultLikely direction
External URL504Continue investigation
Internal ServiceWorksInvestigate Ingress/LB/network
Internal Service504/timeoutInvestigate application/backend
Pod IP directlyWorksInvestigate Service/Ingress path
Pod IP directlyFailsInvestigate application/network

Step 7: Test the Pod Directly

If you can identify the Pod IP:

kubectl get pod <pod-name> -n <namespace> -o wide

Then from the temporary curl Pod:

curl -v http://<pod-ip>:<port>/health

This is extremely useful because it removes the Service from the test.

You now have three different paths:

External
    ↓
Load Balancer
    ↓
Ingress
    ↓
Service
    ↓
Pod

and:

Curl Pod
    ↓
Service
    ↓
Pod

and:

Curl Pod
    ↓
Pod IP

If the direct Pod request works while the Service request fails, investigate Service/endpoints/networking.

If both internal requests work while the external request fails, move your investigation toward the Ingress controller and load balancer.


Step 8: Read the Ingress Controller Logs

Now inspect the controller.

First identify it:

kubectl get pods -A | grep -i ingress

For an Ingress-NGINX installation:

kubectl get pods \
  -n ingress-nginx

Then:

kubectl logs \
  -n ingress-nginx \
  <controller-pod>

For recent logs:

kubectl logs \
  -n ingress-nginx \
  <controller-pod> \
  --since=10m

Search for:

upstream timed out
connect() failed
no live upstreams
connection refused
504
499

A message such as:

upstream timed out while connecting to upstream

points in a different direction from:

upstream timed out while reading response

The first suggests difficulty establishing the upstream connection.

The second suggests the connection exists, but the backend is not producing data quickly enough.

That distinction matters.


Real Public Case: 504 Even After Timeout Changes

This is where real incident evidence becomes more useful than generic advice.

In Ingress-NGINX issue #10093, the reported environment used:

  • Ingress-NGINX v1.4.0
  • NGINX 1.19.10
  • Helm chart 4.3.0
  • proxy-send-timeout: 300
  • proxy-read-timeout: 300
  • proxy-connect-timeout: 300
  • An Ingress annotation with proxy-read-timeout: "3600"

The reporter used a backend designed to delay its response and requested a 65-second response.

The external request still produced:

HTTP/1.1 504 GATEWAY_TIMEOUT

The controller log showed approximately 59 seconds of upstream timing and a 499 on the NGINX side.

That case is valuable because it disproves the simplistic rule:

“A Kubernetes Ingress 504 always means proxy-read-timeout is too low.”

It does not.

The entire request path must be investigated.


Another Real Case: 3600 Seconds Still Produced a 504

Ingress-NGINX issue #10735 provides another useful example.

The reported environment included:

  • Kubernetes v1.28.1
  • AWS
  • Ingress-NGINX v1.7.0
  • proxy-connect-timeout: "3600"
  • proxy-read-timeout: "3600"
  • proxy-send-timeout: "3600"

The request still returned:

504 Gateway Time-out

after approximately two minutes.

Again, this is why changing the three NGINX timeout annotations should not be the first and only troubleshooting step.

The important question is:

Which component stopped waiting?


Step 9: Understand the Three Main Ingress-NGINX Timeouts

If you are actually using Ingress-NGINX, these settings are important.

proxy-connect-timeout

This controls the timeout for establishing a connection with the upstream server.

Example:

nginx.ingress.kubernetes.io/proxy-connect-timeout: "30"

The documented default is 5 seconds in the Ingress-NGINX ConfigMap, and the documentation notes that this value generally cannot exceed 75 seconds.

A connection problem looks different from a slow application response.


proxy-read-timeout

This controls the timeout for reading from the proxied server.

Example:

nginx.ingress.kubernetes.io/proxy-read-timeout: "120"

The important detail is that this is not simply a maximum total response duration.

The timeout is measured between successive read operations.

Therefore, this distinction matters:

Backend takes 90 seconds before sending first data

versus:

Backend sends data regularly every 10 seconds
and continues for 10 minutes

They are not equivalent from the proxy’s perspective.


proxy-send-timeout

This controls the timeout for transmitting a request to the upstream server.

Example:

nginx.ingress.kubernetes.io/proxy-send-timeout: "120"

It is also measured between successive write operations rather than being a simple total-request timer.


Default Ingress-NGINX Timeout Values

The documented ConfigMap defaults include:

SettingDefault
proxy-connect-timeout5 seconds
proxy-read-timeout60 seconds
proxy-send-timeout60 seconds

This explains why 60-second failures are frequently investigated as possible NGINX timeout problems.

But it does not prove NGINX is responsible.

A cloud load balancer may also have a 60-second idle timeout.

AWS documents a 60-second default for both Classic Load Balancer and Application Load Balancer idle timeout behavior.


Step 10: Verify Helm Values

If Helm manages your controller, do not assume the values in your local values.yaml are the values currently running.

If you are managing Kubernetes as part of a wider cloud environment, our guide to cloud infrastructure deployment best practices covers Helm, Kubernetes, monitoring, and infrastructure-management workflows.

First:

helm list -A

Then:

helm get values <release-name> \
  -n <namespace>

For all configured values:

helm get values <release-name> \
  -n <namespace> \
  --all

Also inspect the deployed manifest:

helm get manifest <release-name> \
  -n <namespace>

This helps answer:

What did Helm actually deploy?

Rather than:

What do I think Helm deployed?


Step 11: Compare Helm Configuration With Live Kubernetes Configuration

Suppose your Helm values contain:

controller:
  config:
    proxy-read-timeout: "300"
    proxy-send-timeout: "300"
    proxy-connect-timeout: "60"

Check the live ConfigMap:

kubectl get configmap \
  -n ingress-nginx

Then:

kubectl get configmap <configmap-name> \
  -n ingress-nginx \
  -o yaml

You are looking for:

data:
  proxy-read-timeout: "300"
  proxy-send-timeout: "300"
  proxy-connect-timeout: "60"

This creates a useful three-way comparison:

Helm values
     ↓
Rendered Kubernetes objects
     ↓
Live controller configuration

If those disagree, you have found an important configuration-management problem.


Step 12: Check the Ingress Annotation Itself

Run:

kubectl get ingress <ingress-name> \
  -n <namespace> \
  -o jsonpath='{.metadata.annotations}'

Or:

kubectl describe ingress <ingress-name> \
  -n <namespace>

Look for:

nginx.ingress.kubernetes.io/proxy-read-timeout
nginx.ingress.kubernetes.io/proxy-send-timeout
nginx.ingress.kubernetes.io/proxy-connect-timeout

Ingress-NGINX documents these annotations as per-Ingress overrides of global timeout settings.


Step 13: Verify the Rendered NGINX Configuration

This is a step many troubleshooting guides skip.

If you have access to the Ingress-NGINX controller Pod, inspect its generated NGINX configuration.

For example:

kubectl exec \
  -n ingress-nginx \
  <controller-pod> \
  -- nginx -T

You can then search for the affected hostname or timeout directives.

For example:

kubectl exec \
  -n ingress-nginx \
  <controller-pod> \
  -- nginx -T | grep -E \
  'proxy_(connect|read|send)_timeout'

The exact output and available commands depend on the controller image/version, so treat this as a diagnostic technique rather than a universal command contract.

The important verification chain is:

Ingress annotation
       ↓
Controller configuration
       ↓
Generated NGINX configuration
       ↓
Actual request behavior

If the first three agree but the request still fails at a predictable time, investigate the next layer.


Step 14: Investigate the Cloud Load Balancer

This is essential when your Ingress controller sits behind a cloud load balancer.

A common architecture is:

Internet
   |
   v
AWS ALB / ELB
   |
   v
Ingress Controller
   |
   v
Service
   |
   v
Pod

The load balancer can produce the 504 before NGINX’s own timeout is reached.

AWS specifically documents HTTP 504 causes including:

  • failure to establish a target connection within the connection timeout
  • target not responding before the idle timeout
  • network ACL problems
  • malformed/incomplete response behavior
  • Lambda timeout conditions in applicable architectures.

Therefore inspect:

  • load-balancer access logs
  • target response time
  • target health
  • idle timeout
  • connection timeout
  • security groups
  • network ACLs
  • target registration
  • deregistration behavior

Real AWS Example: The Load Balancer Was the Important Layer

Ingress-NGINX issue #6178 documents a real AWS EKS case involving a Classic Load Balancer in front of NGINX.

The reported configuration had:

AWS ELB idle timeout: 55 seconds
NGINX keepalive timeout: 75 seconds

The reported ELB 504 occurred with a timing difference of approximately 55 seconds, matching the ELB idle timeout.

That is an excellent diagnostic clue.

If your failure occurs at:

~55 seconds

and your load balancer has:

55-second idle timeout

you have much stronger evidence than simply seeing “504.”


Step 15: Compare Timeout Thresholds

Create a table for your actual environment.

For example:

ComponentConfigured timeoutObserved failure
Client??
Cloud LB60s?
Ingress connect5s?
Ingress read60s?
Ingress send60s?
Application server??
External API??

Then ask:

Which timeout is closest to the observed failure?

For example:

Failure consistently occurs at 59–61 seconds

and:

ALB idle timeout = 60 seconds
Ingress read timeout = 300 seconds

The load balancer deserves immediate attention.

Conversely:

Failure occurs at ~300 seconds
ALB idle timeout = 600 seconds
Ingress read timeout = 300 seconds

makes the Ingress layer much more suspicious.


Step 16: Check Application Logs at the Same Timestamp

This is another powerful test.

Suppose the external request begins at:

12:00:00

and the user receives:

504 at 12:01:00

Search application logs around that exact request.

If the application says:

request started 12:00:00
request completed 12:01:12

while the client received:

504 at 12:01:00

then the application was still working when another layer gave up.

That is a very different problem from:

request never reached application

or:

application returned 500

Correlate timestamps across:

Load balancer logs
        ↓
Ingress logs
        ↓
Application logs
        ↓
External dependency logs

If the Kubernetes application depends on third-party services or internal APIs, review our guide to troubleshooting API integration errors for a broader approach to diagnosing upstream failures, authentication problems, and gateway-related errors.


Step 17: Check NetworkPolicy and Security Rules

If the controller cannot reliably reach the backend, inspect network controls.

Check:

kubectl get networkpolicy -A

Then:

kubectl describe networkpolicy <policy-name> \
  -n <namespace>

Also inspect cloud networking where relevant:

  • AWS security groups
  • AWS Network ACLs
  • Azure NSGs
  • GCP firewall rules
  • routing tables
  • CNI configuration
  • service mesh policies

A connection timeout is not necessarily an application timeout.


Step 18: Check Resource Pressure

A slow application may simply be overloaded.

Run:

kubectl top pods -n <namespace>

and:

kubectl top nodes

If metrics-server is available, inspect:

  • CPU
  • memory
  • throttling
  • restart frequency

Then inspect the Pod:

kubectl describe pod <pod-name> -n <namespace>

Look for:

OOMKilled
Back-off restarting failed container
Unhealthy
Readiness probe failed
Liveness probe failed

A backend that is CPU-throttled or repeatedly restarting can look like an Ingress timeout problem.


Step 19: Check the Application’s Own Timeout

Do not forget the application server.

Examples include:

  • Gunicorn
  • uWSGI
  • Node.js
  • Java application servers
  • PHP-FPM
  • ASP.NET
  • Go HTTP servers
  • database connection pools
  • reverse proxies inside the Pod

The architecture might actually be:

Client
 ↓
Load Balancer
 ↓
Ingress
 ↓
Service
 ↓
Pod
 ↓
Application Server
 ↓
Database

If the database query takes 120 seconds but the application server kills the request after 60 seconds, changing NGINX to 300 seconds does nothing useful.


The Timeout Chain You Should Build

For slow requests, document the chain explicitly:

Client timeout
      >
Load balancer timeout
      >
Ingress timeout
      >
Application server timeout
      >
Database/API timeout

The exact ordering depends on the architecture.

The key is to avoid having an upstream component terminate a request before the downstream component has a chance to finish.

For example, if your application legitimately requires 180 seconds:

Load balancer: 60s
Ingress: 300s
Application: 180s

the 60-second load-balancer timeout can still terminate the request first.


When Should You Increase proxy-read-timeout?

Increase it only when your evidence shows that the Ingress-NGINX read timeout is actually the limiting layer.

For example:

metadata:
  annotations:
    nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "300"

If the application genuinely requires long-lived HTTP requests, this may be appropriate.

But do not automatically use:

3600

for everything.

A one-hour timeout can hide:

  • slow database queries
  • deadlocked applications
  • overloaded Pods
  • failed external APIs
  • resource exhaustion
  • broken streaming behavior

Timeouts should reflect an intentional application requirement.

If PostgreSQL is responsible for the delay, use a structured PostgreSQL database performance investigation to check slow queries, missing indexes, locks, connection pressure, and execution plans.


Helm Example for Ingress-NGINX Timeout Configuration

If you are maintaining an existing Ingress-NGINX deployment and Helm manages the controller, the configuration can be represented through Helm values.

ingress-NGINX Deployment

For example:

controller:
  config:
    proxy-connect-timeout: "30"
    proxy-read-timeout: "300"
    proxy-send-timeout: "300"

Then upgrade the existing release using the values file:

helm upgrade <release-name> ingress-nginx/ingress-nginx \
  -n ingress-nginx \
  -f values.yaml

Afterward, verify:

helm get values <release-name> \
  -n ingress-nginx \
  --all

Then:

kubectl get configmap \
  -n ingress-nginx

And finally inspect the generated controller configuration.

Do not consider the change complete simply because helm upgrade returned successfully.


Helm Troubleshooting Checklist

Use this sequence:

helm list -A
helm get values <release> -n <namespace> --all
helm get manifest <release> -n <namespace>
kubectl get ingress -A
kubectl get ingressclass
kubectl describe ingress <ingress> -n <namespace>
kubectl get svc <service> -n <namespace>
kubectl get endpoints <service> -n <namespace>
kubectl get endpointslice \
  -n <namespace> \
  -l kubernetes.io/service-name=<service>
kubectl get pods -n <namespace> -o wide
kubectl logs <controller-pod> \
  -n <controller-namespace> \
  --since=15m

Then test the backend from inside the cluster:

kubectl run curl-test \
  --rm -it \
  --restart=Never \
  --image=curlimages/curl \
  -- sh

This sequence is much safer than repeatedly changing timeout annotations.


A Practical 504 Decision Tree

Use this when troubleshooting an incident.

                 504 Gateway Timeout
                         |
                         v
             Does failure occur consistently
                at a specific duration?
                    /             \
                  YES              NO
                   |                |
                   v                v
          Compare all timeout     Check controller
          layers with timing      and backend logs
                   |
                   v
        Does request reach Ingress?
              /            \
            YES             NO
             |               |
             v               v
       Check controller    Check LB/DNS/
       logs + backend      listener/routing
             |
             v
       Does Service have
       healthy endpoints?
          /        \
        YES         NO
         |           |
         v           v
  Test Service      Fix selector/
  from inside       readiness/
  cluster           endpoints
         |
         v
    Does direct Pod
    request work?
       /       \
     YES        NO
      |          |
      v          v
 Check Ingress   Check app,
 and LB          network,
 layers           resources
      |
      v
Compare:
LB timeout
Ingress timeout
App timeout
Dependency timeout
      |
      v
Change only the
limiting layer
      |
      v
Retest and correlate
logs by timestamp

504 vs 502 vs 503

These errors are related but should not be treated as identical.

StatusGeneral meaningWhat to investigate first
502Bad gateway / invalid upstream interactionConnection, protocol, upstream response
503Service unavailableBackend availability, endpoints, readiness
504Gateway timeoutWhich layer stopped waiting?

The exact behavior depends on the component generating the response.

For example, an AWS Application Load Balancer can generate 504 for several target-side timeout/network conditions.

So the status code alone is insufficient.


Common Kubernetes Ingress 504 Causes

1. Slow application response

The backend simply takes too long.

Test:

time curl -v http://service.namespace.svc.cluster.local:8080/slow

Then compare that time with the external request.


2. Load balancer idle timeout

The cloud load balancer may stop waiting before NGINX or the application does.

Check the LB configuration and access logs.

AWS ALB, for example, has a documented default idle timeout of 60 seconds.


3. No healthy endpoints

Check:

kubectl get endpoints <service> -n <namespace>

and:

kubectl get endpointslice \
  -n <namespace> \
  -l kubernetes.io/service-name=<service>

4. Incorrect Service port

Check:

kubectl describe svc <service> -n <namespace>

Compare:

port
targetPort

with what the application actually listens on.


5. Readiness failure

A Pod can be running while Kubernetes does not consider it ready.

Check:

kubectl describe pod <pod> -n <namespace>

6. NetworkPolicy

Check whether the Ingress controller is permitted to reach the backend.


7. Application resource exhaustion

Check:

kubectl top pods -n <namespace>

and:

kubectl describe pod <pod> -n <namespace>

8. External dependency timeout

The application may be waiting for:

Database
API
Object storage
Message queue
Authentication service

The Ingress only sees a request that has not completed.


9. Controller configuration mismatch

Your Helm values may say one thing while the running controller uses another.

Compare:

helm values
      ↓
rendered manifest
      ↓
ConfigMap
      ↓
Ingress annotations
      ↓
generated proxy configuration

A Real-Data Troubleshooting Example

Suppose you run:

curl -vk https://example.com/api/report

and receive:

HTTP/2 504

after:

60.2 seconds

You check the Ingress:

proxy-read-timeout = 300

You then test internally:

time curl http://report.default.svc.cluster.local:8080/api/report

and get:

Total time: 82 seconds

Now the evidence is much stronger.

You have:

External request: 60 seconds → 504
Ingress read timeout: 300 seconds
Backend response: 82 seconds

That strongly suggests the request is being terminated before the backend finishes, with the load-balancer layer becoming a prime suspect.

If the load balancer has:

Idle timeout = 60 seconds

the numbers line up.

That is a real diagnostic conclusion.

It is much stronger than:

“Increase proxy-read-timeout to 600.”


What If the Backend Takes 90 Seconds?

Suppose:

Backend response = 90s
Ingress read timeout = 60s
Load balancer timeout = 120s

Then the Ingress layer is a logical suspect.

If your application intentionally requires 90 seconds, you could raise the relevant Ingress timeout.

But then verify:

Application timeout > expected response time
Ingress timeout > application response time
Load balancer timeout > Ingress/application requirement

Do not change all values blindly.


What If the Backend Takes 10 Minutes?

That is where architecture matters.

If an HTTP request takes:

10 minutes

you should ask whether synchronous HTTP is appropriate.

Instead of:

POST /generate-report
       |
       | waits 10 minutes
       v
200 OK

consider:

POST /generate-report
       |
       v
202 Accepted
       |
       v
Background job
       |
       v
Client polls job status

or:

Request
   |
   v
Queue
   |
   v
Worker
   |
   v
Result storage

Increasing every timeout to one hour may make the immediate 504 disappear while creating a much larger reliability problem.


Long-Running Requests and Streaming Are Different

Do not confuse:

Backend produces no data for 5 minutes

with:

Backend streams data every 10 seconds for 5 minutes

Because proxy-read-timeout is measured between successive reads, the network behavior matters.

For streaming or WebSocket-style workloads, inspect the controller’s protocol-specific configuration as well.

Ingress-NGINX documentation notes that WebSocket support requires appropriate proxy-read-timeout and proxy-send-timeout values, with a documented example of using values above one hour for long-lived WebSocket connections.


gRPC Considerations

If your Ingress-NGINX configuration uses gRPC, timeout settings are also relevant because the documented gRPC timeout values inherit from the corresponding proxy timeout settings.

Check:

proxy-connect-timeout
proxy-read-timeout
proxy-send-timeout

Rather than assuming ordinary HTTP behavior is identical.


Why Logs Matter More Than the 504 Page

A browser may only show:

504 Gateway Time-out

That tells you almost nothing about the root cause.

Controller logs can tell you whether the proxy:

  • failed to connect
  • connected but waited for data
  • retried an upstream
  • received an upstream error
  • closed a client connection
  • returned a generated 504

Load-balancer logs can tell you whether the request reached the target at all.

Application logs can tell you whether the request was processed.

The strongest diagnosis comes from correlating all three.


The Most Useful Evidence to Collect

For a production incident, capture:

1. Exact request URL/path
2. Exact timestamp
3. HTTP status
4. Total response time
5. Ingress controller version
6. Helm chart version
7. Kubernetes version
8. Ingress YAML
9. IngressClass
10. Service YAML
11. EndpointSlice state
12. Pod state
13. Controller logs
14. Application logs
15. Load-balancer logs
16. Load-balancer timeout configuration
17. NetworkPolicy
18. Application timeout configuration

This turns:

“Ingress gives 504.”

into:

“The request reaches the application, the application needs approximately 82 seconds, the Ingress read timeout is 300 seconds, but the external ALB terminates the connection at approximately 60 seconds.”

That is a real diagnosis.


What Not to Do

Do not immediately set every timeout to 3600

A huge timeout can hide the real problem.

Do not assume every 504 comes from NGINX

The load balancer may be returning it.

Do not assume a running Pod is healthy

Check readiness and actual endpoint membership.

Do not test only through the public URL

Test the Service and Pod directly.

Do not trust only your values.yaml

Verify the deployed Helm release and live Kubernetes objects.

Do not ignore timestamps.

A 59-second, 120-second, or 300-second failure can reveal which layer is responsible.

Do not ignore the controller type.

NGINX annotations do not automatically apply to every Kubernetes Ingress implementation.


Helm vs Ingress Annotations: Which Should You Use?

Configuration methodScopeBest use
Helm valuesController-wideConsistent global defaults
ConfigMapController-wideGlobal controller behavior
Ingress annotationSpecific IngressDifferent timeout for one application
Application configurationApplicationBusiness/application timeout
Load balancer configurationEdgeExternal connection behavior

A good architecture usually uses global defaults plus targeted overrides.

For example:

Global default:
60 seconds

Special long-running API:
300 seconds

Batch processing:
Asynchronous architecture

That is better than making every route wait five minutes.


A Better Helm Verification Pattern

For an existing Helm-managed controller, use:

helm list -A

Find the release.

Then:

helm get values <release> \
  -n <namespace> \
  --all

Then:

helm get manifest <release> \
  -n <namespace>

Now inspect the live resources:

kubectl get deployment \
  -n <namespace>
kubectl get configmap \
  -n <namespace>
kubectl get ingress \
  -A

Finally, test the request again.

The objective is to prove:

Configured
    ↓
Deployed
    ↓
Loaded
    ↓
Used
    ↓
Observed

Kubernetes Ingress vs Gateway API in 2026

There is another important architectural issue for anyone writing new infrastructure today.

Kubernetes now describes Gateway API as the successor to Ingress. The Gateway API provides resources such as:

  • GatewayClass
  • Gateway
  • HTTPRoute
  • GRPCRoute

It supports more expressive routing models.

The Kubernetes project states that the Ingress API is frozen and recommends Gateway instead.

That does not mean every existing Ingress must immediately be deleted.

It means new infrastructure planning should consider Gateway API and an actively maintained implementation.

For an existing Ingress-NGINX environment, the practical strategy is:

Immediate problem
      ↓
Diagnose and fix 504
      ↓
Document current configuration
      ↓
Assess controller support status
      ↓
Plan Gateway API migration

Complete Kubernetes Ingress 504 Checklist

Use this checklist during an actual incident.

Request

  • Exact URL recorded
  • Exact timestamp recorded
  • HTTP status confirmed
  • Response time measured
  • Reproduction confirmed

Ingress

  • IngressClass identified
  • Host checked
  • Path checked
  • Backend Service checked
  • Annotations checked

Service

  • Service exists
  • Port correct
  • TargetPort correct
  • Selector correct
  • EndpointSlices contain healthy endpoints

Pods

  • Pods are Running
  • Pods are Ready
  • No repeated restarts
  • Readiness probes pass
  • CPU/memory checked

Network

  • NetworkPolicy checked
  • Security groups checked
  • Network ACLs checked
  • Routing checked

Ingress controller

  • Controller type confirmed
  • Controller version recorded
  • Controller logs checked
  • Timeout configuration checked
  • Rendered configuration verified

Helm

  • Release identified
  • Chart version recorded
  • helm get values checked
  • helm get manifest checked
  • Live ConfigMap checked

Load balancer

  • LB type identified
  • Idle timeout checked
  • Connection timeout checked
  • Access logs checked
  • Target health checked

Application

  • Application logs checked
  • Application timeout checked
  • Database/API dependency checked
  • Resource utilization checked

Verification

  • Service tested internally
  • Pod tested directly
  • External request retested
  • Logs correlated by timestamp
  • Root cause identified before changing additional settings

The Shortest Reliable Debugging Method

If you are under pressure during a production incident, use this sequence.

1. Measure the failure

curl -vk -o /dev/null \
  -s \
  -w '\nHTTP: %{http_code}\nTotal: %{time_total}s\n' \
  https://example.com/api/test

2. Identify the controller

kubectl get ingressclass

3. Inspect the Ingress

kubectl describe ingress <name> -n <namespace>

4. Check the Service

kubectl describe svc <service> -n <namespace>

5. Check endpoints

kubectl get endpoints <service> -n <namespace>

6. Test internally

curl http://<service>.<namespace>.svc.cluster.local:<port>/health

7. Read controller logs

kubectl logs <controller-pod> \
  -n <controller-namespace> \
  --since=10m

8. Check Helm

helm get values <release> \
  -n <namespace> \
  --all

9. Check the load balancer

Compare its timeout with the observed failure time.

10. Change only the layer proven to be responsible

Then retest.


Final Diagnosis Rule

When you see:

Kubernetes Ingress 504 Gateway Timeout

do not immediately ask:

“What annotation fixes 504?”

Ask:

“Which component returned the 504, and what was it waiting for?”

Then trace the request:

Client
  ↓
Load Balancer
  ↓
Ingress Controller
  ↓
Ingress Rule
  ↓
Service
  ↓
EndpointSlice
  ↓
Pod
  ↓
Application
  ↓
Database / External API

Measure the failure time.

Check the logs.

Test the backend directly.

Compare every timeout.

Verify Helm values against the live configuration.

Only then change the timeout that the evidence identifies as the bottleneck.

That approach is much more reliable than repeatedly increasing. proxy-read-timeout.

And in 2026, there is one additional architectural question: if the affected environment still relies on Ingress-NGINX, fixing today’s 504 should also trigger a review of the migration path because the Ingress-NGINX project has been retired and archived.


Key Takeaway

A Kubernetes Ingress 504 Gateway Timeout is a timing problem somewhere in a request chain, not automatically an Ingress configuration problem.

If you can establish:

Where did the request stop?
When did it stop?
What was it waiting for?
Which timeout expired?
Did the request reach the application?

you can usually turn a vague 504 into a specific, testable root cause.

Related Posts