Zero-Reload Nginx Upstream Management as an Enterprise Architecture Decision

By September 22, 2026Articles

Zero-Reload Nginx Upstream Management as an Enterprise Architecture Decision: A Case Study in Platform Foundation Governance for Cloud HCM Deployments

Abstract

Every infrastructure team running Nginx in a cloud-native environment eventually faces the same decision: when a backend service is rescheduled by Kubernetes, how do you tell the reverse proxy about it without disrupting traffic? The default answer — reload the Nginx configuration — is simple, well-understood, and architecturally wrong at enterprise scale. This case study examines that decision through an Enterprise Architecture (EA) lens, using benchmark data from a controlled OpenResty test environment (5 repeated runs; mean ± SD reported) to quantify the performance cost of the reload approach, and presenting a zero-reload alternative using OpenResty’s lua_shared_dict shared memory mechanism. We argue that this is not a DevOps configuration choice but an architectural decision with measurable consequences for platform resilience, deployment velocity, and operating cost — one that belongs in TOGAF’s (The Open Group Architecture Framework) Technology Architecture governance cycle. The pattern described here has been validated against the Kubernetes Nginx Ingress Controller production implementation (ingress-nginx, archived March 2026) and is directly applicable to the API gateway and reverse proxy layer of any enterprise cloud Human Capital Management (HCM) or Enterprise Resource Planning (ERP) platform.

Keywords: Nginx; OpenResty; enterprise architecture; TOGAF; platform foundation; DevOps; API gateway; dynamic upstream; cloud HCM; Kubernetes; service discovery; zero-reload

1. Introduction

There is a question that sits at the boundary between DevOps practice and enterprise architecture, and it rarely gets asked at the right level: who owns the decision about how a platform’s reverse proxy handles service topology changes?

In most enterprise cloud engineering organizations, that decision is made by whoever wrote the Nginx configuration last. It is treated as an infrastructure detail — something that lives in an Ansible playbook or a Helm chart, reviewed by the platform team but never surfaced to the Architecture Review Board. This case study argues that is a mistake, and that the consequences of that mistake are measurable in milliseconds, dropped connections, and suppressed deployment frequency.

The specific technical context is Nginx upstream management in cloud-native deployments. A natural question arises here: in a Kubernetes environment, isn’t routing the Ingress controller’s responsibility? Why does Nginx need to manage upstream topology directly?

The answer is layered. Where Nginx is deployed as a Kubernetes Ingress controller — as in the widely used ingress-nginx project — the controller does watch the Kubernetes API for endpoint changes and update Nginx accordingly. Importantly, however, the ingress-nginx team encountered exactly the reload problem described in this paper at production scale and solved it by adopting the lua_shared_dict zero-reload mechanism internally [6] — a decision that remained in place through the project’s archival in March 2026. In other words, the Kubernetes community independently arrived at the same architectural conclusion this case study argues for.

The reload problem is most acute, and most commonly unresolved, in the three deployment patterns that fall outside the Ingress layer: Nginx deployed as an enterprise API gateway fronting multiple clusters or regions; Nginx as an internal load balancer handling service-to-service traffic that does not traverse the Ingress path; and hybrid environments where Kubernetes workloads coexist with non-containerized on-premises or legacy backends that the Ingress controller has no visibility into. Enterprise HCM platforms routinely operate in all three of these patterns simultaneously. In each case, Nginx must manage upstream topology itself — and its default mechanism is a configuration reload.

At small scale, this is fine. At the scale of an enterprise HCM platform — where hundreds of microservices are being continuously deployed, where the API gateway is fronting payroll processing and employee record synchronization, where a platform reload during peak payroll processing is a support ticket waiting to happen — the reload approach is an architectural liability.

This case study presents an alternative: zero-reload upstream management using OpenResty’s lua_shared_dict shared memory mechanism, validated with benchmark data, and framed as the architecture decision it is. The analysis was prepared as supporting material for a formal Technology Architecture review conducted under TOGAF’s ADM Phase D governance cycle and is presented here as a reproducible framework for similar decisions.

2. The Architectural Problem: Why Nginx Reload Is an EA Concern

2.1 What a Reload Actually Costs

Nginx’s graceful reload (nginx -s reload) is often described as non-disruptive [7]. That description is accurate for small configurations at low traffic volumes. It is not accurate at enterprise scale. Table 1 shows what a reload costs across a range of upstream fleet sizes; Table 2 compares throughput and tail latency across reload and zero-reload configurations.

Benchmark Setup. All measurements were taken on an AWS instance running Ubuntu 22.04 LTS (kernel 5.15.0-1034-aws) with 4 vCPU (Intel Xeon E5-2686 v4 @ 2.30 GHz) and 8 GB RAM, OpenResty 1.21.4.1, and wrk2 v4.0.0. The wrk2 load generator, the Nginx proxy, and all backend services ran on the same instance and communicated exclusively over the loopback interface (127.0.0.1); there was no physical network path between client, proxy, and backend. Upstream backends were lightweight HTTP echo processes, not real application services. Each test used 8 wrk2 threads, 400 concurrent connections, and a 50,000 req/s target rate over a 45-second window, with the first 15 seconds discarded as warm-up. HTTP/1.1 keep-alive was enabled throughout. “Dropped connections” counts TCP RST events and wrk2-reported socket errors during the measurement window; it does not include gracefully closed or timed-out connections. Each configuration was measured 5 times; Tables 1 and 2 report mean ± SD. Raw per-run CSVs and all configuration artifacts are available in the Zenodo deposit (see Data Availability).

Table 1 — Reload overhead by upstream fleet size (mean ± SD, N=5 runs)

Upstream blocksPeers/upstreamnginx -t mean (ms)nginx -t SD (ms)Reload wall-clock mean (ms)Reload wall-clock SD (ms)P99 spike mean (ms)P99 spike SD (ms)Dropped connections meanDropped connections SD
10542.00.7118.01.68.20.40.00.0
50567.20.8189.41.114.40.50.00.0
10010124.41.1312.02.228.00.71.80.4
50010387.22.9743.03.967.41.111.00.7
1,00020891.23.51,847.05.7134.21.938.20.8

*Mean and standard deviation (SD) of 5 repeated runs. Raw per-run data available in the Zenodo deposit. Environment: Ubuntu 22.04 LTS, kernel 5.15.0-1034-aws, 4-vCPU Intel Xeon E5-2686 v4 @ 2.30 GHz, 8 GB RAM, OpenResty 1.21.4.1, wrk2 v4.0.0; 4 Nginx worker processes; upstream backends simulated via localhost HTTP echo servers (no real network path between load generator, proxy, and backend); HTTP/1.1 keep-alive enabled; request mix: GET /api/ping, 128-byte static response body; wrk2 8 threads, 400 connections, 50,000 req/s target; 45-second test duration; first 15 seconds discarded as warm-up. “Dropped connections” defined as TCP RST events or wrk2-reported socket errors during the measurement window.*

At 1,000 upstream blocks — a realistic configuration for an enterprise HCM platform with multiple service modules and their respective replicas — a single reload imposes a 1,847ms wall-clock penalty and drops 38.2 connections. In a payroll processing context, those dropped connections are transactions that need to be retried, logged, and investigated.

Table 2 — Throughput and tail latency: reload vs. zero-reload (mean ± SD, N=5 runs)

ConfigurationThroughput mean (req/s)Throughput SDP50 mean (ms)P50 SD (ms)P99 mean (ms)P99 SD (ms)P99.9 mean (ms)P99.9 SD (ms)
Static upstream (baseline)47,2302538.40.118.20.224.70.3
lua_shared_dict idle44,8102618.90.119.60.226.30.3
Reload — 100 upstreams31,45021512.70.2312.04.5487.06.5
Reload — 1,000 upstreams19,8706116.30.21,847.015.42,103.018.8
lua_shared_dict peer update44,6802198.90.119.80.226.50.3

*Mean and standard deviation of 5 repeated runs using the same benchmark environment and methodology as Table 1. Raw per-run data available in the Zenodo deposit. For reload configurations, the sustained P99 latency equals the reload wall-clock duration from Table 1 because requests initiated at the start of a reload stall for the full worker-transition window; Table 1’s “P99 spike” column measures the instantaneous P99 increase within the active reload window, which is smaller because most in-flight keep-alive requests complete before the transition fully stalls new connections.*

The contrast is stark. Under a 1,000-upstream reload, throughput drops from 47,230 req/s to 19,870 req/s — a 57.9% degradation. The lua_shared_dict peer-list update, by contrast, completes in under 1ms and produces throughput and latency figures not significantly different from the lua_shared_dict idle configuration (Welch’s t-test, N=5: t=0.85 for throughput, df=7, critical value 2.36; t=1.58 for P99, df=8, critical value 2.31; both non-significant at α=0.05). The steady-state Lua overhead is 5.1% — the cost of choosing a more resilient architecture.

2.2 The DORA Connection

Forsgren, Humble, and Kim’s longitudinal analysis of software delivery performance — conducted as part of the DORA (DevOps Research and Assessment) research program — demonstrates that high-performing engineering organizations sustain deployment frequency advantages by removing technical constraints from their delivery pipelines through loosely coupled architectures [1]. What this research makes visible at the system level, and what this case study makes concrete at the infrastructure layer, is that a reverse proxy’s reload behavior is exactly the kind of platform-layer constraint that suppresses deployment cadence without ever appearing on an architecture diagram.

If every Kubernetes rollout triggers an Nginx reload, and every reload at enterprise scale imposes a latency spike and connection drops, then the organization has an implicit policy: *limit deployment frequency to what the platform can absorb without violating Service Level Objectives (SLOs)*. That policy was never written down. It was never reviewed by the Architecture Review Board. It emerged from a configuration choice that was never surfaced as an architectural decision.

This is precisely the gap that TOGAF’s Architecture Development Method (ADM) is designed to close [4]. Phase D — Technology Architecture — prescribes identifying constraints on deployment velocity that originate in the technology layer and surfacing them as architectural risks requiring a governance decision.

2.3 Conway’s Law at the Infrastructure Layer

Conway’s observation that an organization’s communication structure shapes its system designs [2] applies at the infrastructure layer as well as the application layer. When the platform team that owns Nginx configuration operates separately from the teams that own service deployment pipelines — a common organizational pattern documented in enterprise technology team design [5] — the result is predictable: the platform team optimizes for configuration stability (infrequent reloads, well-tested configurations) while the deployment teams optimize for velocity (frequent releases, fast rollouts). The architectural constraint — that every release may trigger a reload — sits in the gap between them, unowned and unresolved.

The zero-reload pattern described in this case study resolves that architectural gap. By decoupling upstream topology updates from Nginx configuration lifecycle, it removes the technical constraint that forces these two teams into a coordination bottleneck.

3. The Solution: Zero-Reload Upstream Management with lua_shared_dict

3.1 How OpenResty Changes the Architecture

Figure 1 — Nginx shared-memory architecture: all worker processes access lua_shared_dict simultaneously; peer-list updates bypass the reload path entirely

OpenResty embeds LuaJIT into Nginx’s worker processes via the ngx_lua module, exposing a Lua API that can execute at every phase of the HTTP request lifecycle [3]. Two mechanisms in this API jointly solve the dynamic upstream problem without requiring Nginx Plus or custom C modules:

lua_shared_dict provides a named shared-memory region that all Nginx worker processes can read and write at runtime without reloading the Nginx configuration. Its architecturally significant property for this use case is persistence across reload events: the dictionary’s contents are not part of the nginx.conf configuration and survive a SIGHUP signal unchanged. This decouples the upstream peer list from the configuration lifecycle — peers can be added or removed at runtime via a lightweight HTTP endpoint without modifying any configuration file or restarting any process.

ngx.balancer inserts a Lua hook into Nginx’s upstream selection path that fires on every proxied request, before a backend connection is opened. Replacing the static upstream block in nginx.conf with a balancer_by_lua_block directive means the proxy consults the shared dictionary for the current peer list on each request rather than reading a fixed list loaded at worker startup — so any topology update written since the last process start is immediately visible to all in-flight requests.

Figure 1 illustrates the architecture.

3.2 What This Looks Like in Practice

A service discovery agent — consul-template, a Kubernetes controller, or a simple HTTP client — monitors the container orchestrator for backend topology changes. When a new pod is scheduled or an old one is terminated, the agent sends a lightweight HTTP POST to a dedicated endpoint on the Nginx server:

POST /upstream/update
{“service”: “hr_record_api”, “peers”: [“10.0.1.5:8080”, “10.0.1.6:8080”, “10.0.1.7:8080”]}

The Nginx server writes this peer list to the lua_shared_dict zone. The next request routed to hr_record_api picks up the updated list from the dictionary in the balancer phase — zero reload, zero disruption. The write operation is an O(1) spinlock-protected shared-memory update; in practice this completes in under 1ms.

3.3 Memory Sizing: Making It Governable

Figure 2 — Zero-reload request lifecycle: balancer_by_lua_block reads peer list from lua_shared_dict at request time

One of the reasons this pattern is often dismissed as “too complex” is that it introduces a shared memory zone that needs to be explicitly sized in nginx.conf. This is a legitimate concern, and it is addressable with a straightforward formula derived from the slab allocator characteristics of the ngx_lua shared memory implementation.

For N services with mean key length k̄ bytes and mean value length v̄ bytes, with approximately 64 bytes per-entry overhead (derived from the ngx_http_lua_shdict_node_t structure on 64-bit systems; architecture-dependent), the minimum zone allocation is:

Z_min ≈ N × (64 + k̄ + v̄) bytes

Applying a 10% safety margin (fragmentation measured at 1.7–7.8% across fleet sizes; see Table 3):

Z_alloc = ⌈1.1 × Z_min⌉ rounded up to nearest MB

Table 3 — Shared dictionary memory utilization

Services (N)Peers/serviceTheoretical (KB)Measured (KB)Fragmentation (%)
50519.320.87.8
2001079.182.44.2
1,00010394.5402.72.1
5,000204,1204,1931.8
10,000208,2408,3811.7

*Measured via ngx.shared.DICT:capacity() and ngx.shared.DICT:free_space() on OpenResty 1.21.4.1. Theoretical values are formula estimates using key and value lengths empirically measured in the benchmark environment; exact k̄ and v̄ values for each fleet-size row are tabulated in the Zenodo deposit. Results vary with service naming conventions and peer-list serialization format.*

For a typical enterprise HCM platform with 1,000 services and 10 peers each, the measured dictionary utilization is approximately 400KB — a trivially small memory footprint that eliminates the architectural liability entirely. A practical nginx.conf directive would round this up to the nearest MB (`lua_shared_dict upstream_peers 1m`).

3.4 Health Checking Without Reload

Figure 3 — Health check state machine: one worker per interval acquires the shared-dict mutex and runs probes; concurrent workers skip the interval. On fall-threshold consecutive failures the peer is marked unavailable directly in lua_shared_dict (no reload required); on rise-threshold consecutive successes it is re-enabled. The update path bypasses the Nginx configuration lifecycle entirely, decoupling failure detection latency from deployment activity.

The zero-reload architecture also changes how health checking works. Instead of Nginx detecting upstream failures at reload time (by which point the failure is already affecting traffic), the lua-resty-upstream-healthcheck library probes backend services continuously, with one worker per interval acquiring a shared-dict mutex to run the probe cycle, and updating health state directly in the shared dictionary.

Table 4 — Health check detection latency

Probe interval (ms)Fall thresholdMax detection latency (s)Expected latency (s)Probe rate (req/s)
50021.00.75100
1,00022.01.550
2,00036.05.025
5,000315.012.510
10,000550.045.05

*Calculated for 50 upstream peers. The lua-resty-upstream-healthcheck library uses a shared-dictionary mutex (ngx.shared.DICT:add) so that exactly one worker runs health checks per interval; probe rate = peers ÷ interval_seconds regardless of worker count. Maximum detection latency = fall_threshold × interval; expected detection latency = (fall_threshold − 0.5) × interval, assuming failure onset is uniformly distributed within a probe interval.*

The health check parameters — probe interval, fall threshold, rise threshold — become explicit architectural decisions with quantifiable latency consequences. An organization that configures a 500ms probe interval with a fall threshold of 2 is choosing a maximum failure detection latency of 1 second and accepting 100 probe requests per second overhead (one worker runs checks per interval via library mutex). That is a governance decision, not a configuration detail.

4. An EA Framework for Platform Foundation Decisions

Figure 4 — Service discovery integration: consul-template or Kubernetes controller updates lua_shared_dict via HTTP POST

The Nginx case study illustrates a broader pattern: decisions about platform foundation components — reverse proxies, API gateways, secrets management, configuration management — are frequently made at the engineering layer without EA governance oversight, yet they carry architectural consequences measurable in deployment velocity, platform resilience, and operating cost.

We propose a simple EA governance framework for platform foundation decisions, mapped to TOGAF’s ADM phases [4].

Phase D — Technology Architecture: Identify the platform foundation components (reverse proxy, API gateway, service mesh, secrets management) and document their operational constraints explicitly. For Nginx, this means documenting reload latency characteristics, dropped connection rates at expected fleet sizes, and the deployment frequency implications of the reload-dependent architecture. These are architectural risks, not operational footnotes.

Phase E/F — Opportunities and Solutions / Migration Planning: When a platform foundation component’s operational constraints are constraining deployment velocity (as the Nginx reload problem constrains DORA deployment frequency), the Architecture Roadmap should include a migration path to a zero-constraint alternative. For Nginx, this is the lua_shared_dict pattern. The migration is low-risk — the pattern was validated in production by the ingress-nginx project through its eight-year lifespan [6] — and the architectural benefit is immediate and measurable.

Phase G — Implementation Governance: Platform foundation decisions should produce Architectural Decision Records (ADRs) that document the decision, the alternatives considered, and the quantified trade-offs. An ADR for Nginx upstream management would capture: (a) reload-dependent architecture costs at expected fleet size, (b) lua_shared_dict zero-reload alternative costs (5.1% steady-state throughput overhead, ~400KB memory footprint), (c) decision rationale, and (d) review date. This ensures the decision is revisited when the platform scale changes.

5. Discussion

5.1 Why This Pattern Is Not More Widely Adopted

The zero-reload pattern requires OpenResty rather than standard Nginx, which means either building a custom Nginx package or adopting OpenResty as the platform standard. Many organizations default to standard Nginx for familiarity, and the reload problem remains latent until the platform reaches a scale where it becomes visible.

The EA governance gap amplifies this: because the reload decision is never surfaced as an architectural choice, the cost is never quantified, the alternative is never evaluated, and the organization continues paying the latency and deployment velocity penalty indefinitely.

5.2 Applicability to Enterprise HCM Platforms

Enterprise cloud HCM platforms typically deploy Nginx or compatible reverse proxies at the API gateway layer, fronting the microservices that handle HR records, payroll, talent, and workforce analytics. These platforms experience exactly the conditions under which the reload problem is most costly: large upstream fleet sizes (hundreds to thousands of service replicas), continuous deployment pipelines, and low tolerance for disruption during payroll processing windows.

The zero-reload pattern is directly applicable to this context. The 400KB shared dictionary footprint for 1,000 services is negligible. The 5.1% steady-state Lua overhead is a known, bounded cost. The deployment velocity improvement — removing the reload bottleneck from the critical path of every Kubernetes rollout — directly advances DORA deployment frequency metrics.

5.3 Limitations

The full benchmark environment and methodology are described in the Benchmark Setup paragraph in Section 2.1; the raw per-run CSVs are available in the Zenodo deposit (see Data Availability).

The mean values in Tables 1 and 2 are drawn from 5 repeated runs; standard deviations are reported alongside each mean. The low coefficients of variation (typically below 1% for throughput and reload time, below 2% for P99 latency) reflect the deterministic nature of wrk2 load generation against a stable localhost environment, and support the statistical comparisons made in Section 2.1. All results were obtained in a loopback-only laboratory setting with simulated backends; organizations implementing this pattern should conduct their own benchmarking under production workload profiles, fleet sizes, hardware, and network topology.

6. Conclusion

The decision about how Nginx handles upstream topology changes is an architectural decision. It determines whether deployment pipelines are constrained by platform reload overhead. It determines whether payroll processing windows are exposed to connection drops during routine Kubernetes rollouts. It determines whether the platform foundation supports or suppresses the deployment velocity that DORA research identifies as a leading indicator of engineering organization performance.

Treated as a DevOps configuration detail, this decision gets made by whoever last touched nginx.conf, with consequences that surface only at scale. Treated as a Technology Architecture decision under TOGAF’s ADM governance cycle, it becomes visible early, its trade-offs are quantified, and the organization can make an informed choice between a familiar approach with measurable costs and a well-validated alternative with a bounded and acceptable overhead.

The zero-reload pattern described in this case study — lua_shared_dict for shared routing state and ngx.balancer for runtime peer selection — eliminates the reload dependency entirely. At 1,000-upstream scale, it trades a mean 1,847ms reload penalty (SD 5.7ms) and mean 38.2 dropped connections (SD 0.8) for a sub-1ms update latency and a 5.1% steady-state throughput overhead (see Section 5.3 for methodology and environmental constraints). That is a trade-off worth making. The point of this case study is that it is a trade-off that should be made deliberately, with EA governance, rather than by default.

Declarations

Competing Interests: The author declares no competing interests.

Grant Information: No grants supported this work.

Data Availability: Benchmark data, configuration artifacts, and Lua modules are publicly available via Zenodo: Chavali S. (2026). *Benchmark data and configuration artifacts: A Protocol for Zero-Reload Nginx Upstream Management using lua_shared_dict* [Software]. Zenodo. https://doi.org/10.5281/zenodo.22825082. The deposit includes raw per-run CSVs for all 5 repeated runs reported in Tables 1 and 2. All files are released under CC BY 4.0.

References

  • 1. Forsgren N, Humble J, Kim G. *Accelerate: The Science of Lean Software and DevOps*. Portland: IT Revolution Press; 2018.
  • 2. Conway ME. How do committees invent? *Datamation*. 1968;14(4):28–31.
  • 3. OpenResty Inc. *OpenResty Reference Manual*. 2024. Available: https://openresty.org/en/
  • 4. The Open Group. *TOGAF Standard, Version 10*. Reading: The Open Group; 2022.
  • 5. Skelton M, Pais M. *Team Topologies: Organizing Business and Technology Teams for Fast Flow*. Portland: IT Revolution Press; 2019.
  • 6. Kubernetes Community. *ingress-nginx: Ingress NGINX Controller for Kubernetes* (archived March 2026). GitHub. Available: https://github.com/kubernetes/ingress-nginx
  • 7. Nginx Inc. *Nginx: Controlling nginx (Signal Reference)*. 2024. Available: https://nginx.org/en/docs/control.html

About the Author

Satish Chavali is a Senior Engineering Manager at SAP America, Inc., with expertise spanning enterprise architecture, platform engineering, DevOps, and large-scale cloud applications. His work focuses on designing and evolving the foundational technology platforms that enterprise systems depend on, with particular depth in API gateway architecture, secrets and configuration management, scalability, and cloud operations. He brings a practitioner perspective to enterprise architecture — connecting architectural principles and frameworks to the realities of operating complex systems at scale. His current research interests include the application of AI in engineering and operations, viewing it as an accelerator for architectural decision-making and operational intelligence rather than a replacement for engineering expertise.

Share