yum upgrades for production use, this is the repository for you.
Active subscription is required.
Technical Briefing: slow_start Comes to Open-Source NGINX
The Problem
NGINX Plus has shipped a server slow_start parameter since 2015. It ramps traffic to a freshly-live upstream from zero to full weight over a configurable window. Open-source NGINX never got the behavior: it declares the slow_start field in its upstream peer structs, but there are zero references to it in any .c file — the data model was stubbed, the scheduler logic was never wired up.
That gap matters because every rolling restart, auto-scale event, or health-check recovery reintroduces a cold peer at full weight from request one. A cold peer means a cold JVM/JIT, empty PHP opcache, empty connection pools, cold page cache, and initial TCP congestion windows. Hitting it at full weight causes connection pool exhaustion, 502s, doubled tail latency, and retry storms across the pool.
The Implementation
NGINX-MOD — a drop-in nginx build from GetPageSpeed — implements the feature end-to-end in the open-source round-robin scheduler.
Parsing. The parser accepts slow_start=<time> on the server directive inside upstream {}, parsed via ngx_parse_time().
Peer initialization. server->slow_start is propagated onto the peer, and peer->start_time = ngx_current_msec is stamped across the four paths in ngx_http_upstream_init_round_robin() plus two paths in ngx_http_upstream_zone_module.c.
Scheduling. The scheduler scales each peer’s effective weight by a 0–100 factor derived from elapsed time divided by the window, clearing the anchor once ramp-up completes.
ABI safety. Reusing the already-declared start_time field keeps the change ABI-safe — no struct layout changes, no broken third-party dynamic modules.
Installation
Ships with nginx-mod package version 1.30.0-44 and later.
- RHEL/CentOS/AlmaLinux/Rocky: install the GetPageSpeed extras release RPM, enable
getpagespeed-extras-nginx-mod, thendnf swap nginx nginx-mod. - Debian/Ubuntu: install
nginx-modfrom the GetPageSpeed APT repo. Module loading is automatic — noload_moduledirective needed.
Included in GetPageSpeed subscriptions starting at $10/month.
Configuration Semantics
slow_start is a parameter of the server directive, not a standalone directive. It must appear inside upstream {} and inherits NGX_HTTP_UPS_CONF context. Putting it on a listen line or as a top-level directive fails.
Each peer has its own ramp window. The timer starts when the peer is created — nginx start/reload for static servers, POST time for the dynamic upstream API.
It is compatible with weight, max_fails, fail_timeout, max_conns, backup, and down.
Suggested Windows
| Backend | Window |
|---|---|
| Stateless static file servers / reverse proxies | 0s |
| PHP-FPM with OPcache | 15–30s |
| Node.js / Go with warm pools | 10–30s |
| JVM with JIT | 60–120s |
| .NET with CLR JIT | 45–90s |
| Large Python apps | 30–60s |
If unknown, start at 30s and instrument latency during a canary restart. The right value is slightly longer than p99 convergence time.
Measured Verification
In a fresh Rocky Linux 10 VM with nginx-mod-1.30.0, two weight-1 peers with peer B at slow_start=30s, the measured B share tracked the theoretical linear ramp within 0.5% at every sample point:
- 3.5% at 1s
- 25.0% at 10s
- 49.5% at 29s
- 50.0% steady state at 31s and beyond
A control run without slow_start produced an immediate 50/50 split. A three-peer test confirmed slow_start=1m is parsed and respected (peer C reaching full share at t=60s). The dynamic upstream API path (POST with "slow_start":"30s") produced the same monotonic ramp, confirming start_time is stamped at peer creation, not only at startup.
Vanilla nginx rejection: a hand-compiled vanilla nginx 1.30.0 emits nginx: [emerg] invalid parameter "slow_start=30s" during nginx -t, confirming the feature is observable only in NGINX-MOD.
Operational Caveats
- Ramp-up resets after every
nginx -s reload. This is expected — each reload rebuilds the peer list and re-stampsstart_time. Use the dynamic upstream API with state persistence if warmed peers must stay warm across reloads. slow_startdoes nothing with a single peer, since round-robin sends 100% to the only available peer regardless of factor.- It applies consistently across all workers because all are forked from the same master and share the same initial stamp.
- Per-request overhead is a timestamp subtraction, comparison, and multiply — nanoseconds — and short-circuits after a single zero-check once ramp-up completes.
Where It Doesn’t Help
Static file servers (nothing to warm), reverse proxies in front of already-warm pools, backends that pre-warm during startup, and health-check-first architectures where p99 gating already handles warm-up.
Broader Pattern
slow_start joins HTTP/3 reload (reuseport-safe shutdown), active health checks (six types: HTTP, TCP, SSL, MySQL, AJP, FastCGI), and the dynamic upstream API as features present in NGINX Plus but absent from open-source NGINX — where the data model was stubbed but the behavior was never implemented. A 10-instance shop pays Plus $36,750/year versus under $500/year for the GetPageSpeed subscription.
Related Tooling
GetPageSpeed Amplify runs scheduled gixy scans across every host and ties findings to live NGINX runtime metrics. It is drop-in compatible with the nginx-amplify-agent, which reaches EOL in January 2026.
Read the full article: NGINX slow_start: Gradual Upstream Ramp-Up Without Plus
