API Gateway with NGINX: Layering, Ownership, and Migration Patterns
API7.ai
September 14, 2026
NGINX can remain useful when a team adopts an API gateway, but it should not remain as an unexplained extra hop. Keep it when it has a distinct job—such as existing edge TLS, static delivery, or a staged migration boundary. Replace or bypass it when both layers perform the same routing, authentication, retries, and logging.
The key decision is not whether NGINX is “a reverse proxy” and the new product is “an API gateway.” Both can proxy, terminate TLS, route, and apply policy. Decide which layer owns the public contract, then make client identity, upstream TLS, timeout, retry, and rollback behavior explicit.
The NGINX behavior in this guide is reviewed against NGINX Open Source 1.30.5. Pin the deployed package or source build and verify its enabled modules before relying on these examples; a floating documentation page is not a version contract.
Key Takeaways
- Choose one of three intentional models: NGINX as the edge before the gateway, the gateway as the only edge, or a temporary migration chain.
- Treat forwarding headers as untrusted until the immediate peer is trusted and the chain is parsed with a documented rule.
- NGINX
real_ip_recursivedefaults tooff; changing it changes which address becomes$remote_addr. - Upstream certificate verification is not implied by using an
https://upstream;proxy_ssl_verifydefaults tooff. - Bound retries by method, attempt count, time, and the point at which a downstream response has begun.
Decide Whether Two Layers Earn Their Cost
| Model | Use it when | Main risk |
|---|---|---|
| NGINX → API gateway → service | NGINX retains a distinct edge, static, TLS, or migration role | Duplicate policy, latency, and incompatible header trust |
| API gateway → service | The gateway can own the complete public edge | Migration may miss legacy NGINX behavior |
| NGINX → old/new gateway split | A controlled cohort migration needs fast rollback | Temporary rules become permanent or diverge |
Do not evaluate only request latency. A second proxy also adds certificate rotation, configuration delivery, log correlation, capacity, patching, and incident ownership. Conversely, removing NGINX without inventorying host normalization, redirects, body limits, buffering, and error pages can change the external API contract.
Pattern 1: Keep NGINX as a Narrow Edge
flowchart LR
C[Client] --> N[NGINX edge]
N -->|Verified upstream TLS| G[API gateway]
G --> A[API service]
In this pattern, NGINX owns the public socket and a deliberately small set of edge behaviors. The API gateway owns consumer authentication, route policy, quotas, transformation, and API observability. The service retains object and workflow authorization.
Restrict the API gateway so clients cannot reach it around NGINX. Use network policy, firewall rules, private addressing, or authenticated origin connections. If the gateway is publicly reachable, an attacker can bypass NGINX limits and header normalization.
This NGINX excerpt demonstrates the handoff. It is not a complete production configuration: certificate paths, resolver behavior, health checks, and the exact gateway hostname must be adapted and validated for the installed NGINX version.
upstream api_gateway { server api-gateway.internal.example:9443; keepalive 64; } server { listen 443 ssl; server_name api.example.com; ssl_certificate /etc/nginx/tls/public.crt; ssl_certificate_key /etc/nginx/tls/public.key; location / { proxy_http_version 1.1; proxy_set_header Connection ""; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Forwarded-Proto https; proxy_ssl_server_name on; proxy_ssl_name api-gateway.internal.example; proxy_ssl_verify on; proxy_ssl_trusted_certificate /etc/nginx/tls/internal-ca.pem; proxy_connect_timeout 2s; proxy_read_timeout 30s; proxy_next_upstream error timeout; proxy_next_upstream_tries 2; proxy_pass https://api_gateway; } }
Replacing X-Forwarded-For with the resolved $remote_addr creates a simple one-hop contract. If NGINX itself is behind another proxy, configure and test the real-IP module before using this pattern; otherwise $remote_addr identifies the immediately connected proxy rather than the end client.
Establish the Client-IP Trust Boundary
The official ngx_http_realip_module reference separates the trusted peer from the address supplied in a header. set_real_ip_from lists peers allowed to provide replacement addresses, and real_ip_header selects the source field. When compiling NGINX from source, this module is not built by default; enable it with --with-http_realip_module. Distribution packages may differ, so verify the active build with nginx -V. The behavior below was also cross-checked against the NGINX 1.30.5 Real IP module source.
real_ip_recursive is a behavior-changing Boolean with a default of off:
off(default): when the connection comes from a trusted address, NGINX replaces the client address with the last address in the selected header.on: NGINX walks the chain and selects the last non-trusted address.
Neither path proves an end-user identity. The result is only as trustworthy as the configured peers, their header-rewrite behavior, and the network path. Test requests from trusted and untrusted connections with forged headers. Keep $realip_remote_addr in diagnostic logs so operators can see the original connected peer.
Avoid treating $proxy_add_x_forwarded_for as a security control. The proxy module defines it as the incoming X-Forwarded-For value with $remote_addr appended. If an untrusted caller supplied the original header, blindly forwarding the whole chain preserves untrusted data. The proxy defaults and paths discussed below were cross-checked against the NGINX 1.30.5 proxy module source.
Verify Upstream TLS, Not Just Encryption
An https:// value in proxy_pass encrypts the connection, but the documented default for proxy_ssl_verify is off:
off(default): NGINX does not verify the upstream server certificate.on: NGINX verifies the certificate using the configured trusted CA; configure the expected name and SNI when the upstream requires them.
The excerpt sets proxy_ssl_verify on, supplies a trust bundle, enables SNI, and pins the expected gateway name. Test an expired certificate, an untrusted issuer, and a name mismatch. Decide whether NGINX also presents a client certificate so the gateway can authenticate the edge proxy. Encryption without peer verification does not prevent an unintended endpoint from impersonating the gateway.
proxy_ssl_server_name is another behavior-changing Boolean and defaults to off. With off, NGINX does not send TLS SNI to the proxied HTTPS server. With on, it sends the name selected by proxy_ssl_name. Enabling SNI helps a virtual-hosted upstream select a certificate, but it does not enable certificate verification; keep proxy_ssl_verify on as a separate control.
Bound Timeouts and Retries
NGINX documents proxy_next_upstream error timeout as the default retry condition. It also documents an important delivery boundary: NGINX can switch to another upstream only before it has sent part of the response to the client. A failure after response delivery begins cannot be repaired by selecting another server.
The configuration above limits attempts to two, but method safety remains a separate decision. Do not enable retry of non-idempotent operations merely to reduce visible errors. For replayable writes, define an idempotency key, server-side deduplication, an overall deadline, and a maximum total attempt budget across NGINX, the API gateway, service mesh, and client.
Align timeouts as a decreasing budget. The client deadline must be longer than the complete gateway path, while each internal timeout leaves time to return a useful error. A proxy_read_timeout is an inactivity boundary between reads, not automatically a total request deadline; streaming routes need separate treatment.
Pattern 2: Make the API Gateway the Only Edge
Remove NGINX when the gateway can intentionally reproduce every required public behavior and the extra layer has no independent owner. First inventory:
- DNS, certificates, TLS versions, ciphers, and client-certificate behavior;
- redirects, hostname normalization, path rewrites, and error responses;
- maximum headers and bodies, buffering, streaming, and WebSocket upgrades;
- client-IP resolution and every consumer of that value;
- upstream pools, passive or active health behavior, and connection reuse;
- timeouts, retries, cache behavior, compression, and static assets;
- access logs, metrics, correlation fields, and security alerts.
Translate behavior into tests before translating configuration. A syntactically similar route may differ in URI normalization, header merging, regular-expression semantics, or failure timing.
Pattern 3: Use NGINX as a Migration Switch
NGINX can split a small cohort to the new gateway while most traffic continues to the old path. Prefer a stable, non-sensitive selector such as a dedicated test hostname or an operator-controlled cohort header removed from public requests. Avoid percentage routing for writes unless both destinations share compatible state and idempotency behavior.
The rollback must be a reviewed configuration change, not an emergency edit. Define which metrics trigger rollback, how long connections drain, what happens to WebSockets and streams, and how configuration state is reconciled after traffic returns.
Avoid Duplicate Policy
For each policy, write one authoritative owner and any defense-in-depth exception:
| Policy | Recommended ownership question |
|---|---|
| JWT validation | Which layer validates issuer, audience, signature, and time claims? |
| Rate limit | Which identity and shared counter define the quota? |
| Retry | Which layer knows method safety and the remaining deadline? |
| WAF or schema check | Which representation and body limit were inspected? |
| Redirect or rewrite | Which layer owns the external URI contract? |
| Access log | Which event is authoritative for acceptance, denial, and upstream delivery? |
Duplicate defensive validation can be justified, but its keys, failure behavior, and telemetry must agree. A request denied by either layer should carry a reason that operators can attribute without exposing sensitive policy details to clients.
Validation and Cutover Checklist
- Direct access to the gateway and origin is blocked from untrusted networks.
- Trusted and untrusted forwarded-header tests produce the expected
$remote_addr. - The gateway rejects a connection with an invalid NGINX client identity when mTLS is used.
- NGINX rejects an invalid upstream certificate with
proxy_ssl_verify on. - Host, scheme, path, query, and body arrive unchanged unless a documented transformation owns the change.
- Missing, invalid, and valid credentials fail at the intended layer.
- Retry counts, upstream attempts, and total deadlines match the route contract.
- Large requests, streaming, gRPC, and WebSocket routes follow tested buffering and timeout paths.
- Logs correlate one request without treating a caller-supplied ID as trusted identity.
- Rollback restores traffic and configuration, not only DNS.
Summary
NGINX and an API gateway can coexist when each layer has a distinct, tested responsibility. Make the public entry point singular, rebuild client identity from trusted peers, enable upstream certificate verification, and bound retries across the whole path. If NGINX adds no independent value after migration, removing the duplicate hop is usually simpler than maintaining two partially overlapping gateways.
FAQ
Is NGINX itself an API gateway?
NGINX supplies many gateway building blocks. Whether it meets a team's API-management requirements depends on the exact edition, modules, configuration workflow, policy lifecycle, and operating model.
Should NGINX append or replace X-Forwarded-For?
At a trust boundary, first determine the client address from a documented trusted-proxy chain. Forward a normalized value or explicitly documented chain; do not preserve caller-supplied values by default.
Does proxy_pass https://... verify the upstream certificate?
Not by itself. NGINX documents proxy_ssl_verify as off by default. Enable verification and configure trusted CA and name/SNI behavior.
Next Steps
Compare NGINX and Envoy gateway foundations, design safe gateway timeouts and retries, and define trusted client-IP policy.