← Back to DevBytes

Troubleshooting API Gateway: Common Issues and Solutions

Introduction to API Gateway Troubleshooting

An API Gateway is the single entry point that sits between clients and your backend services. It handles request routing, composition, authentication, rate limiting, observability, and protocol translation. Because it sits at the critical intersection of every request, when something goes wrong, the blast radius can be enormous. A misconfigured route, an expired certificate, or an aggressive throttling policy can take down an entire platform in seconds.

This tutorial walks through the most common API Gateway issues developers and platform engineers encounter, explains why they happen, and shows you how to diagnose and fix them with practical, copy-paste-ready solutions. The examples focus on AWS API Gateway and Kong, but the principles apply to NGINX-based gateways, Apigee, Tyk, and Envoy as well.

Why API Gateway Troubleshooting Matters

The API Gateway is often the first component blamed during an outage, even when the root cause lies upstream. Without a structured troubleshooting methodology, teams waste precious time chasing symptoms instead of causes. Understanding the failure modes of your gateway lets you:

Common Issue 1: 502 Bad Gateway Errors

What Causes a 502

A 502 Bad Gateway means the gateway received an invalid response from the upstream server. In AWS API Gateway, this typically happens when the integration target (a Lambda function, an HTTP endpoint, or a VPC link) returns a malformed response or times out. In Kong, it usually surfaces as a connection reset or an upstream timeout.

Diagnosing the Problem

Start by isolating where the failure occurs. Enable execution logging on your API Gateway and inspect the response payload from the integration. For AWS API Gateway with Lambda proxy integration, check CloudWatch Logs for the stage:

# AWS CLI: fetch recent log events for a Lambda-backed API
aws logs filter-log-events \
  --log-group-name "/aws/lambda/my-service-handler" \
  --start-time $(date -d '10 minutes ago' +%s)000 \
  --filter-pattern "ERROR"

A frequent root cause is the Lambda function returning a response that does not match the expected proxy integration format. The response must include statusCode, headers, and body as top-level keys. A missing or non-numeric statusCode triggers a 502 even when the function executed successfully.

Fixing the Response Shape

Here is a correct Lambda proxy response in Node.js:

exports.handler = async (event) => {
  try {
    const result = await processRequest(event);
    return {
      statusCode: 200,
      headers: {
        "Content-Type": "application/json",
        "Access-Control-Allow-Origin": "*"
      },
      body: JSON.stringify(result)
    };
  } catch (err) {
    // Common mistake: throwing here causes a 502
    return {
      statusCode: 500,
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ error: err.message })
    };
  }
};

For HTTP integrations, verify that the backend is reachable from the gateway. A 502 often indicates a security group or network ACL blocking the connection. Test connectivity directly:

# From a host inside the same VPC as the gateway
curl -v -o /dev/null -w "%{http_code}\n" \
  https://internal-backend.example.com/health

Common Issue 2: 429 Too Many Requests

Understanding Throttling

API Gateways enforce rate limiting to protect backend services. AWS API Gateway applies account-level and stage-level throttling. Kong uses plugins like rate-limiting or rate-limiting-advanced. When a client exceeds the configured limit, the gateway returns a 429 with a Retry-After header.

The problem arises when legitimate traffic gets throttled because limits are too conservative or because a single shared client identity is used across many users. This is common in server-to-server integrations where a backend service calls the gateway using a single API key on behalf of thousands of end users.

Diagnosing Throttling

In AWS, check the 4XXError and Count metrics in CloudWatch, filtered by your API stage. A spike in 429s that correlates with traffic volume confirms throttling. Use the following AWS CLI command to inspect throttling configuration:

aws apigateway get-stage \
  --rest-api-id abc123def4 \
  --stage-name prod

Look for the throttlingBurstLimit and throttlingRateLimit fields. If they are absent, the stage inherits account-level defaults, which may be too low for production workloads.

Adjusting Rate Limits

To raise the limits for a specific stage:

aws apigateway update-stage \
  --rest-api-id abc123def4 \
  --stage-name prod \
  --patch-operations \
    op=replace,path=/throttling/burstLimit,value=2000 \
    op=replace,path=/throttling/rateLimit,value=1000

In Kong, configure per-consumer limits so that one noisy client cannot starve others:

curl -i -X POST http://localhost:8001/services/my-service/plugins \
  --data "name=rate-limiting" \
  --data "config.minute=100" \
  --data "config.policy=redis" \
  --data "config.redis_host=redis.internal" \
  --data "config.limit_by=consumer"

Setting limit_by=consumer ensures each API key gets its own bucket, preventing one client from consuming the entire quota.

Common Issue 3: CORS Errors

Why CORS Fails at the Gateway

Browsers enforce the Same-Origin Policy, and Cross-Origin Resource Sharing (CORS) headers must be present in the response. When the API Gateway does not return proper Access-Control-Allow-Origin headers, or when it fails to handle the preflight OPTIONS request, the browser blocks the response and the user sees a cryptic CORS error in the console.

The tricky part is that CORS errors often mask the real problem. A 500 error from the backend will also produce a CORS error in the browser if the error response lacks CORS headers, because the browser never sees the actual status code.

Enabling CORS in AWS API Gateway

For REST APIs, enable CORS on each resource and ensure the OPTIONS method returns the correct headers. You can do this via the console or with a CloudFormation snippet:

OptionsMethod:
  Type: AWS::ApiGateway::Method
  Properties:
    RestApiId: !Ref MyApi
    ResourceId: !Ref MyResource
    HttpMethod: OPTIONS
    AuthorizationType: NONE
    Integration:
      Type: MOCK
      IntegrationResponses:
        - StatusCode: 200
          ResponseParameters:
            method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key'"
            method.response.header.Access-Control-Allow-Methods: "'GET,POST,PUT,DELETE,OPTIONS'"
            method.response.header.Access-Control-Allow-Origin: "'https://app.example.com'"
    MethodResponses:
      - StatusCode: 200
        ResponseParameters:
          method.response.header.Access-Control-Allow-Headers: true
          method.response.header.Access-Control-Allow-Methods: true
          method.response.header.Access-Control-Allow-Origin: true

For Lambda proxy integrations, the function itself must return CORS headers on every response, including error responses. A common mistake is returning CORS headers only on success paths. Always include them in the catch block as shown in the earlier Lambda example.

Testing CORS Headers

Simulate a preflight request with curl to verify the gateway responds correctly:

curl -v -X OPTIONS https://api.example.com/users \
  -H "Origin: https://app.example.com" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: Content-Type,Authorization"

The response must include Access-Control-Allow-Origin matching the request origin (or * for public APIs), plus the allowed methods and headers.

Common Issue 4: Authentication and Authorization Failures

401 Unauthorized vs 403 Forbidden

A 401 means the gateway could not authenticate the request — the token is missing, expired, or malformed. A 403 means the request was authenticated but the caller lacks permission to access the resource. Confusing these two leads to incorrect debugging paths.

Debugging JWT Authorizers

When using a JWT authorizer, the most common issues are an incorrect issuer URL, a mismatched audience claim, or an expired token. Decode the JWT locally to inspect its claims before sending it to the gateway:

# Decode a JWT payload (middle segment) without verification
echo "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ" \
  | base64 -d 2>/dev/null | jq .

Compare the iss and aud claims against your authorizer configuration. In AWS API Gateway, the authorizer's identitySource must match the header where the client sends the token. A mismatch between method.request.header.Authorization and the client sending Bearer without the prefix will cause silent failures.

Custom Authorizer Timeouts

Lambda authorizers have a response timeout (default 3000ms in AWS). If your authorizer calls an external identity provider synchronously, latency spikes can cause the gateway to return a 401 even with a valid token. Cache authorizer responses to reduce cold-path latency:

aws apigateway update-authorizer \
  --rest-api-id abc123def4 \
  --authorizer-id a1b2c3 \
  --patch-operations \
    op=replace,path=/authorizerResultTtlInSeconds,value=300

A TTL of 300 seconds caches authorization results, dramatically reducing calls to the authorizer Lambda and improving p99 latency.

Common Issue 5: Latency and Timeout Issues

Identifying Where Latency Occurs

High latency at the gateway level can originate from the gateway itself, the network path, or the backend. AWS API Gateway exposes the Latency metric, which measures the total time from request receipt to response return, and IntegrationLatency, which measures only the backend integration time. Comparing the two pinpoints the bottleneck.

aws cloudwatch get-metric-statistics \
  --namespace AWS/ApiGateway \
  --metric-name IntegrationLatency \
  --dimensions Name=ApiName,Value=MyApi Name=Stage,Value=prod \
  --start-time 2024-01-01T00:00:00Z \
  --end-time 2024-01-01T01:00:00Z \
  --period 300 \
  --statistics Average,Maximum

If IntegrationLatency is high, the backend is the bottleneck. If Latency is high but IntegrationLatency is low, the overhead is in the gateway itself — possibly due to large payload transformation templates or excessive mapping logic.

Handling Timeout Configuration

AWS API Gateway has a hard 29-second timeout for integration requests that cannot be increased. If your backend takes longer, you must redesign the integration to be asynchronous. A common pattern is to return a 202 Accepted with a correlation ID, then have the client poll or receive a webhook callback:

exports.handler = async (event) => {
  const jobId = await enqueueJob(event.body);
  return {
    statusCode: 202,
    headers: { "Location": `/jobs/${jobId}/status` },
    body: JSON.stringify({ jobId, status: "queued" })
  };
};

In Kong, the upstream timeout is configurable per service. Tune it to match your backend's expected response time, and set a slightly higher value than your backend's own timeout so the backend fails first and returns a meaningful error:

curl -i -X PATCH http://localhost:8001/services/my-service \
  --data "connect_timeout=5000" \
  --data "write_timeout=10000" \
  --data "read_timeout=10000"

Common Issue 6: Payload Size and Content-Type Issues

413 Request Entity Too Large

API Gateways impose payload size limits. AWS API Gateway REST APIs limit request bodies to 10MB, and HTTP APIs have similar constraints. Kong defaults to a configurable buffer size. When a client sends a payload exceeding the limit, the gateway rejects it before it reaches the backend.

The fix is usually architectural: use pre-signed S3 URLs for large file uploads instead of routing the binary through the gateway. The client uploads directly to S3, then notifies the backend via a small API call:

exports.handler = async (event) => {
  const presignedUrl = s3.getSignedUrl('putObject', {
    Bucket: 'uploads-bucket',
    Key: `uploads/${uuid()}`,
    Expires: 300,
    ContentType: 'application/octet-stream'
  });
  return {
    statusCode: 200,
    body: JSON.stringify({ uploadUrl: presignedUrl })
  };
};

Unsupported Media Type (415)

A 415 occurs when the Content-Type header does not match what the gateway or backend expects. This is common when clients send application/json but the integration expects application/x-www-form-urlencoded, or when binary media types are not registered in the gateway configuration.

Register binary media types in AWS API Gateway so the gateway passes them through without attempting to convert them to base64:

aws apigateway update-rest-api \
  --rest-api-id abc123def4 \
  --patch-operations \
    op=add,path=/binaryMediaTypes/image~1png \
    op=add,path=/binaryMediaTypes/application~1octet-stream

Best Practices for API Gateway Reliability

Implement Comprehensive Observability

Structured logging, distributed tracing, and metrics are non-negotiable. Enable access logs with a JSON format that includes the request ID, client IP, path, method, status code, latency, and integration latency. This gives you a searchable record of every request:

{
  "requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "ip": "203.0.113.42",
  "requestTime": "2024-01-15T10:30:00Z",
  "httpMethod": "GET",
  "resourcePath": "/users/{id}",
  "status": 200,
  "latency": 145,
  "integrationLatency": 120,
  "responseLength": 2048
}

Use Canary Deployments

Never push gateway configuration changes directly to production. Use canary deployments to route a small percentage of traffic to the new configuration, monitor error rates, and promote only when metrics are healthy. AWS API Gateway supports canary releases natively at the stage level.

Validate Input at the Gateway

Use request validation to reject malformed requests before they reach the backend. Define JSON schemas for request bodies and enable validation on methods. This reduces backend load and provides immediate, consistent error messages to clients:

aws apigateway update-method \
  --rest-api-id abc123def4 \
  --resource-id xyz789 \
  --http-method POST \
  --patch-operations \
    op=replace,path=/requestValidatorId,value=a1b2c3

Design for Idempotency

Because gateways may retry requests during transient failures, design backend handlers to be idempotent. Accept an Idempotency-Key header and use it to deduplicate operations. This prevents double-charging, duplicate resource creation, and other side effects when the gateway retries.

Automate Configuration with Infrastructure as Code

Manual gateway configuration via the console is a leading cause of production issues. Define your API Gateway resources using CloudFormation, Terraform, or the Serverless Framework. Version-control enables code review, rollback, and drift detection:

resource "aws_api_gateway_rest_api" "main" {
  name        = "production-api"
  description = "Main production API gateway"

  endpoint_configuration {
    types = ["REGIONAL"]
  }
}

resource "aws_api_gateway_method" "proxy" {
  rest_api_id   = aws_api_gateway_rest_api.main.id
  resource_id   = aws_api_gateway_resource.proxy.id
  http_method   = "ANY"
  authorization = "COGNITO_USER_POOLS"
  authorizer_id = aws_api_gateway_authorizer.main.id
}

Conclusion

Troubleshooting an API Gateway effectively requires understanding the request lifecycle from client to backend and knowing where each layer can fail. By mastering the common issues covered here — 502 integration errors, 429 throttling, CORS misconfigurations, authentication failures, latency bottlenecks, and payload constraints — you can dramatically reduce the time it takes to diagnose and resolve production incidents. Pair this knowledge with strong observability, infrastructure as code, canary deployments, and idempotent backend design, and your API Gateway becomes a reliable, transparent layer rather than a black box. The gateway is not just a router; it is the contract between your clients and your services, and investing in its operational excellence pays dividends across your entire platform.

— Ad —

Google AdSense will appear here after approval

← Back to all articles