AWS API Gateway with Nginx and WAF

Bits Lovers
Written by Bits Lovers on
AWS API Gateway with Nginx and WAF

I ran into an interesting architecture problem recently. We had multiple Web Applications running on EC2 instances behind AWS API Gateway, and we needed to add a WAF without breaking the bank. Here’s how we set it up.

The Setup

We wanted to use Nginx as a reverse proxy sitting between API Gateway and our EC2 instances. The main goals were:

  1. Use API Gateway’s built-in throttling so we don’t have to build it ourselves
  2. Handle API key authentication through Cognito instead of rolling our own auth
  3. Centralize access through one entry point to keep costs manageable when we have many APIs spread across different servers

You might look at the diagram below and ask: why not call the app server directly through a load balancer and skip API Gateway entirely?

Diagram from API Gateway with Nginx behind WAF

Diagram from API Gateway with Nginx behind WAF

Fair question. Here’s why we kept API Gateway in the mix.

Why Keep API Gateway?

Throttling. API Gateway has ready-made usage plans. You set quotas and throttling limits per API key, and it just works. Building this from scratch takes time.

Authentication. API Gateway handles API credentials and Cognito integration. We didn’t want to develop our own auth solution. Need API authentication? API Gateway with Cognito JWT authorizers replaces most custom Lambda authorizers.

For a direct call, you’d use something like:

https://app.bitslovers.com/api/find/username

With API Gateway, the equivalent endpoint looks like:

https://6ax1c8mg2a.execute-api.us-east-1.amazonaws.com/prod/find/username

Nginx Configuration

If you need nginx to enforce its own rate limits and IP whitelists in addition to API Gateway’s throttling — for example, to block abusive clients before requests reach API Gateway — nginx rate limiting and whitelisting covers the limit_req_zone and geo configuration in detail.

Alternative 1: Direct Proxy

Here’s the proxy configuration for forwarding traffic from API Gateway to your backend:

server {
  listen 80;
  server_name app.bitslovers.com;
  return 301 https://app.bitslovers.com;
}

server {
  listen        443 ssl;
  server_name app.bitslovers.com;

  location /api/ {
    proxy_max_temp_file_size 2048m;
    proxy_read_timeout 3600s;
    proxy_send_timeout 3600s;
    proxy_connect_timeout 3600s;
    client_max_body_size 100G;
    resolver 127.0.0.1 53 valid=5s;

    set $backend_servers 6ax1c8mg2a.execute-api.us-east-1.amazonaws.com;

    if ($request_uri ~ "/api/") {
      rewrite  ^  $request_uri;
      rewrite  ^/api(/.*)  /prod$1  break;
      proxy_pass https://$backend_servers$uri;
    }
  }
}

Alternative 2: Private API with VPC Endpoint

When you create a Private API Gateway, AWS gives you a few ways to invoke it. This example uses a VPC Endpoint.

You’ll need to create a VPC Endpoint for the API Gateway service (execute-api) and use the unique DNS it provides. One gotcha: you must add a “Host” header with your API Endpoint DNS value.

For example, with curl:

curl -v https://vpce-.execute-api.us-east-1.vpce.amazonaws.com -H "Host: 6ax1c8mg2a.execute-api.us-east-1.amazonaws.com"

The Nginx config for Private API Gateway looks like this:

server {
  listen 80;
  server_name app.bitslovers.com;
  return 301 https://app.bitslovers.com;
}

server {
  listen        443 ssl;
  server_name app.bitslovers.com;

  location /api/ {
    proxy_max_temp_file_size 2048m;
    proxy_read_timeout 3600s;
    proxy_send_timeout 3600s;
    proxy_connect_timeout 3600s;
    client_max_body_size 100G;
    resolver 127.0.0.1 53 valid=5s;
    proxy_set_header Host 6ax1c8mg2a.execute-api.us-east-1.amazonaws.com;

    set $backend_servers vpce-<id>.execute-api.us-east-1.vpce.amazonaws.com;

    if ($request_uri ~ "/api/") {
      rewrite  ^  $request_uri;
      rewrite  ^/api(/.*)  /prod$1  break;
      proxy_pass https://$backend_servers$uri;
    }
  }
}

WAF Options

If you need a WAF, you have two main choices: Cloudflare and Imperva. They work quite differently. For AWS-native WAF configuration, see our deep dive into WAF rules covering rate-based, geo, and custom rules.

Cloudflare requires moving your DNS nameserver to their servers. You then pick which DNS records go through the Cloudflare proxy to use WAF protection.

Imperva doesn’t require moving your DNS. You point your application DNS to an Imperva DNS instead. Each application gets its own Imperva DNS address, so www.example.com would point to something like xxx73hd.impervia.net, which then forwards to your actual server.

With Imperva, make sure your server’s firewall or Security Group only allows traffic from Imperva’s IP ranges.

If you want a more current take on this architecture, I wrote an updated version covering zero trust API security with API Gateway, WAF, and Nginx in 2026 — including mutual TLS, JWT validation at the edge, and how the threat model has shifted since this post was written.

Bits Lovers

Bits Lovers

Professional writer and blogger. Focus on Cloud Computing.

Comments

comments powered by Disqus